@idl3/claude-control
v0.1.22
Published
Local web UI to watch and drive your Claude Code sessions running in tmux — live transcripts, reply, answer AskUserQuestion, attach files, from a browser or phone.
Maintainers
Readme
claude-control
A tiny, local web UI to watch and drive your Claude Code sessions from a
browser or phone. It discovers the Claude sessions you already run inside
tmux, streams each session's transcript live, lets you reply, answer
AskUserQuestion prompts, attach screenshots/files, and capture the pane — all
over 127.0.0.1 (or your Tailscale tailnet), behind an optional token you enter
in-app (never in the URL).
No daemon to babysit, no database: it reads Claude Code's transcript files and talks to tmux. Bind is localhost-only by default.
Install (npm)
npm install -g @idl3/claude-control # or run once: npx @idl3/claude-controlPrerequisites: Node ≥20 and tmux on your PATH (brew install tmux · sudo apt install tmux). Optional: ttyd for the in-browser raw terminal (brew install ttyd · sudo apt install ttyd) — set CLAUDE_CONTROL_TTYD to override its path. The web UI ships prebuilt — no build step on install.
Optional local AI (no API key):
- Voice → text —
brew install ffmpeg whisper-cppand drop a model at~/.claude-control/models/ggml-base.en.bin. The mic in the composer records audio and transcribes it locally. - Prompt enhancer (✨) — defaults to a local MLX model on Apple Silicon. One-time setup:
claude-control lazily startspython3 -m venv ~/.claude-control/mlx-venv ~/.claude-control/mlx-venv/bin/pip install mlx-lmmlx_lm.serveron first use, keeps it warm, and shuts it down when idle. The model (defaultmlx-community/Llama-3.2-3B-Instruct-4bit, ~1.8 GB) auto-downloads on first run. Pick the backend + model in Settings (mlx→claude -p→ rules fallback). Without the venv (or on non-Apple hardware) the enhancer falls back toclaude -p, then a deterministic rules optimiser. Env overrides:CLAUDE_CONTROL_MLX_PYTHON,CLAUDE_CONTROL_MLX_PORT.
claude-control # start the server (prints the URL)
claude-control --help # config + subcommands
claude-control install-service # macOS: launchd auto-start on login + restart on crash
claude-control uninstall-serviceOpen the printed URL. If a token is configured (env CLAUDE_CONTROL_TOKEN, or a
token in ~/.claude-control/token), the app prompts for it on first load and
stores it in your browser — the token is never placed in the URL. With no token
set, it runs open on 127.0.0.1 / your tailnet.
Quick start (from source)
git clone https://github.com/idl3/claude-control.git
cd claude-control
npm install
npm run build # builds the web UI (web/dist)
npm start # prints the URLOpen the printed URL (e.g. http://127.0.0.1:4317/). If a token is configured,
the app prompts for it on first load and remembers it in your browser — it's
never put in the URL. Any Claude Code session running in tmux shows up in the
left rail.
Already have tmux running with Claude sessions? You're done — just run
npm startand they appear automatically.
The tmux setup (the one requirement)
claude-control manages sessions through tmux: it lists tmux windows, finds the ones running Claude Code, and sends your replies as keystrokes to the right pane. So your Claude sessions need to live in tmux.
A) You already use tmux
Nothing to do. claude-control reads your default tmux server (the same one
tmux ls shows). Start it and your sessions appear. To point at a non-default
tmux binary, set CLAUDE_CONTROL_TMUX=/path/to/tmux.
B) You don't use tmux yet
Install it and run Claude inside a tmux session so claude-control can see it:
# macOS: brew install tmux · Debian/Ubuntu: sudo apt install tmux
tmux new -s work # start (or attach) a tmux session
claude # run Claude Code inside it — now it's discoverableThat's it. Open more windows (Ctrl-b c) and run more Claude sessions; each
becomes a row in claude-control. (Tip: detach with Ctrl-b d — the sessions
keep running and stay visible in claude-control.)
A session is recognized when its pane is running Claude Code or has a
matching transcript under ~/.claude/projects/.
Updating
claude-control compares your checkout against its git upstream (origin) and
shows an update banner when new commits are available. Click Update now
— the server pulls, reinstalls, rebuilds the web bundle, and restarts itself in
place; the page reconnects automatically. (Equivalent manual update: git pull
&& npm install && npm run build, then restart.)
Version numbers follow npm semver (bump package.json per release); this is
v0.1.0.
Configuration
All optional. Prefer CLAUDE_CONTROL_*; legacy COCKPIT_* names still work.
| Env | Default | Purpose |
|---|---|---|
| CLAUDE_CONTROL_PORT | 4317 | HTTP/WS port |
| CLAUDE_CONTROL_HOST | 127.0.0.1 | Bind address |
| CLAUDE_CONTROL_TOKEN | (none) | Access token. Also read from ~/.claude-control/token. Sent as Authorization: Bearer (HTTP) / WS subprotocol — never in the URL. Unset and no file ⇒ tokenless. |
| CLAUDE_CONTROL_PROJECTS | ~/.claude/projects | Where Claude Code transcripts live |
| CLAUDE_CONTROL_UPLOADS | ~/.claude-control/uploads | Where attachments are stored (TTL-swept) |
| CLAUDE_CONTROL_TMUX | (auto) | tmux binary override |
| CLAUDE_CONTROL_MAX_UPLOAD_MB | 25 | Per-file upload cap |
Security
- Binds
127.0.0.1by default; cross-origin WebSocket upgrades are rejected. - Token auth — strongly recommended before exposing it (e.g. via
tailscale serve): this UI can type into your live sessions. The token is resolved in order fromCLAUDE_CONTROL_TOKEN, else the file~/.claude-control/token(mode0600). With neither set it runs tokenless (open to anything that can reach the port — the127.0.0.1bind, tailnet ACL, and cross-origin check are the only guards).- The web app prompts for the token on first load and stores it in
localStorage. It's sent as anAuthorization: Bearerheader (and a WS subprotocol) — never placed in the URL (URLs leak via history, server logs, and referrer headers). A401returns you to the prompt. - Set or rotate: write the token to
~/.claude-control/token, then restart —launchctl kickstart -k gui/$(id -u)/com.ernest.claude-control(launchd service), or just re-runnpm start/claude-control. Each browser re-prompts once.bin/install-service.shreads the same file.
- The web app prompts for the token on first load and stores it in
- Uploads are written
0600under the uploads dir and swept after a TTL.
How it works
- Discovery — polls
tmux list-windowsevery few seconds and matches each window to the newest transcript for its cwd (lib/sessions.js). - Transcript — tails each subscribed session's
*.jsonl(bounded reads) and streams appends over WebSocket (lib/transcript.js). - Input — replies and answers are sent with
tmux send-keysto the exact pane (lib/tmux.js); attachments upload to the uploads dir and their path is appended to the message for Claude to read.
Development
npm run dev # server with --watch
cd web && npm run dev # Vite dev server for the UI
npm test # node:test unit testsLicense
MIT
