clarc/docs/plan.md
Jared Miller 350fcf3713
Add implementation plan to docs
Copied from planning phase - full specification for PTY wrapper,
WebSocket protocol, SQLite schema, and deployment setup.
2026-01-28 10:59:48 -05:00

3.6 KiB

claude-remote

self-hosted remote control for claude code, pure bun stack.

summary

wrap claude cli in PTY, stream output to server, approve prompts from phone.

tech stack

  • bun-pty (native PTY via FFI to Rust)
  • bun:sqlite (persistence)
  • Bun.serve() (HTTP + WebSocket + SSE)
  • no frameworks

project location

/home/jtm/projects/agentry/repos/claude-remote/

structure

claude-remote/
├── src/
│   ├── cli.ts           # PTY wrapper, connects to server
│   ├── server.ts        # HTTP + WebSocket + SSE
│   ├── db.ts            # SQLite schema + queries
│   ├── auth.ts          # HMAC signing
│   └── types.ts         # shared types
├── public/
│   └── index.html       # mobile dashboard
├── docs/
│   └── plan.md          # this plan (moved here)
├── CLAUDE.md
├── package.json
├── Dockerfile
├── compose.yml
└── justfile

sqlite schema

CREATE TABLE devices (
  id INTEGER PRIMARY KEY,
  secret TEXT NOT NULL UNIQUE,
  name TEXT,
  created_at INTEGER NOT NULL,
  last_seen INTEGER NOT NULL
);

CREATE TABLE sessions (
  id INTEGER PRIMARY KEY,
  device_id INTEGER NOT NULL,
  started_at INTEGER NOT NULL,
  ended_at INTEGER,
  cwd TEXT,
  command TEXT
);

CREATE TABLE prompts (
  id INTEGER PRIMARY KEY,
  session_id INTEGER NOT NULL,
  created_at INTEGER NOT NULL,
  prompt_text TEXT NOT NULL,
  response TEXT,
  responded_at INTEGER
);

CREATE TABLE output_log (
  id INTEGER PRIMARY KEY,
  session_id INTEGER NOT NULL,
  timestamp INTEGER NOT NULL,
  line TEXT NOT NULL
);

api

REST:

  • GET / - dashboard
  • GET /api/sessions - list sessions
  • POST /api/prompts/:id/approve - approve
  • POST /api/prompts/:id/reject - reject

WebSocket /ws (CLI <-> Server):

  • cli sends: auth, output, resize, exit
  • server sends: input, resize, ping

SSE /events (Server -> Dashboard):

  • session_start, session_end, output, prompt, prompt_response

implementation phases

phase 1: foundation (~300 lines)

  1. create project structure
  2. src/types.ts - message types
  3. src/db.ts - sqlite schema + queries
  4. src/auth.ts - device secret + HMAC

phase 2: server (~250 lines)

  1. src/server.ts - Bun.serve with routes
  2. WebSocket handler for CLI
  3. SSE endpoint for viewers
  4. REST endpoints for approval

phase 3: cli wrapper (~150 lines)

  1. src/cli.ts - spawn claude with bun-pty
  2. WebSocket client to server
  3. forward PTY output, handle resize

phase 4: dashboard (~300 lines)

  1. public/index.html - mobile-first
  2. SSE listener
  3. terminal output display
  4. approve/reject buttons

phase 5: deployment (~100 lines)

  1. Dockerfile (oven/bun)
  2. compose.yml with volume
  3. justfile commands
  4. error handling + reconnection

key decisions

  • bun-pty over node-pty: pure FFI, pre-built binaries
  • SQLite over in-memory: persistence across restarts
  • SSE over WebSocket for dashboard: simpler, auto-reconnect
  • HMAC over TLS certs: simpler deployment
  • plain text output over xterm.js: mobile-friendly, simpler

verification

  1. bun run src/cli.ts - starts PTY wrapper
  2. visit http://localhost:3000 - see dashboard
  3. type in CLI - output appears in dashboard
  4. trigger approval prompt - approve from dashboard
  5. docker compose up - deploys successfully

dependencies

{
  "dependencies": {
    "bun-pty": "^0.4.8"
  }
}

that's it. one external dep.

CLAUDE.md notes

  • if bun-pty needs changes, fork and contribute upstream
  • use bun:sqlite, not better-sqlite3
  • no frameworks (no express, no hono)
  • target ~1000-1500 lines total