# ctrl by Bulletproof — Full Documentation > Pixel-art office visualization for AI coding agents. This is the complete reference documentation for ctrl. For the overview, see: https://bulletproof.sh/llms.txt --- # Getting Started ctrl is a pixel-art office visualization for AI coding agents. It runs a local daemon that monitors your AI sessions and renders each agent as a character working in a virtual office. ## Install Run directly with npx — no global install needed: ```bash npx @bulletproof-sh/ctrl-daemon@latest ``` ## Quick Start 1. **Start the daemon** — open a terminal and run: ```bash ctrl ``` The daemon starts on `localhost:3001`, opens your browser, and begins scanning for AI coding sessions. 2. **Start an AI coding agent** — in another terminal, run your preferred tool: ```bash claude # Claude Code codex # Codex CLI augment # Augment Code ``` 3. **Watch your office** — the browser shows your agent as a pixel-art character sitting at a desk, working in real time. ## What You'll See - **Agent roster** — list of active agents with status indicators (idle, working, waiting for input) - **Live activity** — see which tools agents are using, what files they're editing, token costs - **Session inspector** — click an agent to see their full transcript, tool history, and output - **Office layout** — customize desk arrangements and office themes ## Desktop App For a standalone experience without a browser tab, download the [desktop app](/download). It bundles the daemon and opens automatically. ## VS Code Extension Install the [ctrl extension](https://marketplace.visualstudio.com/items?itemName=bulletproof-sh.ctrl) to embed the office view directly in your editor sidebar. ## AI-Assisted Help ctrl has extensive customization options. The fastest way to get help is to give your AI assistant the full docs and ask questions. ### Step 1: Download the docs Download the complete docs as a single text file: Right-click and "Save link as..." to download: [llms-full.txt](/llms-full.txt) Or from the terminal: ```bash curl -o ctrl-docs.txt https://bulletproof.sh/llms-full.txt ``` ### Step 2: Attach to your AI assistant **ChatGPT** — Click the paperclip icon and attach the downloaded file, then ask your question. **Claude** — Click the paperclip icon and attach the file, or drag and drop it into the chat. **Claude Code** — Claude Code can fetch URLs directly. Just ask: ``` Fetch https://bulletproof.sh/llms-full.txt then help me configure ctrl ``` **Cursor / Windsurf** — Drag the file into the chat, or add it to your project and reference it with `@ctrl-docs.txt`. ### Step 3: Ask anything Once your AI has the docs, try questions like: - "How do I change the color theme?" - "What keyboard shortcuts are available?" - "How do I connect to a remote daemon?" - "How do I add support for a custom AI tool?" - "How do I share my office with a teammate?" ## Next Steps - [CLI Reference](/docs/daemon/cli-reference) — all daemon flags and options - [Settings](/docs/web-app/settings) — configure notifications, themes, connections - [Themes & Typography](/docs/web-app/themes) — 31 color themes, 11 font presets - [Custom Platforms](/docs/platforms/custom-platforms) — add support for any AI tool - [VS Code Extension](/docs/extension/overview) — embed ctrl in your editor - [Relay Sharing](/docs/sharing/relay) — share your office with teammates --- # CLI Reference The `ctrl` command starts the daemon, which monitors AI coding sessions and serves the web UI. ## Usage ```bash ctrl [options] ``` ## Options | Flag | Default | Description | |------|---------|-------------| | `--port ` | `3001` | WebSocket server port. Auto-increments if in use. | | `--host
` | `127.0.0.1` | Bind address. Use `0.0.0.0` to expose on network. | | `--project-dir ` | _(auto-discover)_ | Watch a single project directory. Omit to auto-discover all AI tools. | | `--idle-timeout ` | `15` | Remove agents after this many minutes of inactivity. | | `--animation, -a ` | `random` | TUI background animation. See [TUI docs](/docs/daemon/tui). | | `--no-tui` | | Disable the terminal UI. Useful for headless/background mode. | | `--no-open` | | Don't auto-open the browser on startup. | | `--hooks` | | Enable Claude Code HTTP hooks integration (experimental). | | `--share` | | Enable relay sharing. See [Relay docs](/docs/sharing/relay). | | `--relay-url ` | _(default relay)_ | Custom relay server URL. | | `--no-encrypt` | | Disable end-to-end encryption for relay connections. | | `--relay-only` | | Relay mode only — no local WebSocket server. | | `--label ` | _(hostname)_ | Human-readable label shown to spectators. | | `--no-lock` | | Bypass single-instance lock file. | | `--force, -f` | | Kill existing daemon on the same port and take over. | | `--help, -h` | | Show usage information. | ## Environment Variables | Variable | Equivalent flag | Description | |----------|----------------|-------------| | `CTRL_HOST` | `--host` | Bind address | | `CTRL_SHARE=1` | `--share` | Enable relay sharing | | `CTRL_RELAY_URL` | `--relay-url` | Custom relay server URL | | `CTRL_NO_OPEN=1` | `--no-open` | Don't auto-open the browser | | `CTRL_VERBOSE=1` | _(none)_ | Enable verbose debug logging | | `CLAUDE_HOME` | _(none)_ | Override the Claude Code home directory (default: `~/.claude`). See [Multiple Claude Installations](#multiple-claude-installations). | ## Port Selection The daemon tries `--port` first (default 3001). If that port is in use, it auto-increments up to 10 times (3001 → 3002 → ... → 3010). If all ports are taken, it exits with an error. The web app scans ports 3001–3010 to discover running daemons automatically. ## Single Instance Lock By default, only one daemon runs per machine. A lock file in `~/.ctrl/` prevents duplicate instances. Use `--force` to kill the existing daemon and take its port, or `--no-lock` to bypass the check entirely. ## HTTP API The daemon exposes a health endpoint: ``` GET /health ``` Returns: ```json { "status": "ok", "agents": 3, "version": "0.1.0" } ``` The web app uses `GET /api/_/ctrl-daemon-info` for discovery, which returns the daemon's version, label, agent count, hostname, and protocol version. ## Multiple Claude Installations If you use multiple Claude Code installations (e.g., a custom alias like `claude-myalias` with `CLAUDE_HOME=~/.claude-myalias`), the daemon needs to know where to find sessions. By default it only scans `~/.claude/projects/`. Set the `CLAUDE_HOME` environment variable when starting the daemon to point it at the right directory: ```bash # Watch sessions from a custom Claude installation CLAUDE_HOME=~/.claude-myalias ctrl # Or export it in your shell profile export CLAUDE_HOME=~/.claude-myalias ``` **Limitation:** The daemon can only scan one Claude home directory at a time. If you need to watch sessions from multiple Claude installations simultaneously, run separate daemon instances on different ports: ```bash # Terminal 1 — default ~/.claude ctrl --port 3001 # Terminal 2 — custom installation CLAUDE_HOME=~/.claude-myalias ctrl --port 3002 ``` The web app scans ports 3001–3010 automatically, so both daemons will appear in the connection picker. ## Examples ```bash # Basic usage — auto-discover all AI sessions ctrl # Watch a specific project ctrl --project-dir ~/myproject # Headless mode on a custom port ctrl --port 4000 --no-tui --no-open # Share your office via relay ctrl --share --label "Ben's Workshop" # Force restart on default port ctrl --force ``` --- # Multi-Daemon Discovery The web app can discover and connect to multiple daemons running on the same machine. Each daemon represents a separate project or workspace. ## How It Works 1. On load, the web app scans ports 3001–3010 on `localhost` 2. For each port, it sends a `GET /api/_/ctrl-daemon-info` request 3. Daemons that respond are listed in the connection picker 4. If exactly one daemon is found, it auto-connects with a toast notification 5. If multiple daemons are found, a picker is shown ## Connection Picker When multiple daemons are running, the picker shows: - **Label** — the daemon's `--label` or hostname - **Agent count** — number of active AI agents - **Port** — which port the daemon is listening on - **Project directory** — if the daemon was started with `--project-dir` Click a daemon to connect. The selected daemon persists across page reloads. ## Custom URLs You can connect to a daemon at any URL using the connection picker's custom URL input. This is useful for: - Daemons running on non-standard ports - Remote daemons exposed via SSH tunnel or network - Relay connections (see [Relay Sharing](/docs/sharing/relay)) ## Port Scanning Details - Ports are scanned in batches of 10 for performance - Each port has a 2-second timeout - Scanning completes in ~2 seconds for the full range - An `AbortController` cancels in-flight requests if the component unmounts - Results are sorted: daemons with more agents appear first --- # Terminal UI When the daemon starts, it displays a terminal UI (TUI) with status information and a background animation. ## Display The TUI shows: - **Agent count** — number of active AI coding agents - **Client count** — connected web/extension viewers - **Version** — daemon version and update availability - **Relay status** — connection state if `--share` is enabled - **Share URL** — copyable link for spectators - **Event log** — recent agent activity (joins, status changes, disconnects) ## Background Animations Choose an animation with `--animation ` or `-a `: | Name | Description | |------|-------------| | `matrix` | Matrix-style falling characters | | `starfield` | Parallax starfield | | `aurora` | Northern lights color waves | | `fireflies` | Floating particles | | `life` | Conway's Game of Life | | `pipes` | Screensaver-style growing pipes | | `dvd` | Bouncing DVD logo | | `starwars` | Star Wars crawl text | | `none` | No animation (plain background) | | `random` | Random animation on each start (default) | ## Disabling the TUI Use `--no-tui` to run the daemon without the terminal UI. The daemon still functions normally — it just logs to stdout instead of rendering the TUI. This is useful for: - Background processes / systemd services - CI/CD environments - Piping output to log files - Running inside tmux/screen where the TUI doesn't render well --- # Supported Platforms ctrl ships with built-in adapters for these AI coding tools. No configuration needed — just start the tool and ctrl detects it automatically. ## Claude Code [Claude Code](https://docs.anthropic.com/en/docs/claude-code) by Anthropic. The primary supported platform. - **Session format:** JSONL - **Session location:** `~/.claude/projects/` (nested by project path) - **Detection:** Automatic — scans JSONL files in Claude's session directory - **Features:** Full transcript parsing, tool categorization, cost tracking, subagent detection ## Codex CLI [Codex CLI](https://github.com/openai/codex) by OpenAI. - **Session format:** JSONL - **Session location:** `~/.codex/sessions/` - **Detection:** Automatic - **Features:** Transcript parsing, tool tracking ## Augment Code [Augment](https://www.augmentcode.com/) AI coding assistant. - **Session format:** JSONL - **Session location:** Configurable via Augment's settings - **Detection:** Automatic ## Adding More Tools ctrl's adapter system is extensible. To add support for any AI tool that writes session logs: 1. Use [Custom Platforms](/docs/platforms/custom-platforms) for tools with JSONL/JSON session files 2. Use [MCP Aliases](/docs/platforms/mcp-aliases) to customize how agents are displayed based on their MCP server configuration If your AI tool doesn't write session files, it can't be monitored by ctrl yet. We're exploring HTTP hook integrations for tools that support webhooks. --- # Custom Platforms Add support for any AI coding tool by creating a `~/.ctrl/platforms.json` configuration file. ## Configuration File Create `~/.ctrl/platforms.json`: ```json { "platforms": [ { "id": "my-tool", "displayName": "My AI Tool", "shortLabel": "MT", "adapter": "claude", "sessionFormat": "jsonl", "filePattern": "*.jsonl", "scanRecursive": true, "sessionRoots": ["~/.my-tool/sessions"] } ] } ``` ## Platform Entry Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `id` | string | Yes | Unique identifier for this platform | | `displayName` | string | Yes | Full name shown in the UI | | `shortLabel` | string | Yes | 2-3 character abbreviation for compact views | | `adapter` | string | Yes | Parser to use: `"claude"`, `"codex"`, or `"augment"` | | `sessionFormat` | string | Yes | File format: `"jsonl"` or `"json"` | | `filePattern` | string | Yes | Glob pattern for session files (e.g., `"*.jsonl"`) | | `scanRecursive` | boolean | Yes | Whether to scan subdirectories | | `sessionRoots` | string[] | Yes | Directories to scan for session files. Supports `~` for home. | | `sessionRootsEnv` | string | No | Environment variable that overrides `sessionRoots` | ## Adapter Selection The `adapter` field determines how session files are parsed: - **`claude`** — Expects Claude Code JSONL format (message/tool_use/tool_result entries) - **`codex`** — Expects Codex CLI JSONL format - **`augment`** — Expects Augment session format Choose the adapter that most closely matches your tool's session file format. ## Environment Variable Override Use `sessionRootsEnv` to let users override session directories via an environment variable: ```json { "platforms": [ { "id": "my-tool", "displayName": "My AI Tool", "shortLabel": "MT", "adapter": "claude", "sessionFormat": "jsonl", "filePattern": "*.jsonl", "scanRecursive": true, "sessionRoots": ["~/.my-tool/sessions"], "sessionRootsEnv": "MY_TOOL_SESSIONS_DIR" } ] } ``` If `MY_TOOL_SESSIONS_DIR` is set, it replaces `sessionRoots` entirely. ## Focus Command You can also configure a custom focus command in the same file. See [Focus Command](/docs/sharing/focus-command) for details. ```json { "platforms": [...], "focusCommand": "tmux select-pane -t {sessionId}" } ``` ## Example: Monitoring a Custom Claude Fork ```json { "platforms": [ { "id": "claude-custom", "displayName": "Claude (Custom Build)", "shortLabel": "CC", "adapter": "claude", "sessionFormat": "jsonl", "filePattern": "*.jsonl", "scanRecursive": true, "sessionRoots": ["~/.claude-custom/projects"] } ] } ``` ## Reloading The daemon reads `~/.ctrl/platforms.json` on startup. To pick up changes, restart the daemon: ```bash ctrl --force ``` --- # MCP Aliases MCP aliases let you override how agents are displayed based on which MCP server they're connected to. This is useful when multiple AI tools share the same session format but you want to distinguish them visually. ## Configuration Add an `aliases` array to `~/.ctrl/platforms.json`: ```json { "aliases": [ { "when": { "mcpServer": "my-custom-server" }, "displayName": "My Custom Agent", "shortLabel": "CA" } ] } ``` ## How It Works 1. When ctrl detects an agent session, it checks the session metadata for MCP server connections 2. If an agent is connected to an MCP server matching an alias `when.mcpServer`, the alias overrides the agent's display name and short label 3. The underlying adapter and parsing remain unchanged — only the visual identity changes ## Fields | Field | Type | Description | |-------|------|-------------| | `when.mcpServer` | string | MCP server name to match against | | `displayName` | string | Override display name for matching agents | | `shortLabel` | string | Override 2-3 character abbreviation | ## Example: Distinguishing Agent Roles If you run Claude Code with different MCP servers for different purposes: ```json { "aliases": [ { "when": { "mcpServer": "code-review-server" }, "displayName": "Code Reviewer", "shortLabel": "CR" }, { "when": { "mcpServer": "test-writer-server" }, "displayName": "Test Writer", "shortLabel": "TW" } ] } ``` Each agent appears with its role-specific name in the office and roster, even though they're all running Claude Code underneath. --- # Web App Overview The ctrl web app renders your AI coding agents as pixel-art characters working in a virtual office. Access it at `http://localhost:3001` after starting the daemon. ## Main Panels ### Office Canvas The central view is a pixel-art office rendered on an HTML Canvas. Each agent sits at a desk and moves between states: - **Working** — typing at their desk, tools in use - **Idle** — sitting quietly, no active tasks - **Waiting** — paused for user input or permission The office layout is customizable — rearrange desks, change themes, and resize the view. ### Agent Roster The sidebar lists all connected agents with: - **Status indicator** — color-coded dot (green = working, yellow = waiting, gray = idle) - **Platform badge** — which AI tool (Claude, Codex, Augment, or custom) - **Active tool** — what the agent is currently doing (reading files, editing, running commands) - **Token cost** — running cost estimate for the session Click an agent to open the session inspector. ### Session Inspector Detailed view of a single agent's session: - **Transcript** — full conversation history (user messages, assistant responses, thinking blocks) - **Tool history** — chronological list of tool uses with timing and results - **Output panel** — terminal output, file diffs, and command results - **Cost breakdown** — input tokens, output tokens, cache hits, total cost ### Settings Access via the gear icon. See [Settings](/docs/web-app/settings) for the full reference. - **Themes** — 31 color themes from warm to high-contrast. See [Themes & Typography](/docs/web-app/themes). - **Typography** — 11 font presets. See [Themes & Typography](/docs/web-app/themes). - **Notifications** — sound effects for task completion, permission requests, and errors - **Keep Awake** — prevent device sleep during agent work - **Line grouping** — collapse repeated output lines - **Connection** — switch daemons or enter a custom URL ## Keyboard Shortcuts | Key | Action | |-----|--------| | `Escape` | Close inspector / deselect agent | | `1-9` | Select agent by roster position | See [Keyboard Shortcuts](/docs/web-app/keyboard-shortcuts) for the full list including edit mode shortcuts. ## Hosted Version A hosted version of the web app is available at [ctrl.bulletproof.sh](https://ctrl.bulletproof.sh). It connects to your local daemon — no data leaves your machine except when using relay sharing. ## Share URLs When relay sharing is enabled, spectators can view your office at: ``` https://ctrl.bulletproof.sh/r/#key= ``` The encryption key is in the URL fragment (after `#`), so it's never sent to the server. See [Relay Sharing](/docs/sharing/relay) for setup. --- # Settings Open Settings from the gear icon in the bottom toolbar, or click the **Settings** button. ## Toggles | Setting | Default | Description | |---------|---------|-------------| | **Daemon Auto-Discovery** | On | Automatically scan for local daemon instances on common ports | | **Notifications** | On | Play sound effects when agents complete tasks, request permission, or error | | **Keep Awake** | On | Prevent your device from sleeping while agents are working (uses the Web Wake Lock API) | | **Group Lines** | Off | Collapse repeated transcript output lines by agent | ## Custom Daemon URL Override the default WebSocket connection by entering a custom daemon URL. Useful when running the daemon on a non-default port or connecting to a remote machine. Leave blank to use auto-discovery or the default `ws://localhost:3001/ws`. ## Layout Management - **Export Layout** — Download the current office layout as a JSON file - **Import Layout** — Load a previously exported layout JSON file - **Reset to Default** — Restore the default office layout ## Sharing Generate a shareable link or QR code so others can view your office. See [Relay Sharing](/docs/sharing/relay) for details on encrypted sharing. ## Danger Zone **Reset All Settings** clears all ctrl preferences from localStorage, including: - Theme and typography selections - Toggle states (notifications, wake lock, etc.) - Custom daemon URL - Selected agent The office layout is preserved — use "Reset to Default" in Layout Management to reset that separately. ## Storage All settings persist in your browser's localStorage. They are not synced across devices or browsers. Keys use the `ctrl.` prefix (e.g., `ctrl.soundEnabled`, `ctrl.keepAwakeEnabled`). --- # Themes & Typography ctrl offers 31 color themes and 11 typography options. Changes apply instantly and persist in localStorage. ## Selecting a Theme Open **Settings** and browse the theme list. Click any theme to apply it. The default is **Warm Slate: Mint**. ## Color Themes ### Warm Slate (12 variants) Warm, muted palettes with subtle accent colors. Best for extended use. | Theme | Primary | Secondary | Accent | |-------|---------|-----------|--------| | Amber | Amber | Teal | Sage | | Coral | Coral | Teal | Dusty Rose | | Gold | Gold | Slate Blue | Burnt Sienna | | Teal | Bright Teal | Warm Peach | Forest | | Violet | Soft Violet | Warm Coral | Plum | | Sage | Sage Green | Dusty Rose | Dark Sage | | Sky | Sky Blue | Amber | Steel | | Rose | Dusty Rose | Sage Green | Burgundy | | Peach | Warm Peach | Cool Blue | Terracotta | | Lavender | Lavender | Mint | Plum | | **Mint** (default) | Mint | — | — | | Tangerine | Tangerine | Teal | Burnt Orange | ### High Contrast (10 variants) Bold colors on black backgrounds. Great for visibility and accessibility. | Theme | Colors | |-------|--------| | Yellow | Bright yellow + cyan on black | | Cyan | Cyan + hot pink on black | | Magenta | Magenta + cyan on black | | Orange | Bright orange + cyan on black | | Green | Bright green + yellow on black (classic terminal) | | Red | Bright red + cyan on black | | Blue | Electric blue + orange on black | | Purple | Purple + green on black | | White | Pure white + cyan (monochrome) | | Pink | Hot pink + lime on black | ### Classic (9 themes) Unique character themes inspired by popular color schemes. | Theme | Description | |-------|-------------| | Midnight Ocean | Deep navy with coral and teal accents | | Phosphor | Neon green on dark — retro terminal aesthetic | | Arctic Steel | Cool blues with warm highlights | | Rose Pine | Mauve, rose, and pine tones | | Copper Forge | Copper, slate, and sage metallic palette | | Paper & Ink | Light theme — cream background with navy text and red accents | | Carbon | Lime and teal on very dark — technical feel | | Tokyo Night | Electric indigo, violet, and orange neon | | Ember | Deep black with hot orange and molten red | ## Typography Select a typography preset from Settings. Each preset changes three font families: display (headings), body (text), and mono (code). | Preset | Display | Body | Mono | |--------|---------|------|------| | **Geometric** (default) | Plus Jakarta Sans | Outfit | Space Mono | | Modern | Inter | Inter | JetBrains Mono | | Editorial | Instrument Serif | Source Sans 3 | Fira Code | | Technical | Space Grotesk | DM Sans | IBM Plex Mono | | Chakra | Chakra Petch | Inter | JetBrains Mono | | Bebas | Bebas Neue | Lato | Fira Code | | Syne | Syne | Outfit | Space Mono | | Playfair | Playfair Display | Lora | Fira Code | | Quicksand | Quicksand | Rubik | Inconsolata | | Work Sans | Work Sans | Karla | JetBrains Mono | | Poppins | Poppins | Nunito Sans | IBM Plex Mono | Fonts are self-hosted and loaded via `@font-face` declarations — no external requests to Google Fonts. Typography selections persist across sessions. --- # Keyboard Shortcuts ## General | Key | Action | |-----|--------| | `Escape` | Close inspector / deselect agent | | `1-9` | Select agent by roster position | ## Office Edit Mode Enter edit mode from the **Edit** button in the bottom toolbar. These shortcuts are active only while editing. | Key | Action | |-----|--------| | `Escape` | Step-by-step deselect: furniture → tool → placement → exit edit mode | | `Delete` / `Backspace` | Delete selected furniture | | `R` | Rotate selected furniture | | `T` | Toggle furniture state (e.g., open/closed) | | `Ctrl+Z` / `Cmd+Z` | Undo | | `Ctrl+Y` / `Cmd+Y` | Redo | | `Ctrl+Shift+Z` / `Cmd+Shift+Z` | Redo (alternate) | ## Bottom Toolbar The bottom toolbar provides quick access to common actions: | Button | Action | |--------|--------| | **Status** | Shows connection status (Local / Remote / Relay / Off) with tooltip details | | **About** | Version and project info | | **Settings** | Open settings panel | | **Edit** | Toggle office edit mode | | **Setup** | Show setup instructions | | **Fit** | Zoom to fit office in viewport | | **Share** | Copy shareable link (when relay is enabled) | | **GitHub** | Open project on GitHub | --- # VS Code Extension The ctrl VS Code extension embeds the office viewer directly in your editor sidebar. No browser tab needed. ## Install Search for **ctrl** in the VS Code Extensions marketplace, or install from the command line: ```bash code --install-extension bulletproof-sh.ctrl ``` ## Usage After installing, open the **Ctrl** panel from the VS Code Activity Bar (sidebar). The extension automatically connects to your local daemon. The embedded view is the same web app — all features (themes, settings, session inspector) work identically. ## Configuration The extension exposes one setting: | Setting | Options | Default | |---------|---------|---------| | `ctrl.webAppUrl` | Production, Alpha, Beta, Local | Production (`https://ctrl.bulletproof.sh`) | **Options:** - **Production** — `https://ctrl.bulletproof.sh` (stable release) - **Alpha** — `https://ctrl.alpha.bulletproof.sh` (preview features from `develop`) - **Beta** — `https://ctrl.beta.bulletproof.sh` (pre-release from `staging`) - **Local** — `http://localhost:5173` (for development) Change via **Settings → Extensions → Ctrl** or add to `settings.json`: ```json { "ctrl.webAppUrl": "https://ctrl.alpha.bulletproof.sh" } ``` ## Commands | Command | Description | |---------|-------------| | `Ctrl: Show Panel` | Open or focus the ctrl panel | Access via the Command Palette (`Cmd+Shift+P` / `Ctrl+Shift+P`). ## How It Works The extension runs a webview that loads the ctrl web app. It communicates with the daemon the same way the browser version does — via WebSocket. Your session data stays local. --- # Relay Sharing Share your office with teammates in real time. The relay forwards encrypted WebSocket messages between your daemon and remote viewers. ## Quick Start ```bash ctrl --share ``` The daemon prints a share URL: ``` Share URL: https://ctrl.bulletproof.sh/r/abc123#key=def456 ``` Send this URL to anyone — they'll see your office in real time, read-only. ## How It Works 1. The daemon connects to a relay server via WebSocket 2. The relay assigns a room code and forwards messages between participants 3. All messages are end-to-end encrypted with AES-GCM by default 4. The encryption key is in the URL fragment (`#key=...`), so it never reaches the relay server 5. Spectators open the share URL and see your office rendered live ## Encryption End-to-end encryption is enabled by default. The daemon generates a random AES-GCM key, encrypts all messages before sending them to the relay, and embeds the key in the URL fragment. The relay server sees only encrypted blobs — it cannot read agent names, session content, or any office state. To disable encryption (e.g., for debugging): ```bash ctrl --share --no-encrypt ``` ## Custom Relay Server By default, ctrl uses the hosted relay at `relay.bulletproof.sh`. To use your own: ```bash ctrl --share --relay-url wss://my-relay.example.com ``` Or via environment variable: ```bash CTRL_RELAY_URL=wss://my-relay.example.com ctrl --share ``` ## Relay-Only Mode Run a daemon that only connects to a relay, without starting a local WebSocket server: ```bash ctrl --relay-only --share ``` This is useful for headless servers where you don't need local browser access. ## Environment Channels The relay URL defaults to the appropriate environment: | Branch | Relay URL | |--------|-----------| | production | `wss://relay.bulletproof.sh` | | alpha | `wss://relay.alpha.bulletproof.sh` | | beta | `wss://relay.beta.bulletproof.sh` | ## Labels Spectators see your daemon's label in their connection UI. Set it to something descriptive: ```bash ctrl --share --label "Backend Team" ``` Defaults to your machine's hostname. --- # Focus Command The "focus terminal" feature brings an agent's terminal window to the foreground when you click them in the UI. By default, ctrl uses native terminal APIs (macOS only) to find and activate the terminal window — kitty via remote control, and Terminal.app/iTerm2 via AppleScript. For environments like tmux, agent-deck, or other multiplexers, you can configure a custom focus command. ## The Problem When agents run inside tmux panes, the default focus mechanism finds the tmux client window's TTY — not the individual pane. This means clicking "focus" activates the tmux window but can't switch to the correct pane. ## Custom Focus Command Add a `focusCommand` to `~/.ctrl/platforms.json`: ```json { "focusCommand": "tmux select-pane -t {sessionId}" } ``` When you click "focus terminal" in the UI, ctrl runs your command instead of the built-in AppleScript activation. If the command fails (non-zero exit), it falls back to the built-in method. ## Variables These placeholders are replaced in your command before execution: | Variable | Description | Example | |----------|-------------|---------| | `{sessionId}` | Session filename without extension | `abc123-def456` | | `{pid}` | Agent's TTY process ID | `12345` | | `{tty}` | TTY device path | `/dev/ttys003` | | `{jsonlFile}` | Full path to session JSONL file | `/home/user/.claude/projects/.../session.jsonl` | | `{platform}` | Platform identifier | `claude-code` | String variables (`{sessionId}`, `{jsonlFile}`, `{platform}`) are shell-escaped with single quotes for safe interpolation. `{pid}` and `{tty}` are raw values (integer and device path). ## Examples ### tmux Switch to the pane running the agent: ```json { "focusCommand": "tmux select-pane -t {sessionId}" } ``` ### agent-deck [agent-deck](https://github.com/asheshgoplani/agent-deck) is a Go-based TUI for managing AI coding agents in tmux: ```json { "focusCommand": "agent-deck focus {sessionId}" } ``` ### Custom script Run any script with agent context: ```json { "focusCommand": "/path/to/my-focus-script.sh --session {sessionId} --pid {pid}" } ``` ## Security Model The focus command template is read from `~/.ctrl/platforms.json` — a file only the local user can write. The WebSocket message that triggers focus only sends an integer agent ID; all variable values come from the daemon's own state, not from the network client. Variables are shell-escaped before substitution to prevent injection via crafted metadata. ## Fallback Behavior If the custom command exits with a non-zero code, ctrl falls back to the built-in terminal activation: 1. Resolve the agent's TTY device 2. Check that the TTY process is still alive 3. Use AppleScript/JXA to activate the terminal window (macOS only) If both methods fail, the UI shows "Terminal not found". --- # Desktop App The ctrl desktop app bundles the daemon and web UI into a standalone application. No browser tab needed — just launch the app and go. ## Download Download from [bulletproof.sh/download](/download) or from [GitHub Releases](https://github.com/bulletproof-sh/ctrl/releases). ### macOS - **Apple Silicon** (M1/M2/M3/M4): `.dmg` for ARM64 - **Intel**: `.dmg` for x86_64 - Code signed and notarized with Apple Developer ID ### Windows - **x86_64**: `.msi` installer or `.exe` NSIS installer - Code signed with Entrust SAS certificate ### Linux - **x86_64**: `.deb` and `.AppImage` - **ARM64**: `.deb` and `.AppImage` ## Auto-Updates The desktop app checks for updates on launch and installs them in the background. Updates are signed and verified before installation. | Channel | Branch | Domain | |---------|--------|--------| | Stable | production | `get.bulletproof.sh/ctrl-desktop/stable/` | | Beta | staging | `get.bulletproof.sh/ctrl-desktop/beta/` | | Alpha | develop | `get.bulletproof.sh/ctrl-desktop/alpha/` | ## Bundled Daemon The desktop app includes the daemon as a sidecar binary. It starts automatically when you launch the app and stops when you close it. You don't need to run `ctrl` separately. ## System Requirements - **macOS**: 12.0 (Monterey) or later - **Windows**: 10 or later - **Linux**: Ubuntu 20.04+ or equivalent (glibc 2.31+) - **RAM**: 100MB - **Disk**: 150MB ## Troubleshooting **App won't start on macOS**: Right-click → Open → Open to bypass Gatekeeper on first launch. **Daemon not detecting agents**: Ensure your AI tool is running and writing session files. Check `~/.claude/projects/` (for Claude Code) or the appropriate session directory. **Update stuck**: Delete `~/Library/Application Support/ctrl-desktop/` (macOS) or `%APPDATA%/ctrl-desktop/` (Windows) and relaunch. --- # WebSocket API The daemon exposes a WebSocket endpoint for real-time communication with clients. This is the protocol used by the web app, desktop app, and VS Code extension. ## Connection ``` ws://localhost:3001/ws ``` The port matches the daemon's `--port` flag (default 3001). ## Protocol After connecting, the client should send a `clientVersion` message. The daemon responds with an `existingAgents` snapshot, then streams updates in real time. ## Message Format All messages are JSON-encoded strings with a `type` discriminant: ```json { "type": "messageType", ...fields } ``` ## Daemon → Client Messages ### existingAgents Sent immediately after connection. Contains the full current state. ```json { "type": "existingAgents", "agents": [ { "id": 1, "platform": "claude-code", "status": "working", "activeTool": "Edit", "metadata": { "isSubagent": false }, "tokenUsage": { "input": 5000, "output": 1200 } } ] } ``` ### agentCreated A new agent session was detected. ```json { "type": "agentCreated", "id": 2, "platform": "claude-code", "status": "idle" } ``` ### agentClosed An agent session ended. ```json { "type": "agentClosed", "id": 2 } ``` ### agentStatus An agent's status changed. ```json { "type": "agentStatus", "id": 1, "status": "waiting", "waitReason": "permission" } ``` Status values: `"idle"`, `"working"`, `"waiting"` ### agentToolStart An agent started using a tool. ```json { "type": "agentToolStart", "id": 1, "tool": "Edit", "toolId": "tool_abc123", "category": "write" } ``` Tool categories: `"read"`, `"write"`, `"edit"`, `"execute"`, `"search"`, `"delegate"`, `"plan"`, `"network"`, `"system"` ### agentToolDone An agent finished using a tool. ```json { "type": "agentToolDone", "id": 1, "toolId": "tool_abc123" } ``` ### agentCostUpdate Token usage updated for an agent. ```json { "type": "agentCostUpdate", "id": 1, "tokenUsage": { "input": 8000, "output": 2400, "cacheRead": 3000, "cacheCreate": 1500 } } ``` ### agentSessionResult An agent's session completed. ```json { "type": "agentSessionResult", "id": 1, "result": { "success": true, "duration": 45000, "turnCount": 12, "totalCost": 0.15 } } ``` ### scannerReady Initial scanning of session directories is complete. Agents detected during scan have already been sent as `agentCreated` messages. ```json { "type": "scannerReady" } ``` ## Client → Daemon Messages ### clientVersion Identify the client and its protocol version. Send immediately after connecting. ```json { "type": "clientVersion", "version": "0.1.0", "protocolVersion": 2 } ``` ### clientLayout Send the office layout to the daemon (for relay broadcast to other clients). ```json { "type": "clientLayout", "layout": { ...officeLayoutObject } } ``` ### focusAgent Request the daemon to bring an agent's terminal to the foreground. ```json { "type": "focusAgent", "id": 1 } ``` Response: ```json { "type": "focusAgentResult", "success": true } ``` Or on failure: ```json { "type": "focusAgentResult", "success": false, "error": "Terminal not found" } ``` ## Protocol Versioning The daemon and client exchange protocol versions via `clientVersion`. If the client's protocol version is below the daemon's minimum compatible version, the daemon sends a version mismatch warning. Clients should prompt users to update. ## Building Integrations The WebSocket API is stable and suitable for building custom dashboards, monitoring tools, or integrations. Connect, send `clientVersion`, and process the stream of agent events. For relay connections, messages are wrapped in a relay envelope: ```json { "v": 1, "src": "daemon", "type": "relay", "payload": "...encrypted or plain message..." } ``` --- # Troubleshooting Common issues and solutions when using ctrl. ## Daemon Won't Start **Port in use** ``` Error: Port 3001 is already in use ``` Another daemon (or another process) is using the port. Either: - Use `ctrl --force` to kill the existing daemon and take over - Use `ctrl --port 3002` to start on a different port - Find and kill the process: `lsof -i :3001` **Lock file exists** ``` Error: Another daemon instance is running ``` A previous daemon didn't clean up. Use `ctrl --force` to take over, or `ctrl --no-lock` to bypass. ## No Agents Detected **AI tool not running**: Start your AI coding tool (Claude Code, Codex, etc.) and wait a few seconds. The daemon scans for new sessions every 2 seconds. **Wrong session directory**: The daemon auto-discovers sessions from known locations. If your tool writes sessions elsewhere, use [Custom Platforms](/docs/platforms/custom-platforms) to tell ctrl where to look. **Custom Claude installation**: If you use a custom Claude alias with a non-default home directory (e.g., `CLAUDE_HOME=~/.claude-myalias`), the daemon won't find those sessions unless you set `CLAUDE_HOME` when starting the daemon. See [Multiple Claude Installations](/docs/daemon/cli-reference#multiple-claude-installations). **Using `--project-dir`**: When started with `--project-dir`, the daemon only watches that specific directory. Remove the flag to auto-discover all sessions. ## Web App Won't Connect **Daemon not running**: Check that `ctrl` is running in a terminal. The web app connects to `ws://localhost:3001/ws` by default. **Different port**: If the daemon started on a non-default port (e.g., 3002), the web app should auto-discover it. If not, enter `ws://localhost:3002/ws` in the connection picker. **Browser blocking localhost WebSocket**: Some browsers or extensions block WebSocket connections to localhost. Try disabling ad blockers or privacy extensions temporarily. ## Focus Terminal Not Working **macOS only**: The built-in focus feature only works on macOS with Terminal.app, iTerm2, or kitty. **kitty setup**: kitty requires remote control to be enabled. Add these lines to your `kitty.conf`: ``` allow_remote_control yes listen_on unix:/tmp/kitty.sock ``` Restart kitty after editing the config. The daemon will log an actionable error message if the config is missing or incorrect. **tmux/multiplexer**: The built-in focus finds the tmux client window, not individual panes. Use [Focus Command](/docs/sharing/focus-command) to configure tmux pane switching. **Process exited**: If the agent's terminal process has exited, focus won't work. The UI shows "Terminal process exited". ## Relay Sharing Issues **Connection fails immediately**: Check your network connection. The relay server must be reachable. Try `curl https://relay.bulletproof.sh/health`. **Spectators see nothing**: Ensure you're using the full share URL including the `#key=...` fragment. Without the key, encrypted messages can't be decrypted. **High latency**: The relay adds network round-trip time. For local viewing, connect directly to `localhost:3001` instead of using the relay. ## Token Costs Wrong Token costs are estimated based on model pricing data bundled with ctrl. If you're using a custom model or pricing has changed, costs may be inaccurate. Cost tracking is best-effort — use your provider's dashboard for official billing. ## Extension Not Working **Extension not activating**: The VS Code extension activates when it detects a running daemon. Start `ctrl` first, then reload VS Code. **Webview blank**: Try `Developer: Reload Webviews` from the VS Code command palette. ## Getting Help - [GitHub Issues](https://github.com/bulletproof-sh/ctrl/issues) — bug reports and feature requests - [GitHub Discussions](https://github.com/bulletproof-sh/ctrl/discussions) — questions and community help ---