Self-hosted PWA launcher for Claude Code sessions on a remote dev box
  • Python 69.4%
  • JavaScript 14.4%
  • CSS 8.1%
  • Shell 5.2%
  • HTML 2.9%
Find a file
Hiten Patel 8178b58d7a
All checks were successful
CI / lint-and-test (push) Successful in 35s
ci: migrate to arm64 runner (Oracle VM)
Moves linting/testing from x86 NAS runner to ARM Oracle VM runner.
Python tooling works natively on ARM. 2x faster CPU, 3x more RAM.
2026-04-24 16:37:08 +00:00
.github/workflows ci: migrate to arm64 runner (Oracle VM) 2026-04-24 16:37:08 +00:00
docs v0.3.0 hardening: allow-list, multi-token, kuma, backups, https docs 2026-04-21 12:59:40 +00:00
scripts v0.3.0 hardening: allow-list, multi-token, kuma, backups, https docs 2026-04-21 12:59:40 +00:00
static After /clear, preserve cloud bridge URL via original_session_id 2026-04-22 21:42:08 +00:00
systemd v0.2.0 foundation: tests, lint, log rotation, CI 2026-04-21 11:20:34 +00:00
tests Auto-prune ghost PWA history entries 2026-04-23 06:57:25 +00:00
.gitignore v0.2.0 foundation: tests, lint, log rotation, CI 2026-04-21 11:20:34 +00:00
AGENTS.md v0.3.0 hardening: allow-list, multi-token, kuma, backups, https docs 2026-04-21 12:59:40 +00:00
app.py Auto-prune ghost PWA history entries 2026-04-23 06:57:25 +00:00
LICENSE Initial commit: claude-code-pwa 2026-04-21 06:57:34 +00:00
pyproject.toml v0.2.0 foundation: tests, lint, log rotation, CI 2026-04-21 11:20:34 +00:00
README.md v0.3.0 hardening: allow-list, multi-token, kuma, backups, https docs 2026-04-21 12:59:40 +00:00
requirements-dev.txt v0.2.0 foundation: tests, lint, log rotation, CI 2026-04-21 11:20:34 +00:00
requirements.txt Initial commit: claude-code-pwa 2026-04-21 06:57:34 +00:00

claude-code-pwa

CI

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:

  1. Create a .venv/ inside the project and install deps
  2. Create ~/.claude-pwa/ for runtime data (config, state, logs)
  3. Generate a random bearer token (stored in ~/.claude-pwa/config.json)
  4. Generate PWA icons
  5. Install + start a systemd user service that binds to 0.0.0.0:8866
  6. Enable loginctl enable-linger so 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 from tmux 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.0 by 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_tokens in ~/.claude-pwa/config.json as a list of {"name": "...", "token": "..."} so each device has its own revocable token. Generate with scripts/add-token.sh <name>.
  • Folder allow-list: POST /api/sessions accepts only paths under the configured allowed_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