Claude Controller
OPEN SOURCE · 2026Claude Code's built-in remote is a chat relay — you can read along, but you can't approve a tool, switch modes, or drive a picker from your phone. And anything cloud-hosted bills the paid API.
Claude Controller spawns the real Claude Code CLI on your own machine and relays it to a custom mobile PWA over a private Tailscale tunnel. From your phone you approve tool requests with one tap, switch model / effort / permission mode mid-run, send slash commands, answer pickers, and watch live status — all on your Max-plan credits. It never calls the paid API.
A terminal relay, not an API client. The backend spawns the CLI in a PTY and consumes two structured signals it already emits — blocking HTTP hooks for approvals and pickers, and the transcript JSONL for content — normalizing both into a per-session bus forwarded over WebSocket. Transport (encryption, TLS, device auth) is delegated to Tailscale and Caddy.
PHONE · PWA
React 19 PWA renders message cards and sends taps over WSS
Vite · Tailwind 4
TAILSCALE + CADDY
WireGuard tunnel + TLS, bound to the tailnet IP only
WireGuard · Let's Encrypt
BACKEND
spawns the CLI in a PTY; loopback hooks + JSONL tail → SessionBus
node-pty · ws
CLAUDE CODE CLI
the real CLI on your machine, on Max-plan credits
no API
Most of the protocol is clean structured data. The friction is everywhere the CLI is interactive — built for a person at a keyboard, not a phone:
terminal relay, no API
the CLI runs in a PTY; all content comes from blocking hooks + the JSONL transcript — never screen-scraping, never a paid API call
driving ink pickers
AskUserQuestion and plan-mode are interactive pickers — shown as tappable cards, then driven over the PTY with timed keystrokes reverse-engineered from the CLI source
statusline IPC
statusLine isn't HTTP-hookable, so the CLI runs a committed dump script that writes its payload to disk; the session watches it for live model, context, and cost
confirmed input
prompts go as bracketed paste with the submit sent separately and confirmed against the transcript — a trailing newline coalesced into a big paste gets dropped
calls to the paid API — your Max-plan credits
tool approvals, mid-session
backend binds 127.0.0.1 — Caddy alone faces the tailnet
open source