- Python 69.4%
- JavaScript 14.4%
- CSS 8.1%
- Shell 5.2%
- HTML 2.9%
|
All checks were successful
CI / lint-and-test (push) Successful in 35s
Moves linting/testing from x86 NAS runner to ARM Oracle VM runner. Python tooling works natively on ARM. 2x faster CPU, 3x more RAM. |
||
|---|---|---|
| .github/workflows | ||
| docs | ||
| scripts | ||
| static | ||
| systemd | ||
| tests | ||
| .gitignore | ||
| AGENTS.md | ||
| app.py | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
| requirements-dev.txt | ||
| requirements.txt | ||
claude-code-pwa
A self-hosted PWA dashboard for spawning and managing Claude Code sessions on a remote machine, from your phone.
Designed as an alternative to routing claude through a Telegram bot. Sessions run as interactive claude --remote-control --name processes inside a shared tmux session, so they appear — with correct names — in the Claude desktop/mobile app. The PWA adds:
- Create sessions in any folder, with optional custom names (
/cc+/mkcc) - List active + historical sessions with live/dead status
- Chat to the active session from your phone (sends via
tmux send-keys, captures the pane, strips ANSI/prompt chrome) - End sessions (kills the tmux window and deletes the session file so it disappears from the Claude app too)
- 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.
Architecture
┌─ phone (PWA) ──────────────────┐ ┌─ dev machine ──────────────────────┐
│ │ │ │
│ Installable PWA │ │ FastAPI backend (this repo) │
│ (HTML/CSS/JS, no framework) │◄─────►│ ├─ REST API for session CRUD │
│ │ │ └─ shells out to tmux + claude │
└─────────────────────────────────┘ │ │
│ tmux session "claude" │
│ ├─ window: ironpulse (manual) │
│ ├─ window: nasdocker (manual) │
│ └─ window: pwa-<uuid> (PWA) │
│ └─ claude --remote-control │
│ --name <name> │
│ --session-id <uuid> │
└────────────────────────────────────┘
│
▼
Claude cloud / app
Requirements
- Linux with
systemd(or adapt for your init of choice) - Python 3.10+
tmux- Claude Code CLI installed and logged in (
claude auth login) - 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) and optional name → Start - Claude boots (~5 s), then tap the session to open the chat view
- Messages you send go through
tmux send-keys; responses are extracted fromtmux capture-pane - The session is also visible in the native Claude app (same session UUID + name), so you can continue there
- End (menu in chat view) kills the tmux window and deletes the session file
Runtime data
All writable state lives at ~/.claude-pwa/:
~/.claude-pwa/
├── config.json # bearer token(s), claude binary path, allowed_roots, kuma URL
├── history.json # session history (persists across restarts)
├── 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.
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.
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. - Claude is invoked with
--dangerously-skip-permissions. Anyone who gains authenticated access to the PWA 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/sessionsaccepts only paths under the configuredallowed_roots(default["~"]). Symlink escapes outside the roots are rejected.
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