- Python 78%
- JavaScript 10.5%
- CSS 7.9%
- HTML 1.9%
- Shell 1.7%
|
|
||
|---|---|---|
| .github/workflows | ||
| backlog | ||
| config | ||
| docs | ||
| hybrid_agent | ||
| scripts | ||
| static | ||
| systemd | ||
| tests | ||
| .gitignore | ||
| AGENTS.md | ||
| app.py | ||
| CLAUDE.md | ||
| LICENSE | ||
| push.py | ||
| pyproject.toml | ||
| README.md | ||
| requirements-dev.txt | ||
| requirements.txt | ||
| supervisor.py | ||
claude-code-pwa
A self-hosted PWA dashboard that supervises Claude Code remote-control servers on a remote machine, from your phone.
You declare the folders you care about. The backend keeps exactly one healthy claude remote-control server alive per folder — restarting it on crash or reboot, backing off when it keeps failing, and raising an attention banner when a folder needs a human (a /login, a trust prompt). Those servers are what the Claude desktop/mobile app connects to, so starting a new session from your phone is a native Claude-app action; this PWA's job is to make sure there is always something for it to connect to.
- Supervised folders — declare a folder once; the reconcile loop keeps its anchor server running (
claude remote-control --permission-mode bypassPermissions --spawn same-dir --name <name>) - Restart / pause / resume / remove per folder, from the phone
- Attention banner when a folder is
needs_attentionorunhealthy, with a one-tap terminal into that anchor - Adopt an already-running remote-control server discovered in the shared tmux session, turning it into a supervised folder
- Other sessions — live, non-supervised Claude sessions found in the tmux session, with end / bulk-end
- Interactive terminal per session or anchor — full xterm.js over WebSocket, attached to the tmux window via a PTY (grouped tmux session, so your desktop client keeps its own view). Includes a mobile key bar (esc / tab / ⇧tab / arrows / ^c / ⏎)
- Uptime Kuma heartbeat that reports down when any folder is unhealthy or the reconcile loop stalls
- Installable on iOS/Android home screen as a PWA
Pair with Tailscale or any other private network to reach your dev machine securely from anywhere.
Note: this used to be a session launcher (
POST /api/sessions, history, dormant-session resume). Those paths were removed in the supervisor pivot. Sessions the PWA never saw are no longer replayed from history — only what is live in tmux, plus what you declare insupervised.json, is shown.
Architecture
┌─ phone (PWA) ──────────────────┐ ┌─ dev machine ──────────────────────┐
│ │ │ │
│ Installable PWA │ │ FastAPI backend (this repo) │
│ (HTML/CSS/JS, no framework) │◄─────►│ ├─ /api/supervised* CRUD │
│ │ │ └─ supervisor.py reconcile loop │
└─────────────────────────────────┘ │ (one asyncio lock, 15 s tick) │
│ │
┌─ Claude mobile / desktop app ──┐ │ tmux session "claude" │
│ connects to an anchor server │◄──────►│ ├─ window: sup-ironpulse (anchor) │
│ and starts sessions in it │ │ ├─ window: sup-nasdocker (anchor) │
└─────────────────────────────────┘ │ └─ window: <other live sessions> │
│ │
│ anchor = claude remote-control │
│ --permission-mode bypassPermissions
│ --spawn same-dir --name <name> │
└────────────────────────────────────┘
Requirements
- Linux with
systemd(or adapt for your init of choice) - Python 3.10+
tmux- Claude Code CLI 2.1.263 or newer, logged in — the supervisor uses the
claude remote-controlsubcommand, not the older--remote-controlflag - Optional: Tailscale or a reverse proxy if you want to reach it from outside your LAN
Install
git clone https://git.hiten-patel.co.uk/hiten/claude-code-pwa.git ~/dev/claude-code-pwa
cd ~/dev/claude-code-pwa
./scripts/install.sh
The install script will:
- Create a
.venv/inside the project and install deps - Create
~/.claude-pwa/for runtime data (config, state, logs) - Generate a random bearer token (stored in
~/.claude-pwa/config.json) - Generate PWA icons
- Install + start a systemd user service that binds to
0.0.0.0:8866 - Enable
loginctl enable-lingerso the service survives logout
Print the URL + token at the end. Open it on your phone, enter the token once, and tap Add to home screen in your browser.
Usage
- Tap + → enter a folder (e.g.
~/dev/my-project), optional name and model → Add - The reconcile loop pre-seeds workspace trust, spawns the anchor, and watches its pane until it reports ready. The card goes
starting→ok - Start sessions in that folder from the Claude app — the anchor is the server they connect to
- Restart respawns the anchor. Pause stops it and leaves the folder declared. Remove stops it and drops the declaration
- If a folder shows
needs_attention(usually a first-time/login), the banner links straight to a terminal on that anchor — deal with it there once and the loop resumes - Other sessions lists live Claude sessions that aren't anchors; Adopt promotes one to a supervised folder
Status model
| Status | Meaning |
|---|---|
starting |
anchor spawned, pane not yet reporting ready |
ok |
ready marker seen and stable for ready_stable_seconds (default 30) |
needs_attention |
pane wants a human (/login, trust prompt). Respawn is suspended until resolved |
unhealthy |
window died; respawn scheduled under backoff (10s / 30s / 60s / 300s, 5 failures max) |
paused |
enabled: false — the loop leaves it alone |
Runtime data
All writable state lives at ~/.claude-pwa/:
~/.claude-pwa/
├── config.json # bearer token(s), claude binary path, allowed_roots, kuma URL
├── supervised.json # declared folders: [{id, cwd, name, model, enabled}]
├── supervisor_state.json # runtime per folder: status, window_id, restarts, backoff
├── state.json # currently active session
├── claude_urls.json # bridgeSessionId → claude.ai/code URL cache
└── logs/
└── pwa.log # rotated at 10 MB × 5 backups
This directory is intentionally outside the repo so the repo stays clean.
Both supervisor files are written atomically (temp file + os.replace). A malformed supervised.json is logged and the last good in-memory state is kept — the supervisor never collapses to an empty list, and never mass-kills anchors because a file got truncated. On first boot with no supervised.json, the supervisor seeds it from anchors already live in the tmux session.
Backup & restore
./scripts/backup.sh ~/backups/claude-pwa # → ~/backups/claude-pwa/claude-pwa-YYYYMMDD-HHMMSS.tar.gz
./scripts/restore.sh ~/backups/claude-pwa/claude-pwa-20260421-091500.tar.gz [--force]
The backup excludes logs/ and prunes archives older than KEEP_DAYS (default 30). Cron-friendly.
Optional Uptime Kuma heartbeat
Add a kuma_push_url to config.json and the backend will curl it every 60s (configurable via kuma_push_interval). If unset, no heartbeat task runs.
The heartbeat is status-aware: it pushes down when any supervised folder is in a status other than ok/paused, and also when the reconcile loop itself has stalled (no tick for 3 × supervisor_interval), with the offending folder ids in the message. So Kuma alerts on "a folder needs a login" as well as on "the process died".
Security
- The backend binds to
0.0.0.0by default. Restrict access via firewall / Tailscale ACLs / reverse proxy — the bearer token is a second line of defense, not the first. For non-LAN deployments, see docs/reverse-proxy.md for Caddy and Tailscale Funnel recipes that terminate HTTPS in front of the PWA. - Anchors are spawned with
--permission-mode bypassPermissions, and the supervisor pre-seedshasTrustDialogAcceptedin~/.claude.jsonfor each declared folder. Anyone who gains authenticated access to the PWA — or to the Claude account those anchors are logged into — can run anything on your machine. Treat the token like an SSH key. - Multi-token auth is supported: define
auth_tokensin~/.claude-pwa/config.jsonas a list of{"name": "...", "token": "..."}so each device has its own revocable token. Generate withscripts/add-token.sh <name>. - Folder allow-list:
POST /api/supervisedaccepts only paths under the configuredallowed_roots(default["~"]). Paths are symlink-resolved before the check, so escapes outside the roots are rejected.
Hybrid delegation
Claude can remain the main orchestrator for every remote-control session while delegating bounded planner, reviewer, and implementer stages to subscription models or OpenRouter fallbacks. This uses Claude Code hooks and a local CLI; MCP is not required. See docs/hybrid-delegation.md for secure setup, concurrency, and rollback.
Development
See AGENTS.md for repository layout, conventions, and a quick orientation for humans or AI agents picking up the codebase.
Backlog and milestones live on Forgejo: https://git.hiten-patel.co.uk/hiten/claude-code-pwa/issues
License
MIT