# cmux-remote Browser-based remote terminal UI for [cmux](https://github.com/manaflow-ai/cmux). Access and control your cmux terminal sessions from any device with a web browser. ![Version](https://img.shields.io/badge/version-0.1.0-blue) ## Features - **Real-time terminal streaming** — Adaptive polling with differential updates (patches, not full redraws) - **Claude Code mode** — Auto-detects Claude Code sessions and renders with structured blocks (tool use, code, diffs, tables, selections) - **Remote access via ngrok** — Secure tunnel with QR code for quick mobile access - **Mobile-first UI** — Virtual keyboard, touch gestures, iOS PWA support - **Multi-workspace** — Sidebar for switching between workspaces, panes, and surfaces - **Browser surface support** — Screenshots for non-terminal surfaces with pinch-zoom - **Scrollback history** — Auto-loads scrollback when scrolling up - **Authentication** — Password-based auth with bcrypt + JWT, rate-limited login - **Themes** — Dark/light mode with persistent preference ## Installation ### Global CLI ```bash npm install -g cmux-remote ``` ### From source ```bash git clone https://github.com/user/cmux-remote.git cd cmux-remote npm install npm run build ``` ## Prerequisites - **cmux** must be installed and running (`cmux` binary in PATH, or set `CMUX_PATH`) - **Node.js** >= 18 - **ngrok** account (optional, for remote tunnel) — set `NGROK_AUTHTOKEN` env var ## Usage ```bash # Start with defaults (port 9870, ngrok tunnel enabled, no auth) cmux-remote # Set a password cmux-remote -P mypassword # Local only (no tunnel) cmux-remote --no-tunnel # Custom port with verbose logging cmux-remote -p 3000 -v # All options cmux-remote --help ``` ### Options | Option | Default | Description | |--------|---------|-------------| | `-p, --port ` | `9870` | Local server port | | `-P, --password ` | none | Access password (or `CMUX_REMOTE_PASSWORD` env) | | `--no-tunnel` | tunnel on | Disable ngrok tunnel | | `--tunnel-domain ` | none | Fixed ngrok domain (or `NGROK_DOMAIN` env) | | `--poll-rate ` | `200` | Screen poll interval | | `--theme ` | `dark` | Default theme (`dark` or `light`) | | `-v, --verbose` | off | Verbose server logging | ### Configuration file Create `~/.cmux-remote/config` for persistent settings: ```ini CMUX_REMOTE_PASSWORD=mypassword NGROK_AUTHTOKEN=your_token NGROK_DOMAIN=my-domain.ngrok-free.app CMUX_PATH=/usr/local/bin/cmux ``` Environment variables set in the config file won't override existing env vars. ## Architecture ``` Browser ←── WebSocket ──→ Node.js Server ←── CLI/Socket ──→ cmux daemon │ │ ├─ terminal-view.js ├─ screen-poller.ts (adaptive polling) ├─ claude-parser.js ├─ input-handler.ts (key translation) ├─ claude-renderer.js ├─ cmux-client.ts (cmux CLI wrapper) └─ app.js (orchestrator) └─ session-manager.ts ``` ### Server (`src/`) | Module | Description | |--------|-------------| | `index.ts` | CLI entry point (Commander.js), bootstraps all components | | `server/app.ts` | Express 5 HTTP server, REST API, static files, CSP headers | | `server/websocket.ts` | WebSocket server, client sessions, message routing | | `server/auth.ts` | bcrypt password hashing, JWT tokens, rate limiting | | `server/tunnel.ts` | ngrok tunnel management | | `bridge/cmux-client.ts` | cmux CLI wrapper with concurrency limiting and caching | | `bridge/screen-poller.ts` | Adaptive screen polling with diff-based updates | | `bridge/input-handler.ts` | Translates browser keys to cmux protocol | | `bridge/session-manager.ts` | Client auth state and surface subscriptions | | `protocol/messages.ts` | Type-safe WebSocket message definitions | | `utils/config.ts` | Configuration interface and defaults | | `utils/text-differ.ts` | Line-based diff algorithm for incremental updates | ### Client (`public/`) | File | Description | |------|-------------| | `js/app.js` | Main orchestrator — routing, WS lifecycle, surface switching | | `js/terminal-view.js` | ANSI-to-HTML renderer with 256-color and 24-bit RGB support | | `js/claude-parser.js` | Parses Claude Code output into structured blocks | | `js/claude-renderer.js` | Renders Claude blocks (prompts, tools, code, diffs, tables) | | `js/claude-keyboard.js` | Claude-specific keyboard shortcuts (Yes/No/Esc) | | `js/websocket-client.js` | WebSocket client with auto-reconnection | | `js/virtual-keyboard.js` | Mobile virtual keyboard with modifiers (Ctrl, Alt, Shift) | | `js/sidebar.js` | Workspace/surface tree navigation | | `js/gestures.js` | Touch swipe and gesture handling | | `js/theme.js` | Dark/light theme persistence | | `js/auth.js` | Login flow and JWT management | ### Adaptive Polling The screen poller adjusts its rate based on activity: - **Fast** (200ms) — Active terminal with recent changes - **Idle** (1-2s) — No changes for 3+ poll cycles - **Deep idle** (2s) — 10+ seconds inactive Resets to fast mode on any user input. Sends full screen snapshots every 30 seconds; incremental diff patches otherwise. ### Claude Code Mode When a surface runs Claude Code, the client automatically switches to a structured renderer: - **Prompts** with `❯` marker - **Tool use** blocks with icon, name, and arguments - **Tool results** in bordered boxes - **Code blocks** with syntax-aware display - **Diff blocks** with +/- coloring - **Tables** parsed from box-drawing characters - **Selection menus** with clickable options - **Status bar** with mode pill (Plan/Code/Bypass) and git branch ## Protocol Communication uses JSON over WebSocket. **Client → Server:** ``` auth { token } subscribe { workspace, surface } unsubscribe { surface } send-text { workspace, surface, text } send-key { workspace, surface, key } list-workspaces scroll-request { workspace, surface, lines } ``` **Server → Client:** ``` screen { surface, content, lines, scrollback? } screen-diff { surface, patches[] } workspaces { workspaces[] } browser-screenshot { surface, imageData, mime } auth-ok / auth-fail error { message } ``` ## Development ```bash # Development with auto-reload (no tunnel) npm run dev:local # Development with tunnel npm run dev # Run tests npm test # Build for production npm run build # Start production server npm start ``` ## License MIT