Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 48 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<p align="center">
<img src="docs/images/ptylon-logo.webp" alt="Ptylon logo" width="140" />
</p>

# Ptylon

> A self-hosted, browser-native terminal workspace — Termius in a browser tab.
Expand All @@ -6,15 +10,19 @@
[![Node.js 22+](https://img.shields.io/badge/node-22%2B-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

![Ptylon workspace with persistent terminal panes](docs/images/web-console-workspace.png)
![Ptylon server-hall terminal workspace](docs/images/ptylon-hero.webp)

**Ptylon** brings persistent terminals, browser tools, files, editing, and
workspaces into one browser tab. It is designed for a server you control—not a
hosted shell service.
**Ptylon** puts persistent terminals, a real browser, files, and an editor in a single browser tab on a server you own. It is built for running coding agents — **Claude Code, Codex, and anything else that lives in a shell** — where their sessions survive restarts, they can drive a real browser, and you can check on them from your phone.

| [Quick start](#quick-start) | [Screenshots](#screenshots) | [Architecture](#architecture) | [Production](#production-with-systemd) | [Contributing](CONTRIBUTING.md) |
| --- | --- | --- | --- |

## The Problem

Running a coding agent on a remote box is clunky. You SSH in, start Claude Code in tmux, and hope the session is still alive when you come back. The agent can run commands but can't *see* a browser to test what it built. Your files are on the server, your editor is somewhere else, and none of it opens on a phone. Close the laptop and the work is stranded.

Ptylon removes that friction: the agent runs in a persistent server-side session, gets a real browser it can click through, and the whole workspace — terminals, files, editor, browser — is one authenticated URL you can open from anything.

## Why Ptylon

- **Keep the shell alive.** A dedicated PTY daemon owns `node-pty` sessions, so a
Expand All @@ -25,6 +33,18 @@ hosted shell service.
reverse-proxy guidance, a production smoke check, and an explicit security
boundary around the server workspace.

## Built for AI Agents

- **Persistent agent sessions.** A dedicated PTY daemon owns the shell, so an agent like Claude Code keeps running when the web gateway restarts or you switch devices. Only an explicit daemon restart ends a session.
- **A browser the agent can see.** Server-side Chrome renders as a live panel and is scriptable over CDP through the `webc` CLI and a loopback admin API — the agent can open a page, click, type, and screenshot the same session you see.
- **"It needs you" notifications.** A running command raises an unread badge via OSC escapes, so you know when the agent is waiting on input without watching it.
- **Everything in one tab.** Split panes, named workspaces, a file manager, and a Monaco editor sit next to the terminal.
- **Reachable from anything.** The workspace is one authenticated URL — open it from a laptop or a phone and pick up where the agent left off.

### A typical session

Open Ptylon, start Claude Code in a pane, and ask it to build a feature. It edits files, runs the dev server, and opens a browser panel to check the result — you watch it click through the page. You split off a second terminal for logs and close the laptop. On your phone later you reopen the same URL: the agent is still running, the browser panel is still there, and you approve the next step.

## Screenshots

<p align="center">
Expand Down Expand Up @@ -53,19 +73,21 @@ browser
-> localhost PTY daemon :8792
-> raw node-pty bash sessions

SQLite data: ./data/web-console.db
SQLite data: `./data/web-console.db` (legacy default; override with `WEB_CONSOLE_DB_PATH`)
Workspace state: localStorage + SQLite
Uploads: configurable UPLOAD_DIR
```

Services:
Service roles (the current systemd unit filenames retain the legacy `web-console` name for compatibility):

```text
web-console.service Next.js standalone app, port 8790
web-console-ws.service authenticated WebSocket gateway, port 8791
web-console-pty.service localhost-only PTY daemon, port 8792
```

Architecture decisions: [raw `node-pty` backend](docs/adr/001-raw-node-pty-backend.md) and [restartable WebSocket gateway](docs/adr/002-restartable-websocket-gateway.md).

## Requirements

- Node.js 22+
Expand Down Expand Up @@ -306,3 +328,23 @@ Some sites detect headless Chrome or datacenter/server IPs. Anti-bot flows such
- Server-side browser panels can be blocked by headless-browser or IP reputation checks even when normal pages and local previews work.
- Click-to-cursor across wrapped input lines is covered by `pnpm test:browser-regression`; keep that browser smoke passing before changing terminal input handling.
- For private forks or self-hosted deployments, rotate any real `.env` secrets before publishing a fork or support bundle.

## Roadmap

**Shipped**
- Persistent PTY daemon with a restart-safe WebSocket gateway
- Server-side browser panels with CDP control via `webc`
- Docker Compose and systemd deployment, theme gallery, mobile controls

**Next**
- Headed / VNC browser mode for anti-bot targets
- A session backend that survives host reboots
- Published container image

## Acknowledgments

Built on [xterm.js](https://xtermjs.org), [node-pty](https://www.npmjs.com/package/node-pty), the [Monaco Editor](https://microsoft.github.io/monaco-editor/), and [Next.js](https://nextjs.org).

## License

[MIT](LICENSE) — alexfrmn, 2026
21 changes: 21 additions & 0 deletions docs/adr/001-raw-node-pty-backend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# ADR 001: Keep `node-pty` as the terminal backend

## Status

Accepted

## Context

Ptylon provides browser-accessible terminal sessions that must behave like
ordinary shell processes and remain owned by the long-lived PTY daemon.

## Decision

Use raw [`node-pty`](https://github.com/microsoft/node-pty) sessions. Do not
place tmux, zellij, or another terminal multiplexer behind the default backend.

## Consequences

PTY semantics, process ownership, and signal handling stay close to a native
terminal. The daemon can recover its own live sessions, while higher-level
multiplexer features remain an optional user choice inside a shell.
21 changes: 21 additions & 0 deletions docs/adr/002-restartable-websocket-gateway.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# ADR 002: Separate the restartable WebSocket gateway from PTY ownership

## Status

Accepted

## Context

Browser connections can fail or require a gateway restart. Restarting a
browser-facing component must not terminate coding-agent or user shell work.

## Decision

The localhost-only `web-console-pty.service` owns `node-pty` processes. The
authenticated `web-console-ws.service` only proxies terminal I/O to that daemon.

## Consequences

Restarting the WebSocket gateway preserves live shell sessions. Restarting the
PTY daemon intentionally ends them, so it remains protected from unattended
automatic restarts.
Binary file added docs/images/ptylon-hero.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/ptylon-hero.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/ptylon-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/ptylon-logo.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/icon-192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/icon-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion public/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Ptylon",
"short_name": "Console",
"short_name": "Ptylon",
"description": "Browser-based terminal workspace",
"start_url": "/",
"display": "standalone",
Expand Down
2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const metadata: Metadata = {
appleWebApp: {
capable: true,
statusBarStyle: "black-translucent",
title: "Console",
title: "Ptylon",
},
};

Expand Down