Claude Remote — MacLustr Command Center
A private, single-administrator web application that exposes the real Claude Code CLI, running inside
tmux on a Mac Studio of the MacLustr cluster, to any browser. Nothing is simulated: the browser terminal is
an xterm.js view attached to a tmux session in which claude (or a login shell) runs.
Production instance: https://claude.spboucher.ai → node M5M36 (deployed 2026-11-10).
This README is a summary. Every statement about behaviour is backed by the source; anything that is not implemented or not verified is listed under Limitations and in the "Limitations / not verified" section of each document.
Architecture
browser (desktop / phone)
│ HTTPS + WSS
▼
Caddy on the public gateway (BHS64) ── TLS termination, reverse proxy
│ WireGuard tunnel (the node dials out; no inbound port on the LAN)
▼
M5M36 ─ apps/server Fastify, port 8620
│ auth (password + passkey/TOTP), REST + WebSocket API /api/v1,
│ static web app (apps/web/out), PostgreSQL 17 (loopback), audit, notifications
│
│ unix socket (0600, dir 0700) + bearer token — HTTP and WebSocket
▼
apps/runtime no TCP listener; the only process that spawns anything
│ tmux sessions, node-pty attach, durable tasks, file/git operations,
│ Claude Code introspection, host metrics, hook endpoint
▼
tmux server (socket `-L claude-remote`)
cr-<uuid> one per terminal session: `claude …` or a login shell
crt-<uuid> one per background task (local, or `ssh <allow-listed node>`)Details: docs/architecture.md, docs/network-architecture.md, binding engineering contract: docs/CONTRACT.md.
Repository layout
| Path | Content |
|---|---|
packages/shared |
@cr/shared: zod schemas and types — state machines, browser DTOs, WebSocket wire formats, runtime IPC types |
apps/runtime |
Runtime daemon (Fastify on a unix socket): tmux, PTY attach, scrollback mirror, tasks, hooks, fs/git, skills, MCP, metrics. bin/cr-hook is the Claude Code hook relay |
apps/server |
Browser-facing server: authentication, API, WebSocket proxy, static files, migrations (migrations/*.sql) |
apps/web |
Next.js App Router static export (xterm.js, Monaco, React Query). dev/mock-server.mjs is a mock API used only by the Playwright suite; e2e/, e2e-real/ |
deploy/ |
make-manifest.py (mld manifest generator), backup.sh, restore.sh |
scripts/ |
dev-up.sh, dev-down.sh (local run), ci.sh (local CI gate) |
docs/ |
Documentation set (index below) |
.env.example |
Every configuration variable, without secrets |
Quick start (local development, macOS)
Requirements: Node ≥ 24, pnpm, tmux, a local PostgreSQL with psql/createdb on PATH, and the claude
CLI on PATH if you want Claude sessions (shell sessions work without it).
pnpm install
pnpm --filter @cr/web build # optional: without it dev-up serves the API only
scripts/dev-up.sh # builds shared/runtime/server, creates + migrates claude_remote_dev,
# starts both processes on http://localhost:8620
cat /tmp/cr-dev-data/setup-token # one-time token for the first-run page (/setup/)
scripts/dev-down.sh # stops both and kills ONLY the dev tmux server (claude-remote-dev)dev-up.sh uses /tmp/cr-dev-data (data), /tmp/cr-dev-workspace (workspace root), database
claude_remote_dev and its own tmux socket. It never sets CR_INSTALL_CLAUDE_HOOKS, so your
~/.claude/settings.json is not modified and sessions do not report WAITING_FOR_INPUT in this mode.
Tests
| Command | What runs |
|---|---|
scripts/ci.sh |
build shared → typecheck all → runtime tests → server tests → web unit tests → full build |
scripts/ci.sh --e2e |
the above + Playwright (apps/web/e2e, against the dev mock server) |
pnpm --filter @cr/runtime test |
runtime suite (real tmux, real claude binary, never sends a prompt) |
pnpm --filter @cr/server test |
server suite (one temporary Postgres database per file; integration/real-runtime.test.ts starts the real runtime) |
pnpm --filter @cr/web test |
web unit tests |
pnpm --filter @cr/web exec playwright test -c playwright.real.config.ts |
2 flows against a real backend started with scripts/dev-up.sh (web export built with NEXT_PUBLIC_E2E=1) |
Last recorded results (laptop, 2026-11-10): runtime 209 passed · server 175 passed (17 against the real
runtime) · web unit 12 passed · Playwright 19 passed against the mock (15 Chromium desktop, 4 WebKit
iPhone 15 emulation) and 2 passed against the real backend · scripts/ci.sh green.
Deployment summary
Deployed with the cluster orchestrator mld from the gateway (M1M32), pinned to M5M36:
- PM2 processes
claude-remote-runtime,claude-remote-server(port 8620),claude-remote-backup(nightly, 03:35); - PostgreSQL 17 on loopback (role and database
claude_remote); - code in
~/apps/claude-remote(git clone; the node directory is the source of truth), data in~/claude-remote-data(0700), workspace root~/workspace; - public route
https://claude.spboucher.ai→ Caddy (BHS64) → WireGuard → M5M36:8620.
Updating is done on the node (edit, build, migrate, restart, commit, push): see docs/deployment.md.
Status of the 17 deliverables
Detail, files, tests and limitations per item: docs/deliverables-status.md.
| # | Deliverable | Status |
|---|---|---|
| 1 | Working production web application | Deployed and verified (health, auth refusals, headers); no administrator created yet |
| 2 | Real Claude Code PTY runtime | Implemented; real claude 2.1.322 rendered in tmux on the node; Claude not yet authenticated |
| 3 | Secure private authentication | Implemented and tested; passkeys verified with a software authenticator only |
| 4 | Persistent terminal sessions | Implemented and tested for every failure mode except a real host reboot (simulated by killing the tmux server) |
| 5 | Polished desktop interface | Implemented; 15 Playwright tests (Chromium, mock API) + 2 against the real backend |
| 6 | Tested mobile interface | Implemented; tested in WebKit iPhone 15 emulation only |
| 7 | Project management | Implemented; create/existing tested end to end, clone not tested against the real git server from the app |
| 8 | File browsing and editing | Implemented and tested end to end |
| 9 | Skills and MCP management | Implemented; listings tested against the real CLI, mutations tested with mocks/fakes only; registry install returns 501 |
| 10 | Authorized MacLustr integration | Implemented (agentd telemetry, allow-listed SSH tasks); in-app remote tasks tested with a fake ssh only |
| 11 | Background task management | Implemented and tested end to end (local tasks) |
| 12 | Real observability | Implemented (system status, host metrics, audit trail, cluster view); no external alerting |
| 13 | Deployment configuration | Done (mld manifest generator, PM2, tunnel route, git adoption) |
| 14 | Backup and recovery procedures | Nightly encrypted backup + restore drill succeeded; backups are not yet copied off the node |
| 15 | Security documentation | Written (docs/security.md) |
| 16 | Automated tests | 396 unit/integration tests + 21 Playwright tests, local gate scripts/ci.sh; no hosted CI |
| 17 | Demonstrated laptop-independence test | Not yet demonstrated — pending the owner (laptop off, phone on cellular) |
Documentation index
| Document | Subject |
|---|---|
| docs/architecture.md | Processes, data flows, state machines, storage |
| docs/security.md | Threat model, implemented controls mapped to tests, residual risks |
| docs/authentication.md | First-run setup, login flows, sessions, throttling |
| docs/terminal-runtime.md | tmux sessions, attach, scrollback mirror, hooks, backpressure |
| docs/session-persistence.md | Exact guarantees per failure |
| docs/cluster-integration.md | agentd telemetry, remote tasks, git server, local models |
| docs/mobile-experience.md | Phone layout, key toolbar, gestures, PWA shell |
| docs/deployment.md | mld manifest, PM2, environment, first run, remote-first updates |
| docs/operations-runbook.md | Routine checks and procedures |
| docs/troubleshooting.md | Symptoms → causes → actions |
| docs/backup-recovery.md | Backup content, restore, disaster recovery |
| docs/api.md | API conventions; reference is /api/v1/openapi.json |
| docs/implementation-log.md | Chronological build log |
| docs/deliverables-status.md | The 17 deliverables in detail |
| docs/CONTRACT.md · infrastructure-inventory · node-selection · network-architecture · deployment-plan · migration | Earlier design documents (kept as written) |
Limitations / not verified
- Not verified on a real iPhone (Safari or installed PWA); emulation only.
- The laptop-independence test has not been run.
- Passkey login has not been exercised in a real browser.
WAITING_FOR_INPUThas not been observed from a real (paid) Claude conversation; the hook relay itself is tested end to end with the real hook script.- Web Push is not implemented (in-app notifications only). Skills-registry install is not implemented (501).
- There is no dedicated LaTeX / Jupyter / research feature; a PDF file preview exists in the editor, nothing more. TeX is not installed on the node.
- No local model endpoint exists on the cluster; the Local Models panel is empty by design.
- The application runs under the cluster operator's macOS account, not a dedicated OS identity (see docs/security.md).
- Single host: if M5M36 is down the service is down. Backups currently stay on M5M36.
- No hosted CI;
scripts/ci.shis run by hand. The rootlintscript references ESLint, which is not configured in this repository.