Share your real terminal with anyone via a 6-digit code. No port forwarding. No VPNs. Fully peer-to-peer after the WebRTC handshake.
Both sides can view and control each other's terminals using /switch.
Host machine Cloudflare Edge Peer machine
──────────── ─────────────── ────────────
term-bridge-agent
│
├─ node-pty ($SHELL) ──────────────────────────────────── node-pty ($SHELL)
│ real shell (P2P WebRTC DataChannel) real shell
│
└─ WebSocket ──► Hono Worker ──► SessionRoom DO ◄── WebSocket ◄─ agent
(router) (signaling relay)
- Host runs
term-bridge→ agent callsPOST /session→ Worker creates a Durable Object, stores pairing code in KV - Peer runs
term-bridge connect 483-291→ resolves code → connects to the same DO - DO relays SDP offer/answer + ICE candidates between the two
- WebRTC DataChannel opens → DO goes idle. Terminal data flows P2P, DTLS-encrypted end-to-end
Both host and peer spawn their own PTY. Data flows both ways:
| Direction | Channel | Description |
|---|---|---|
| Host → Peer | DataChannel (raw) | Host PTY output |
| Peer → Host | DataChannel (raw) | Keystrokes to host PTY |
| Peer → Host | Ctrl rev_data |
Peer PTY output |
| Host → Peer | Ctrl rev_input |
Keystrokes to peer PTY |
Use /switch to toggle which terminal you're viewing and controlling.
term-bridge/
├── term-bridge-worker/ Cloudflare Worker (Hono + Durable Objects)
│ ├── src/index.ts Routes + SessionRoom DO
│ ├── static/install.sh curl|bash installer
│ └── wrangler.jsonc DO bindings, KV, migrations
│
├── term-bridge-agent/ CLI agent (Node.js + node-pty + WebRTC)
│ ├── src/
│ │ ├── index.ts Entry point (host / connect modes)
│ │ ├── session.ts Create session via signaling server
│ │ ├── pty.ts Host PTY + WebRTC DataChannel bridge
│ │ ├── client.ts Peer connect mode + reverse PTY
│ │ ├── terminal-io.ts Host stdin/stdout routing
│ │ ├── commands.ts Slash-command system + ctrl protocol
│ │ ├── rtc-cleanup.ts Safe WebRTC resource teardown
│ │ ├── config.ts Signaling server URL config
│ │ └── ui.ts CLI banner / code display
│ └── scripts/
│ ├── build.ts esbuild bundle
│ └── fix-pty.js macOS node-pty binary fixup
│
└── .gitignore
cd term-bridge-worker
npm install
# Create KV namespace for pairing codes
npx wrangler kv namespace create CODES
# Copy the `id` into wrangler.jsonc → kv_namespaces[0].id
npx wrangler kv namespace create CODES --preview
# Copy the `preview_id` into wrangler.jsonc → kv_namespaces[0].preview_id
# Local dev
npm run dev
# Deploy to Cloudflare
npm run deployYour Worker URL: https://term-bridge-worker.<subdomain>.workers.dev
cd term-bridge-agent
npm install
echo 'TERM_BRIDGE_SERVER=https://term-bridge-worker.<subdomain>.workers.dev' > .env
npm run devOutput:
● Term Bridge agent running
Machine: MacBook-Pro
Code: 483-291
Waiting for connections...
Peer runs: term-bridge connect 483-291
On another machine:
npm run dev -- connect 483-291Or after npm install -g term-bridge-agent:
term-bridge connect 483-291Once connected, either side can switch to the other's terminal:
/switch → Switch to the other machine's terminal
/switch → Switch back to your own
/status → See current view mode (local/remote)
| Command | Description |
|---|---|
/switch |
Toggle between local and remote terminal |
/status |
Show connection info and current view |
/help |
List available commands |
/exit, /quit, /q |
Disconnect session |
/kick |
Kick the peer (host only) |
/transfer <file> |
Send a file to the peer |
The Cloudflare Worker exposes:
| Method | Path | Description |
|---|---|---|
POST |
/session |
Create session → returns { code, sessionId } |
GET |
/join/:code |
Resolve pairing code → { sessionId } |
GET |
/session/:id/ws?role=host|client |
WebSocket upgrade (signaling) |
GET |
/install |
curl | bash installer script |
GET |
/app |
Status page |
| Variable | Where | Description |
|---|---|---|
TERM_BRIDGE_SERVER |
agent .env |
Base URL of your Cloudflare Worker |
SHELL |
system | Auto-detected — which shell to spawn |
- Terminal data is DTLS-encrypted end-to-end — Cloudflare never sees it
- The Durable Object only relays signaling (SDP + ICE), not terminal content
- Pairing codes expire after 10 minutes (KV TTL)
- Add Bearer token auth to
POST /sessionto prevent unauthorized sessions
- Worker: Cloudflare Workers, Hono, Durable Objects, KV
- Agent: Node.js, node-pty, node-datachannel (libdatachannel), WebSocket
- Protocol: WebRTC DataChannel with SDP/ICE signaling over WebSocket