Yaojia Wang d6809c65c4 feat(v0.7): Walk-away Workbench (Band A + B) — multi-agent parallel build
Implements docs/PLAN_WALKAWAY_WORKBENCH.md (27 tasks, waves R0→W0→W1×14→W2→W3→W4)
via module-builder agents. 23 tasks built, 0 blocked.

Band A (finish the walk-away loop): A1 Web Push + lock-screen approve/deny
(web-push dep), A2 voice dictation, A3 quick-reply chips + saved-prompt palette,
A4 activity timeline, A5 stuck/idle alert.
Band B (workbench above the terminal): B1 read-only git diff viewer, B2 statusLine
telemetry → per-tab cost/context/PR gauges, B3 create git worktrees from the UI,
B4 plan-mode / permission-mode relay.

New: src/push/* (subscription store + VAPID push), src/http/{diff,statusline}.ts,
src/session/timeline.ts, public/{diff,timeline,quickreply,push-ui,...}.ts, sw-push,
statusLine script; extends hook intake, manager, server routes (Origin/CSRF guards
+ per-IP rate limits on state-changing ones; loopback-only ingest), terminal-session,
tabs, projects detail, service worker, setup-hooks (statusLine + ntfy bridge).

Orchestrator reconciled a W0 contract gap: added the 21 v0.7 Config fields to the
Config interface in types.ts (T-types had left them only in config.ts's return).

Verified: both tsc clean, full vitest + coverage 91.4/84.1/92.2/93.4 (≥80×4),
build:web OK. W4 review: no CRITICAL/HIGH; all security checks pass. Follow-ups
(non-blocking): move approve.mode validation into parseClientMessage, drop CSP
ws:/wss: wildcard, validate worktree base ref, +2 targeted tests.
2026-06-30 17:42:18 +02:00

Web Terminal

A browser-based terminal that exposes the host machine's local shell over WebSocket. Open http://<host-ip>:3000 from any device on your LAN (phone, tablet, another computer) and you get a live, interactive terminal — primarily for vibe coding: hand Claude Code a task, walk away, reconnect from anywhere to check on it.

Sessions survive disconnects: the shell (and whatever's running in it) keeps going when you close the tab; reconnect and the output replays.

⚠️ This hands a full shell to anyone who can reach the port. LAN-only, no authentication. Never port-forward or tunnel it to the public internet. See Security.

Features

  • Multi-tab — each tab is an independent shell session. Tabs auto-name to the current folder (double-click to rename), show a connection dot (🟢/🟡/🔴) and an unread-output dot, and can be drag-reordered. + opens a new tab in the active tab's directory.
  • Claude Code cockpit (with hooks, see below) — each tab shows Claude's status (⚙ working / needs approval / ✓ idle), sends a browser notification when it needs you, and lets you tap Approve / Reject on a tool request with no typing.
  • tmux keepalive (optional) — run the shell inside tmux so sessions survive a server/host restart, not just a disconnect.
  • Mobile + desktop shortcut bar — one-tap Esc / Esc·Esc / ⇧Tab / arrows / Enter / ^C / ^O / ^T / ^B / Tab / / (the Claude Code keys a phone keyboard can't produce).
  • Session keepalive + replay — the PTY keeps running across disconnects; reconnect replays a ~2 MB scrollback ring buffer.
  • Toolbar🔍 scrollback search · ⚙ themes & font size · ▦ all-sessions dashboard · 📱 QR connect (scan to open on another device). Clickable links. Installable as a PWA.

Requirements

  • Node.js ≥ 18 (developed on v24)
  • macOS/Linux. On macOS, node-pty compiles a native addon — install Xcode Command Line Tools (xcode-select --install) if npm install fails.

Install

npm install        # installs deps; postinstall makes node-pty's spawn-helper executable

Run

npm run build:web  # bundle the frontend (public/main.ts → public/build/main.js)
npm start          # serve on 0.0.0.0:3000

Then:

# find your LAN IP (macOS):
ipconfig getifaddr en0
# open http://<that-ip>:3000 on any device on the same network

For frontend development, run npm run dev:web (esbuild --watch) alongside npm start.

Claude Code cockpit (optional)

To see Claude's status per tab and approve/reject tool calls from your phone, install the hooks once:

npm run setup-hooks        # adds http hooks to ~/.claude/settings.json (backs it up)
# npm run setup-hooks -- --remove   # to uninstall

Then run claude inside a tab. The hooks POST to the server (loopback only) when Claude starts/stops/needs permission; the tab badge updates live, and a PermissionRequest shows an Approve / Reject bar. The hooks are a no-op outside web-terminal (they curl $WEBTERM_HOOK_URL, which is only set in spawned shells), so they're safe to leave installed.

tmux keepalive — run with USE_TMUX=1 (or just have tmux on PATH; it auto-detects) to keep sessions alive across a server restart:

USE_TMUX=1 npm start

Configuration

All via environment variables (no hardcoding):

Var Default Meaning
PORT 3000 listen port
BIND_HOST 0.0.0.0 listen address
SHELL_PATH $SHELL or /bin/zsh shell to spawn
IDLE_TTL 86400 (s) reclaim a detached session after this idle time
SCROLLBACK_BYTES 2097152 per-session replay ring buffer (bytes)
MAX_PAYLOAD_BYTES 1048576 max single WS frame
USE_TMUX auto 1/0/auto — run the shell inside tmux (keepalive across restart); auto = on if tmux is on PATH
ALLOWED_ORIGINS (derived) extra allowed WS origins, comma-separated

allowedOrigins is derived from the host's network-interface IPs (plus localhost and anything in ALLOWED_ORIGINS) — never from BIND_HOST, since 0.0.0.0 is never a real browser Origin.

Security

This is a no-auth, LAN-only tool by design. The defenses that matter:

  • Origin check (cannot be skipped): the WS handshake rejects any Origin not on the allow-list (HTTP 401). This blocks Cross-Site WebSocket Hijacking — a malicious page in your browser trying to connect to ws://<your-lan-ip>:3000.
  • Path-scoped upgrades: only /term is accepted for WS upgrade.
  • Frame size cap: oversized frames are rejected (MAX_PAYLOAD_BYTES).
  • Never expose to the public internet. ws:// is unencrypted — on an untrusted network (café/office Wi-Fi) traffic (including keystrokes: passwords, API keys) can be sniffed. The recommended deployment is Tailscale (WireGuard-encrypted), which also lets you use wss:// (the frontend auto-selects wss on HTTPS).

Development

npm test           # vitest, all modules
npm run typecheck  # tsc (backend + frontend)
npm run build      # compile backend to dist/

Real-PTY end-to-end tests (test/integration/) auto-skip where posix_spawn is unavailable (e.g. sandboxes) and run everywhere else.

How it works

The server is a byte-shuttle, not a terminal: node-pty provides a pseudo-terminal so the shell believes it has a real TTY, and xterm.js in the browser interprets the ANSI bytes and renders. The server never parses terminal semantics. PTY lifecycle is decoupled from the WebSocket — a disconnect detaches (the PTY keeps running) rather than killing it, which is what makes session survival work.

Design and rationale: docs/TECH_DOC.md (why) and docs/ARCHITECTURE.md (how).

Description
No description provided
Readme 12 MiB
Languages
TypeScript 61.2%
Swift 36.7%
CSS 1.1%
JavaScript 0.8%
HTML 0.1%
Other 0.1%