|
|
# 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.
|
|
|
|
|
|

|
|
|
|
|
|
## 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, no tunnel — front it with a reverse proxy, no auth)
|
|
|
cmux-remote
|
|
|
|
|
|
# Set a password
|
|
|
cmux-remote -P mypassword
|
|
|
|
|
|
# Opt back into the ngrok tunnel
|
|
|
cmux-remote --tunnel
|
|
|
|
|
|
# Override the external URL shown in the banner / QR code
|
|
|
cmux-remote --public-url https://cmux-remote.example.com
|
|
|
|
|
|
# Custom port with verbose logging
|
|
|
cmux-remote -p 3000 -v
|
|
|
|
|
|
# All options
|
|
|
cmux-remote --help
|
|
|
```
|
|
|
|
|
|
### Options
|
|
|
|
|
|
| Option | Default | Description |
|
|
|
|--------|---------|-------------|
|
|
|
| `-p, --port <port>` | `9870` | Local server port |
|
|
|
| `-P, --password <pass>` | none | Access password (or `CMUX_REMOTE_PASSWORD` env) |
|
|
|
| `--tunnel` | off | Enable ngrok tunnel (off by default — use a reverse proxy instead) |
|
|
|
| `--tunnel-domain <domain>` | none | Fixed ngrok domain (or `NGROK_DOMAIN` env) |
|
|
|
| `--public-url <url>` | `https://cmux-remote.yirugi.synology.me` | External URL for banner/QR (or `CMUX_PUBLIC_URL` env) |
|
|
|
| `--poll-rate <ms>` | `200` | Screen poll interval |
|
|
|
| `--theme <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
|
|
|
CMUX_PUBLIC_URL=https://cmux-remote.yirugi.synology.me
|
|
|
# ngrok is off by default; only needed if you start with --tunnel
|
|
|
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
|