- Go 81.7%
- Vue 9.1%
- TypeScript 6.6%
- Makefile 1.6%
- HTML 0.4%
- Other 0.6%
| daemon | ||
| dashboard | ||
| server | ||
| .gitignore | ||
| Makefile | ||
| README.md | ||
| SPEC.md | ||
CCRM — Claude Code Remote Control
Remote monitoring and control of Claude Code sessions. Manages Claude Code as subprocesses via the Claude Agent SDK's JSON protocol, with the ability to attach interactively via tmux. Monitor activity via a web dashboard and send prompts remotely.
Architecture
┌─ Developer Machine ──────────────────────────────────────────────┐
│ │
│ ccrmd (daemon) │
│ ├─ Spawns claude as subprocess (JSON lines on stdin/stdout) │
│ ├─ Routes subprocess messages (system, assistant, result) │
│ ├─ Auto-allows tool permissions (PoC) │
│ ├─ Accepts CLI commands via Unix control socket │
│ └─ Connects to server via WebSocket │
│ │ │
│ On "ccrm attach": │
│ ├─ Stops subprocess, opens tmux with claude --resume <id> │
│ └─ On detach/exit: restarts subprocess with --resume │
│ │ │
└─────────┼────────────────────────────────────────────────────────┘
│ WebSocket
▼
┌─ Server ────────────────────────────────────────────────────────┐
│ ccrm-server │
│ ├─ REST API + WebSocket hub │
│ ├─ SQLite (sessions, events) │
│ └─ Vue 3 dashboard (embedded static files) │
└──────────────────────────────────────────────────────────────────┘
Dual-mode sessions
Each session operates in one of two modes, switchable at runtime:
SUBPROCESS MODE (default) INTERACTIVE MODE (attach)
┌─────────────────────────┐ ┌──────────────────────────┐
│ daemon process │ │ tmux session │
│ └─ claude subprocess │ ccrm attach │ └─ claude --resume <id>│
│ stdin ← JSON msgs │ ──────────────→ │ (full TUI) │
│ stdout → JSON msgs │ │ user types directly │
│ (daemon controls) │ ccrm detach / │ │
│ │ ←────────────── │ │
│ │ session exits │ │
└─────────────────────────┘ └──────────────────────────┘
- Subprocess mode — default. The daemon controls Claude, sends prompts via stdin, reads structured JSON responses from stdout. No tmux involved.
- Interactive mode — activated by
ccrm attach. The daemon stops the subprocess, opens a tmux session runningclaude --resume <session_id>, and the user interacts directly with the TUI. When the user detaches or exits, the daemon automatically resumes subprocess mode.
Components
| Component | Description |
|---|---|
daemon/ |
Go daemon (ccrmd) + CLI (ccrm) for session management |
server/ |
Go web server with REST API, WebSocket hub, and SQLite |
dashboard/ |
Vue 3 + TypeScript + Tailwind web UI |
Prerequisites
- Go 1.21+
- Bun
- tmux (only needed for
ccrm attach) - Claude Code CLI
Quick Start
1. Build
make all
This produces three binaries in bin/:
ccrm— CLI for session managementccrmd— Background daemonccrm-server— Web server
2. Configure the daemon
Create ~/.config/ccrm/config.yaml:
server:
url: "ws://localhost:8080/ws/daemon"
api_key: "change-me-daemon-key"
daemon:
socket_path: "/tmp/ccrm.sock"
tmux_prefix: "ccrm-"
log_level: "info"
claude:
binary: "claude"
3. Configure and start the server
# Default config — listens on :8080, SQLite at ./ccrm.db
./bin/ccrm-server
# Or with a config file:
./bin/ccrm-server --config server.yaml
Server config (server.yaml):
server:
listen: ":8080"
database:
path: "ccrm.db"
auth:
daemon_api_key: "change-me-daemon-key"
dashboard_token: "change-me-dashboard-token"
4. Start the daemon
./bin/ccrm daemon start
5. Start a session
cd /path/to/your/project
ccrm start --name my-project
The daemon spawns Claude as a subprocess. You can now send prompts or attach interactively.
6. Send a prompt
ccrm send my-project "list the files in this directory"
7. Attach interactively
ccrm attach my-project
This switches the session to interactive mode — you get the full Claude TUI. Detach with Ctrl-b d and the daemon automatically resumes subprocess control.
8. Open the dashboard
Navigate to http://localhost:8080 and enter your dashboard token.
CLI Reference
ccrm start [--name <n>] [--claude-args "..."] Start a new Claude Code session
ccrm list List all sessions (name, mode, status)
ccrm attach <name> Switch to interactive mode and attach
ccrm stop <name> Gracefully stop a session
ccrm kill <name> Force-kill a session
ccrm send <name> "prompt" Send a prompt to a session
ccrm daemon start Start the daemon in the background
ccrm daemon stop Stop the daemon
ccrm daemon status Check daemon status
All session commands communicate with the daemon via its Unix control socket. The daemon must be running.
REST API
All API endpoints require the dashboard token via Authorization: Bearer <token> header.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/sessions |
List sessions (?active=true to filter) |
| GET | /api/sessions/:id |
Get session details |
| GET | /api/sessions/:id/events |
List session events (?limit=N&offset=N) |
| POST | /api/sessions/:id/prompt |
Send a prompt ({"prompt": "..."}) |
WebSocket
| Endpoint | Auth | Description |
|---|---|---|
/ws/daemon |
X-API-Key header |
Daemon connection |
/ws/dashboard |
?token= query param |
Dashboard live updates |
Development
# Run each component in a separate terminal:
make dev-daemon # go run ./cmd/ccrmd
make dev-server # go run ./cmd/ccrm-server
make dev-dashboard # vite dev server on :5173 (proxies /api and /ws to :8080)
Makefile Targets
| Target | Description |
|---|---|
make all |
Build daemon, server, and dashboard |
make daemon |
Build ccrm and ccrmd binaries |
make server |
Build ccrm-server (includes dashboard) |
make dashboard |
Install dashboard dependencies |
make dashboard-build |
Build dashboard for production |
make dev-daemon |
Run daemon in dev mode |
make dev-server |
Run server in dev mode |
make dev-dashboard |
Run dashboard Vite dev server |
make clean |
Remove build artifacts |
Session Lifecycle
ccrm start → [starting] → system message → [idle]
│
prompt sent ──→ [active] → result → [idle] → drain queue
│
tool_use_permission → [waiting_permission] → auto-allow → [active]
│
ccrm stop/kill → [stopped]
Attach/detach cycle:
[subprocess/idle] → ccrm attach → [interactive/active] → detach → [subprocess/idle]
Control Socket Protocol
The daemon listens on a Unix socket (default /tmp/ccrm.sock) for JSON commands from the CLI.
| Command | Fields | Response |
|---|---|---|
session.start |
name, path, claude_args | Session JSON |
session.stop |
session_name | OK |
session.kill |
session_name | OK |
session.attach |
session_name | tmux_session name |
sessions.list |
— | Session array |
prompt.send |
session_name, prompt | prompt_status |
daemon.stop |
— | OK (then shuts down) |
Project Structure
ccrm/
├── daemon/ Go daemon + CLI
│ ├── cmd/
│ │ ├── ccrm/main.go CLI entry point
│ │ └── ccrmd/main.go Daemon entry point
│ └── internal/
│ ├── claude/ Subprocess protocol + process lifecycle
│ ├── config/ YAML config loading
│ ├── relay/ WebSocket client + protocol types
│ ├── session/ Session state machine + manager
│ └── tmux/ tmux operations (for attach mode)
├── server/ Go web server
│ ├── cmd/
│ │ └── ccrm-server/main.go
│ └── internal/
│ ├── api/ REST endpoints + auth
│ ├── config/ Server config
│ ├── db/ SQLite + migrations
│ └── ws/ WebSocket hub
├── dashboard/ Vue 3 frontend
│ └── src/
│ ├── views/ Login, Dashboard, SessionDetail
│ ├── components/ SessionCard, EventStream, PromptInput
│ ├── composables/ useWebSocket
│ ├── stores/ Pinia stores (auth, sessions)
│ └── types/ TypeScript types
└── Makefile
License
MIT