# 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 <port>` | `9870` | Local server port |
| `-P, --password <pass>` | none | Access password (or `CMUX_REMOTE_PASSWORD` env) |
| `--no-tunnel` | tunnel on | Disable ngrok tunnel |
| `--tunnel-domain <domain>` | none | Fixed ngrok domain (or `NGROK_DOMAIN` 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
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
