Drive Claude Code from your phone — in bed. A mobile-first remote control for AI coding agents — works with Claude, MiniMax, GLM, DeepSeek, or any model on OpenRouter.
Use the official claude at your desk. When you want to lie down, start bedcoder next to it —
a tiny headless daemon that end-to-end encrypts the Agent SDK message stream and relays it to
your phone through a zero-knowledge relay. It reuses your ~/.claude session, so desktop ↔ phone
hand off with context intact.
Bedcoder is not a replacement for
claude— it runs beside it. The desktop terminal only shows a pairing code + QR; all interaction happens on the phone.
This repository is the open-source core: the agent (the bedcoder CLI), the relay, the
shared protocol, and e2e tests. The mobile/web client (Flutter) is maintained separately.
| Platform | Link |
|---|---|
| 🌐 Web | web.bedcoder.org |
| 🍎 iOS | App Store |
| 🤖 Android | Download APK |
phone (web/app) ⇄ wss ⇄ relay ⇄ wss ⇄ bedcoder (agent) next to:
(Go, ZK) ├─ Agent SDK query() claude (official TUI)
├─ HTTP port preview ↑ shares ~/.claude/
├─ filesystem RPC
└─ terminal mirror
- The relay only ever parses the outer envelope (routing); the payload is end-to-end encrypted and opaque to it.
- Pairing: the agent shows an 8-char code + QR; the phone enters the code; both confirm a 6-digit SAS (man-in-the-middle check). No accounts, no signup.
| Dir | Lang | Role |
|---|---|---|
agent/ |
TypeScript / Node | The bedcoder CLI. Wraps @anthropic-ai/claude-agent-sdk; forwards the session and provides port-preview, filesystem RPC, and terminal mirror. |
relay/ |
Go | Zero-knowledge WebSocket router + WebRTC signaling. Single static binary, embedded SQLite (pure-Go, no CGO). |
protocol/ |
TypeScript (Zod) | Shared wire types — the Envelope + per-channel payloads. Bundled into the agent. |
e2e/ |
TypeScript (vitest) | Integration tests across agent + relay + protocol. |
Install the CLI and run it in your project (uses the public relay at wss://relay.bedcoder.org by default):
npm i -g bedcoder
bedcoder # new session — prints a pairing code + QR
# or take over an existing ~/.claude session:
bedcoder --resumeThen open the web client (or the iOS
/ Android app), enter the
code, confirm the SAS — and code from your phone. Back at the desk, claude --resume picks the same
session right up.
On first launch bedcoder prompts for a backend; the choice is cached so subsequent launches reuse it. Skip the menu with a flag:
bedcoder --claude # Anthropic (uses `claude login` or ANTHROPIC_API_KEY)
bedcoder --minimax # MiniMax-M2.7
bedcoder --glm # GLM (Z.AI)
bedcoder --deepseek # DeepSeek
bedcoder --openrouter # any model on OpenRouter (Claude / GPT / Llama / …)Non-Anthropic providers ask for an API key once and cache it in ~/.bedcoder/config.json (mode 0600).
For headless / CI use, set the env var instead: MINIMAX_API_KEY, GLM_API_KEY, DEEPSEEK_API_KEY,
OPENROUTER_API_KEY.
| Provider | Where to get a key |
|---|---|
| Claude (Anthropic) | claude login, or https://console.anthropic.com/settings/keys |
| MiniMax | https://platform.minimaxi.com/user-center/basic-information/interface-key |
| GLM (Z.AI) | https://z.ai/manage-apikey/apikey-list |
| DeepSeek | https://platform.deepseek.com/api_keys |
| OpenRouter | https://openrouter.ai/keys |
Pin a specific model with --model <id> (use the provider's native model ID — e.g.
claude-opus-4-7, glm-5.1, deepseek-v4-pro, openai/gpt-4o, meta-llama/llama-3.1-405b-instruct).
To route opus / sonnet / haiku tiers to different non-Anthropic models, export
ANTHROPIC_DEFAULT_OPUS_MODEL / …_SONNET_MODEL / …_HAIKU_MODEL directly.
--claude / --minimax / --glm / --deepseek / --openrouter
skip the provider menu
--resume [id] continue an existing ~/.claude session (latest, or a specific id)
--relay <url> relay URL (default wss://relay.bedcoder.org)
--model <id> pin a model (provider's native model ID)
--models-url <url> model catalog JSON (or env BEDCODER_MODELS_URL)
--worktree <name> run inside .worktrees/<name> — parallel tasks on one repo
--tunnel [auto|p1,p2] port-discovery hint for the preview tab (preview is always on)
--rewind-code enable SDK file checkpointing so /rewind can restore code (opt-in)
--log [path] write a diagnostics log (default ~/.bedcoder/agent.log)
--skip-version-check skip the startup SDK ↔ claude CLI compatibility check
--fake run a fake engine (no provider auth) — for development
cd relay
CGO_ENABLED=0 go build -trimpath -o bedcoder-relay ./cmd/relay
BEDCODER_ADDR=:8787 ./bedcoder-relay…or use the included Dockerfile. Point the agent at it with bedcoder --relay wss://your-host.
Relay environment variables:
| Var | Default | Purpose |
|---|---|---|
BEDCODER_ADDR |
:8787 |
listen address |
BEDCODER_DB |
bedcoder.db |
SQLite path (sessions table only) |
BEDCODER_ALLOWED_ORIGINS |
(any) | comma-separated CORS allowlist |
BEDCODER_VAPID_PUBLIC / BEDCODER_VAPID_PRIVATE / BEDCODER_VAPID_SUBJECT |
— | optional Web Push (VAPID); push is a no-op without them |
Requirements: Node ≥ 20 + pnpm 9, Go ≥ 1.24.
make install # pnpm install (protocol + agent + e2e workspace)
make test-all # protocol + agent + relay + e2e
make lint # eslint + tsc + gofmt + go vet
make build-agent # bundle protocol → agent/dist/index.js
make build-relay # static relay binary- End-to-end encryption. A per-session symmetric key (XSalsa20-Poly1305 / TweetNaCl) encrypts every payload. The key reaches the phone only via the QR (native) or an X25519 ECDH pairing (web) — never the relay.
- Zero-knowledge relay. It stores only a
sessionsrow (id, timestamps, push token) — no message content, no history (delivered then dropped), no keys. There is no "user" concept: one code = one session = one encrypted channel. - MITM-resistant pairing: commitment + 6-digit SAS comparison.
- Filesystem sandbox. The files channel stays within the project directory — rejecting
..escapes, sensitive paths (/etc/passwd,~/.ssh), and symlink escapes.
The agent calls the SDK query() with a sessionId, so sessions land in ~/.claude/projects/...,
shared with the official claude CLI. bedcoder --resume picks up a session created by claude, and
vice versa — no parallel session store.
The Flutter mobile/web client is not open-source yet, and we will make them public after the product receives enough downloads. In the meantime, use the hosted clients: the web client, the iOS app, or the Android APK.