A local web dashboard for all your Claude Code sessions — live and historical. See every session's status at a glance, drill into transcripts, tasks, and token stats, mirror a live tmux pane in the browser, send prompts, answer permission dialogs, and interrupt turns — all from one page. Zero npm dependencies (Node stdlib backend + vanilla-JS frontend), no build step.
- Binds 127.0.0.1 only; requests with a non-localhost
Hostheader get 403. - A random per-launch token is required on every
/api/*call (X-CCC-Tokenheader, or?token=for the EventSource URL). It is printed once at startup and never persisted. - This server can type into your terminals (tmux
send-keys) — treat the URL+token like a credential. Keys are restricted to a fixed whitelist (Enter, Escape, arrows, Tab, BSpace, C-c, 1–9, y, n); prompts go through the tmux paste-buffer; only panes mapped to live Claude processes (or panes the server launched) are addressable. - Strictly read-only on
~/.claude— never writes, never deletes stale pid files. From~/.claude.jsonit reads only the.projectskey names (the file holds secrets; nothing else is exposed). - The server invokes tmux/osascript via
execFilewith argv arrays — no shell, ever. Thebin/ccclauncher is a shell script but builds no command strings: quoted argv only, noeval. - Static serving is a fixed 3-file whitelist (
/,/app.js,/style.css); request input is never joined into a filesystem path. - No dollar/cost figures anywhere in the API or UI (token counts only).
- macOS (notifications use the built-in
osascript) - Node >= 18
- tmux (>= 3.2 for direct-exec launches; 3.6b verified)
- the
claudeCLI on your PATH
git clone git@github.com:jskysim/claude-control-center.git
cd claude-control-centerNo npm install needed — there are zero dependencies.
./bin/ccc # or: node server.jsOpen the printed http://127.0.0.1:7777/?token=... URL. Set CCC_PORT to
change the port. Optionally symlink the launcher onto your PATH:
ln -s "$PWD/bin/ccc" ~/bin/ccccd into a project and run:
ccc claude # instead of plain `claude`
ccc claude --resume # any claude args pass through verbatimThis wraps claude in a ccc-<dir>-<rand> tmux session so the dashboard can
control it (send prompts, answer dialogs, interrupt). Plain (non-tmux)
terminal sessions still appear in the dashboard but are view-only — the
prompt bar shows a relaunch hint. Any claude already running inside tmux is
auto-adopted; ccc claude is convenience, not a requirement.
- Detach with
Ctrl-b d— the session keeps running and stays controllable. tmux lslists sessions;tmux attach -t ccc-...re-attaches (the dashboard's Info tab has a copy button for the exact command).- Killing the pane/window ends the Claude session.
All routes require the token. :id is a session id.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/ping |
liveness check |
| GET | /api/sessions |
all sessions, sorted busy → ready → idle → done |
| GET | /api/sessions/:id |
full record for one session |
| GET | /api/sessions/:id/transcript?tailKb=64 |
seed transcript entries (rest via SSE) |
| GET | /api/sessions/:id/tasks |
task list |
| GET | /api/stats |
activeSessions / sessionsToday / messagesToday / tokensTodayByModel |
| GET | /api/projects |
known project cwds (for the New Session picker) |
| GET | /api/events?token=... |
SSE stream |
| POST | /api/sessions/new |
{cwd, prompt?} → launch claude in a new ccc-* tmux session |
| POST | /api/watch |
{sessionId|null} → select which pane the server mirrors |
| POST | /api/sessions/:id/prompt |
{prompt} → paste+Enter; resumes done sessions into tmux |
| POST | /api/sessions/:id/keys |
{key} → whitelisted key (dialogs, Esc, arrows) |
| POST | /api/sessions/:id/kill |
kill the session's tmux pane (404 if not tmux-backed) |
SSE event names: sessions (full snapshot), transcript (appended entries),
pane (rendered screen of the watched session), notify (in-page mirror of
macOS notifications), plus comment heartbeats every 25s.
Headless E2E covers binding/auth, the read-only and exec-discipline audits, adoption, prompt/keys/kill delivery, pane mirroring, notification debounce, and SSE — but a few checks need a human at a browser/terminal:
- Select a tmux session → the Live tab mirrors keystrokes you type in the real terminal within ~1s.
- Send a prompt from the GUI → it appears in the real terminal and Claude runs; the Transcript tab streams the durable turn.
- Trigger a permission dialog → answer it with the
[1]quick-key button → Claude proceeds. [Esc]mid-turn interrupts; busy→ready fires exactly one macOS banner.- Prompt a
donesession → a newccc-*session appears intmux lsand is attachable; the response streams in. - Multiline prompt into the real claude TUI pastes without premature submit (bracketed paste).
- New Session modal → a new JSONL appears under the right
~/.claude/projects/...directory.