A web-based remote terminal for Claude Code and Codex CLI. Access either agent from any device — phone, tablet, or desktop — over your local network or Tailscale.
Not a chat wrapper. This is a real terminal emulator (xterm.js + node-pty + tmux) that runs the upstream CLI interactively, exactly as it would behave in your local terminal.
- Two backends, one UI — Spawn a Claude Code session or a Codex session from the same browser; each session is tagged with its backend (blue for Claude, emerald for Codex)
- History browser — Cross-backend history view: browse every Claude Code and Codex session that exists on your disk, side by side, and resume any of them in one click
- Real terminal — xterm.js renders the full terminal experience: colors, cursor, scrollback, links
- Multi-session — Click "+" to spawn unlimited sessions, switch freely between them
- tmux-backed persistence — Sessions survive server restarts; the PTY lives in tmux, the WebSocket just attaches to it
- Session ring buffer — 5 MB of output per session is replayed on reconnect, so switching devices doesn't lose context
- File upload — Drag & drop files or click the paperclip button to send files to the running agent
- Cross-device — Works on iPhone, iPad, Android, and desktop browsers via Tailscale
- iPad / iOS-friendly — Touch key bar (Esc, Tab, Ctrl+C, arrows) and IME fixes for iOS 26
- Dark/Light theme — Follows system preference, toggleable in sidebar
- Token auth — Simple token-based authentication to protect your terminal
- Single port — HTTP + WebSocket on one port (default 3109), simple firewall setup
Browser (any device) Server (your Mac)
┌──────────────────┐ ┌──────────────────────┐
│ xterm.js │◄──WS──►│ server.ts │
│ (Terminal UI) │ │ ├─ Next.js (pages) │
│ + backend tabs │ │ ├─ WebSocket server │
│ │ │ └─ TerminalManager │
│ IndexedDB │ │ ├─ tmux:ccrt-#1 │──► claude (PTY)
│ (session list) │ │ ├─ tmux:ccrt-#2 │──► codex (PTY)
└──────────────────┘ │ └─ ... │
│ │
│ History scanner: │
│ ~/.claude/projects/* │
│ ~/.codex/sessions/* │
└──────────────────────┘
- server.ts — Custom HTTP server serving Next.js pages + WebSocket upgrade on
/ws/terminal - TerminalManager — Manages tmux-backed PTY lifecycles, ring buffers, attach/detach per session
- backends.ts — Picks the right CLI (
claudeorcodex) and builds the right argv, including--resumesemantics for each - history-index.ts — Scans both
~/.claude/projects/*/(Claude Code) and~/.codex/sessions/*/(Codex) and renders a unified history browser - WebSocket protocol — JSON messages:
create(withbackend: 'claude' | 'codex'),attach,input,resize,kill,list
-
Node.js 20+ (or Bun)
-
tmux (
brew install tmuxon macOS) -
At least one of:
- Claude Code CLI — looked up at
~/.local/bin/claude,/opt/homebrew/bin/claude,/usr/local/bin/claude - Codex CLI — looked up at
/opt/homebrew/bin/codex,/usr/local/bin/codex,~/.local/bin/codex
You can install only one if you only need that backend; the UI will just fail to spawn the missing one.
- Claude Code CLI — looked up at
-
macOS (node-pty prebuilds are darwin-arm64; Linux should also work with rebuild)
git clone https://github.com/AliceLJY/cc-remote-term.git
cd cc-remote-term
npm install
# Generate a random token
export CC_TERMINAL_TOKEN=$(openssl rand -hex 24)
echo "CC_TERMINAL_TOKEN=$CC_TERMINAL_TOKEN" > .env.local
echo "Your token: $CC_TERMINAL_TOKEN"
# Build & start
npm run build
npm startOpen http://localhost:3109?token=YOUR_TOKEN in your browser. The sidebar's "+" button opens a backend picker (Claude / Codex).
If you have Tailscale installed, the server auto-detects your Tailscale IP on startup:
[cc-terminal] Tailscale: http://100.x.x.x:3109
Access from any device on your Tailnet: http://100.x.x.x:3109?token=YOUR_TOKEN
Drop a plist like this into ~/Library/LaunchAgents/com.cc-remote-term.web.plist, then launchctl bootstrap gui/$UID ~/Library/LaunchAgents/com.cc-remote-term.web.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.cc-remote-term.web</string>
<key>WorkingDirectory</key><string>/Users/YOU/Projects/cc-remote-term</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/npm</string>
<string>start</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>CC_TERMINAL_TOKEN</key><string>YOUR_TOKEN_HERE</string>
<key>NODE_ENV</key><string>production</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key><string>/tmp/cc-remote-term.out.log</string>
<key>StandardErrorPath</key><string>/tmp/cc-remote-term.err.log</string>
</dict>
</plist>| Environment Variable | Default | Description |
|---|---|---|
CC_TERMINAL_TOKEN |
(required) | Auth token for WebSocket connections |
PORT |
3109 |
Server port |
NODE_ENV |
development |
Set to production for optimized builds |
Session limits (in lib/types.ts):
| Constant | Default | Description |
|---|---|---|
MAX_SESSIONS |
10 | Maximum concurrent PTY sessions |
IDLE_TIMEOUT |
30 min | Auto-kill detached idle sessions |
RING_BUFFER_SIZE |
5 MB | Output history per session (replayed on attach / reconnect) |
- Frontend: Next.js 16 (App Router), React 19, Tailwind CSS 4, xterm.js 6
- Backend: Custom Node.js HTTP server, WebSocket (ws), node-pty, tmux (for session persistence)
- Storage: IndexedDB (client-side session list); session metadata persisted in
data/sessions.json
Inspired by Happy.
MIT