A claude.ai/code–style web client for Claude Code, built on Next.js 16 (Turbopack) + React 19 + shadcn/ui. It gives you true, live remote control of your Claude Code sessions from a browser — including your phone on the same Wi‑Fi — by talking to the same first‑party Remote Control API the official web app uses, and by reading the same local session files your terminal writes so the messages match your terminal exactly.
Reuses your existing Claude Code login — no API key required. The OAuth token stays on the server; the browser never sees it.
- Remote Control · live — drives the actual live session (not a resumed copy) over Anthropic's first‑party Code Sessions API, exactly like claude.ai/code. No cold start, fully live, sharable.
- Message parity — history is read from
~/.claude/projects/**/*.jsonl, the same files the terminal appends to, so what you see here is byte‑for‑byte what the terminal shows (an earlier version read cloud events and diverged when the bridge disconnected — this does not). - True realtime, no polling — the server tails the session file by byte
offset and pushes appended lines over SSE using
fs.watch; new tokens, tool calls and results stream into the UI as they land. - Claude‑web‑parity UI — markdown + GitHub‑dark syntax highlighting in chat
and tool blocks, collapsible combined command/result blocks,
Edited file +N -Mdiff stats, inline thinking blocks, inline images, live "responding…" status, presence and share. - Resume classic sessions — every session under
~/.claude/projectsis listed (including terminal‑started ones); remote‑controlled sessions are flagged with a 📡 marker parsed from thebridge-sessionrecord. - Open a
claude.ai/codelink — paste ahttps://claude.ai/code/session_…link and it resolves to the local session hosting it.
Requirements: Node 20+, macOS or Linux, and Claude Code installed & logged
in (claude once, so credentials exist).
git clone https://github.com/serebano/claude-code-web-client.git
cd claude-code-web-client
npm install
# dev server
npm run dev # → http://localhost:3000
# production
npm run build && npm startTest from your phone (same Wi‑Fi):
npm run dev -- -H 0.0.0.0 # bind to all interfaces
# then open http://<your-LAN-ip>:3000 on the phoneAdd your LAN IP(s) to allowedDevOrigins in next.config.ts — Next 16 blocks
cross‑origin dev asset/HMR requests by default (they 403 otherwise).
| Var | Default | Meaning |
|---|---|---|
WORKDIR |
parent of this app | Directory the interactive (SDK) agent works in |
MODEL |
claude-opus-4-8 |
Model id for SDK‑driven turns |
No API key. The server reads your existing Claude Code OAuth token at runtime:
- macOS Keychain —
security find-generic-password -s "Claude Code-credentials" -w(Claude Code migrated credentials into the Keychain). - Fallback —
~/.claude/.credentials.json(older setups / Linux).
The claudeAiOauth.accessToken is used server‑side only; the organization UUID
is resolved live from /api/oauth/claude_cli/roles. Nothing is hardcoded and no
secret ever reaches the browser. See lib/rc.ts.
All on https://api.anthropic.com, authed with the Claude Code OAuth token
(scope user:sessions:claude_code):
| Purpose | Request |
|---|---|
| List sessions | GET /v1/code/sessions |
| History (paged) | GET /v1/code/sessions/{id}/events (sort_order, cursor) |
| Live stream | GET /v1/code/sessions/{id}/events/stream (SSE: replays then tails/long‑polls) |
| Send / drive | POST /v1/code/sessions/{id}/events → {events:[{payload:{type:"user",message:{role:"user",content},client_platform,uuid}}]} |
| Presence heartbeat | POST /v1/code/sessions/{id}/client/presence → {client_id, clear} |
Headers: Authorization: Bearer <token>, anthropic-version: 2023-06-01,
anthropic-beta: oauth-2025-04-20, x-organization-uuid: <org>.
For message parity and instant updates the client prefers the on‑disk session file over cloud events:
- Session mapping — a
claude.ai/code/session_Xlink ↔ bridge idcse_X(inbridge-sessionrecords) ↔ local session UUID (the.jsonlfilename). - Byte‑offset tailing —
lib/rc-local.tsreads only appended bytes since the last offset;tailLocalFromByte()is an async generator that catches up, then watches the file (fs.watch+ a safety interval) and yields events for new lines. Large files (100MB+) are handled with bounded head+tail reads. - The SSE route (
app/api/rc/stream) pushes those events to the browser'sEventSource; the client folds them into the view with a pure reducer.
Browser (React / shadcn) Next.js route handlers Data source
──────────────────────── ───────────────────────── ───────────────
RcPanel ── GET /api/rc/sessions ───▶ lib/rc.ts (list) api.anthropic.com
── GET /api/rc/stream ─────▶ tailLocalFromByte() (SSE) ─────▶ ~/.claude/projects
── POST /api/rc/send ──────▶ lib/rc.ts (drive live session) ▶ api.anthropic.com
── POST /api/rc/presence ──▶ presence heartbeat
AppSidebar ─ GET /api/sessions ─────▶ lib/sessions.ts (parse jsonl)
ChatPanel ── POST /api/chat (SSE) ──▶ Agent SDK query() (resume/fork)
Key files:
| Path | Role |
|---|---|
lib/rc.ts |
Remote Control API client (token, org, list, send, stream, presence) |
lib/rc-local.ts |
Local .jsonl parsing + byte‑offset realtime tailing |
lib/sessions.ts |
Parse ~/.claude/projects/*.jsonl (title, cwd, branch, RC marker) |
app/api/rc/* |
Route handlers proxying RC + streaming SSE to the browser |
components/rc-panel.tsx |
The live Remote Control conversation view |
components/markdown.tsx, code-block.tsx |
Markdown + highlight.js rendering |
components/app-sidebar.tsx, chat-panel.tsx |
Sidebar + SDK‑driven chat |
By default the Agent SDK runs isolated and loads none of your configured MCP
servers. lib/runtime.ts opts back in with:
settingSources: ["user", "project", "local"]This makes SDK‑driven turns load your real Claude Code config — settings, hooks,
CLAUDE.md, and MCP servers — reusing the CLI's OAuth tokens. Project‑scoped MCP
servers are keyed to a directory in ~/.claude.json, so point WORKDIR at the
directory where the server you want is configured.
- Localhost, unauthenticated. Add auth before exposing it beyond your LAN.
- Undocumented, internal API. This uses the official client's OAuth token against an internal API. It works today (list, live stream, send, presence all verified) but can change without notice and is a Terms‑of‑Service gray area. Use on your own account at your own discretion.
- Never write to session files. The
.jsonlfiles under~/.claude/projectsare the source of truth for both the terminal and this app — the app only ever reads/tails them.
MIT — see LICENSE. Personal/experimental project; not affiliated with Anthropic.