# The Socket & CLI — How Agents Drive cmux Programmatically

> The cmux CLI connects to a Unix socket that the running app exposes. Agents (Claude Code, Codex, etc.) send commands over that socket to create workspaces, send keystrokes, report metadata to the sidebar, and trigger notifications — all without touching the GUI. This page covers the CLI entry point, the socket path rules, and the Go-based remote daemon used for SSH sessions.

- Repository: manaflow-ai/cmux
- GitHub: https://github.com/manaflow-ai/cmux
- Human wiki: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-e789cf316a9a
- Complete Markdown: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-e789cf316a9a/llms-full.txt

## Source Files

- `CLI/cmux.swift`
- `CLI/CLISocketPathResolver.swift`
- `CLI/CMUXCLI+Events.swift`
- `Sources/SocketControlSettings.swift`
- `daemon/remote/go.mod`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [CLI/CLISocketPathResolver.swift](CLI/CLISocketPathResolver.swift)
- [CLI/cmux.swift](CLI/cmux.swift)
- [CLI/CMUXCLI+Events.swift](CLI/CMUXCLI+Events.swift)
- [Sources/SocketControlSettings.swift](Sources/SocketControlSettings.swift)
- [Sources/SocketControlSettings+SocketPathMarkers.swift](Sources/SocketControlSettings+SocketPathMarkers.swift)
- [daemon/remote/go.mod](daemon/remote/go.mod)
- [daemon/remote/cmd/cmuxd-remote/main.go](daemon/remote/cmd/cmuxd-remote/main.go)
- [daemon/remote/cmd/cmuxd-remote/cli.go](daemon/remote/cmd/cmuxd-remote/cli.go)
</details>

# The Socket & CLI — How Agents Drive cmux Programmatically

When you run cmux, it opens a Unix domain socket and listens for commands. The `cmux` CLI is the primary client for that socket. Any process — including AI coding agents such as Claude Code or Codex — can send commands over the socket to create workspaces, inject keystrokes, post sidebar notifications, stream events, and more, entirely without touching the GUI.

This page explains how the socket works, how the CLI finds it, what commands are available, how access is controlled, and how the Go-based remote daemon bridges the socket into SSH sessions and cloud VMs.

---

## The Socket in One Sentence

When the cmux app starts, it creates a Unix socket file. The CLI (and any other authorized local process) connects to that file, writes JSON-RPC or text commands, and reads responses — all without any GUI interaction.

```text
┌──────────────────────────────────────────────────────────┐
│                  macOS machine                           │
│                                                          │
│  ┌─────────────────┐    Unix socket     ┌─────────────┐ │
│  │  cmux.app       │◄──────────────────►│  cmux CLI   │ │
│  │  (listener)     │  ~/...cmux/cmux.sock│  (client)   │ │
│  └─────────────────┘                    └─────────────┘ │
│                                                 ▲        │
│                                          ┌──────┘        │
│                                    Claude Code / Codex / │
│                                    any authorized process │
└──────────────────────────────────────────────────────────┘
```

---

## Socket Path Rules

The app and the CLI must agree on which socket file to use. The rules live in two places: `Sources/SocketControlSettings.swift` on the app side and `CLI/CLISocketPathResolver.swift` on the CLI side. Both share constants via the `CMUXSocketPathDomain` module.

### Canonical paths by app variant

| App variant | Default socket path |
|---|---|
| Release (`com.cmuxterm.app`) | `~/Library/Application Support/cmux/cmux.sock` |
| Debug tagged (`com.cmuxterm.app.debug.<tag>`) | `/tmp/cmux-debug-<tag>.sock` |
| Debug untagged (blocked at launch) | `/tmp/cmux-debug.sock` |
| Nightly | `/tmp/cmux-nightly.sock` |
| Staging | `/tmp/cmux-staging.sock` |
| User-scoped fallback (port conflict) | `~/Library/Application Support/cmux/cmux-<uid>.sock` |

Legacy path `/tmp/cmux.sock` is still tracked as a fallback for older installs.

Sources: [Sources/SocketControlSettings.swift:1-62](), [CLI/CLISocketPathResolver.swift:83-88]()

### How the CLI resolves which socket to connect to

`CLISocketPathResolver.resolve()` is the single entry point. It works in three tiers:

1. **Explicit** — `--socket <path>` flag passed on the command line.
2. **Environment** — `CMUX_SOCKET_PATH` env var (legacy alias: `CMUX_SOCKET`; both set and different → error).
3. **Auto-discovery** — when neither flag nor env is set, builds a candidate list and probes each one.

The auto-discovery candidate list, in priority order:
1. Computed default for this executable's bundle ID and variant.
2. Last recorded socket path (written to marker files when the app successfully binds).
3. Implicit fallback paths for the variant.
4. Up to 12 recently-modified tagged debug sockets found by scanning `/tmp` and `~/Library/Application Support/cmux/` for files matching `cmux-debug-*.sock`.

For each candidate path, `canConnect()` does a non-blocking Unix `connect()` followed by a `poll()` with a 150 ms timeout. The first path that is actively accepting connections wins. If nothing is live, the resolver falls back to the first path where the socket file exists at all.

```text
CLI starts
    │
    ├─ --socket flag?  ──yes──► use it directly
    │
    ├─ CMUX_SOCKET_PATH set? ──yes──► use env path
    │
    └─ auto-discover
           │
           ├─ default path for bundle ID
           ├─ last-known path (marker file)
           ├─ variant fallback paths
           └─ scanned tagged sockets (mtime-sorted, up to 12)
                   │
                   └─ canConnect() probe (150ms each)
                          │
                          ├─ live connection found ──► use it
                          └─ socket file exists ──► use it
```

Sources: [CLI/CLISocketPathResolver.swift:76-190](), [CLI/CLISocketPathResolver.swift:249-329]()

### Untagged debug builds are blocked

To prevent a developer's untagged debug build from stomping on another agent's socket, `SocketControlSettings.shouldBlockUntaggedDebugLaunch()` refuses to start if bundle ID is `com.cmuxterm.app.debug` and `CMUX_TAG` is not set. XCTest environments and `CMUX_UI_TEST_*` vars bypass this. This is why `CLAUDE.md` insists on `./scripts/reload.sh --tag <name>` for all dev work.

Sources: [Sources/SocketControlSettings.swift:370]()

---

## Access Control Modes

The app side exposes five modes configurable in Settings (and overridable via env vars `CMUX_SOCKET_ENABLE` / `CMUX_SOCKET_MODE`):

| Mode | Who can connect | Socket file permissions |
|---|---|---|
| `off` | Nobody | `0o600` |
| `cmuxOnly` *(default)* | Only processes that cmux itself launched | `0o600` |
| `automation` | Any process running as the same macOS user | `0o600` |
| `password` | Any process that provides the correct password | `0o600` |
| `allowAll` | Any local process, any user, no auth | `0o666` |

In `cmuxOnly` mode the app checks process ancestry — the connecting process must be a descendant of the cmux app process. This is the default and prevents arbitrary processes from driving the terminal without explicit opt-in.

### Password storage

When `password` mode is in use, the secret is stored at `~/Library/Application Support/cmux/socket-control-password` (directory `0o700`, file `0o600`). The env var `CMUX_SOCKET_PASSWORD` overrides the file at runtime. On first launch after an upgrade, cmux migrates any existing password from the legacy keychain entry (`com.cmuxterm.app.socket-control` / `local-socket-password`) to the file.

Sources: [Sources/SocketControlSettings.swift:8-62](), [Sources/SocketControlSettings.swift:64-291]()

---

## Wire Protocols: V1 Text and V2 JSON-RPC

The socket speaks two distinct protocols. Legacy commands use **V1** (plain text); all modern automation uses **V2** (newline-delimited JSON-RPC).

### V1 — Plain text

Used for a handful of window-management commands inherited from earlier versions. The client writes one line, the server writes one or more lines back. The CLI reads until 120 ms of idle silence after the first newline.

```
# Request
list_windows\n

# Response (one line per window)
window:1 My Project
window:2 Server Logs
```

V1 commands: `ping`, `new_window`, `current_window`, `close_window`, `focus_window`, `list_windows`.

Sources: [daemon/remote/cmd/cmuxd-remote/cli.go:65-70](), [daemon/remote/cmd/cmuxd-remote/cli.go:229-256]()

### V2 — JSON-RPC (newline-delimited)

All workspace, surface, pane, notification, browser, and streaming commands use V2. Each request and response is a single JSON object terminated by `\n`.

**Request shape:**
```json
{"id": "a3f2", "method": "workspace.create", "params": {"title": "my-project", "initial_command": "npm run dev"}}
```

**Success response:**
```json
{"id": "a3f2", "ok": true, "result": {"workspace_id": "workspace:7"}}
```

**Error response:**
```json
{"id": "a3f2", "ok": false, "error": {"code": "not_found", "message": "workspace not found"}}
```

Read timeout: 15 s. Max frame size in the remote daemon: 4 MB.

Sources: [daemon/remote/cmd/cmuxd-remote/main.go:26-53](), [daemon/remote/cmd/cmuxd-remote/cli.go:259-303]()

---

## CLI Command Reference

The Swift `cmux` CLI and the Go `cmuxd-remote` CLI share the same logical command set (the Go binary is a relay, not a separate command surface). All the V2 commands below map flag names to JSON-RPC method names.

### Workspace management

| CLI command | JSON-RPC method | Key flags |
|---|---|---|
| `list-workspaces` | `workspace.list` | — |
| `new-workspace` | `workspace.create` | `--command`, `--working-directory`, `--name` |
| `close-workspace` | `workspace.close` | `--workspace` |
| `select-workspace` | `workspace.select` | `--workspace` |
| `current-workspace` | `workspace.current` | — |

### Surface (panel/split) management

| CLI command | JSON-RPC method | Key flags |
|---|---|---|
| `list-panels` | `surface.list` | `--workspace` |
| `focus-panel` | `surface.focus` | `--panel`, `--workspace` |
| `new-surface` | `surface.create` | `--workspace`, `--pane` |
| `new-split` | `surface.split` | `--surface`, `--direction` |
| `close-surface` | `surface.close` | `--surface` |
| `refresh-surfaces` | `surface.refresh` | — |

### Sending input

| CLI command | JSON-RPC method | Key flags |
|---|---|---|
| `send` | `surface.send_text` | `--surface`, `--text` |
| `send-key` | `surface.send_key` | `--surface`, `--key` |

### Notifications

```bash
cmux notify --title "Build done" --body "Tests passed" --workspace workspace:1
```

Maps to `notification.create`. Agents use this to post status updates visible in the cmux sidebar without any shell output.

Sources: [daemon/remote/cmd/cmuxd-remote/cli.go:63-91]()

### Browser control

The `browser` subcommand drives an embedded browser panel. Sub-commands: `open`, `navigate`, `back`, `forward`, `reload`, `get-url`, `snapshot`, `eval`, `click`, `dblclick`, `hover`, `focus`, `check`, `uncheck`, `type`, `fill`, `press`, `select`, `screenshot`.

```bash
cmux browser open --url https://localhost:3000 --workspace workspace:1
cmux browser screenshot --surface surface:2 > preview.png
```

Sources: [daemon/remote/cmd/cmuxd-remote/cli.go:93-121]()

### Streaming events

```bash
cmux events --category workspace --reconnect --cursor-file /tmp/cmux-cursor.txt
```

The `events` subcommand opens a persistent streaming connection over V2 (`events.stream` method) and prints one NDJSON object per line to stdout. The `--cursor-file` flag atomically writes the last seen `seq` number so a restarted consumer can resume without gaps. With `--reconnect`, transient errors (socket not found, broken pipe, ECONNRESET, etc.) trigger a 1-second sleep and retry rather than an immediate exit.

Frame types: `ack`, `heartbeat`, `event`. Each `event` frame carries a monotonically increasing `seq` number.

Sources: [CLI/CMUXCLI+Events.swift:8-134]()

### Environment variables set by cmux terminals

When the app spawns a shell inside a terminal panel, it automatically sets:

| Variable | Value |
|---|---|
| `CMUX_SOCKET_PATH` | Path to the active socket |
| `CMUX_WORKSPACE_ID` | ID of the containing workspace |
| `CMUX_SURFACE_ID` | ID of the surface (terminal panel) |
| `CMUX_TAB_ID` | ID of the tab |

Because `CMUX_SOCKET_PATH` is already set, child processes (including AI agents) automatically connect to the right socket without any extra configuration.

Sources: [CLI/cmux.swift]() (env resolution at line 2796-2826)

---

## How an AI Agent Drives cmux: End-to-End Flow

```text
Agent process (Claude Code / Codex / script)
        │
        │  inherits CMUX_SOCKET_PATH from terminal env
        ▼
cmux CLI  (or direct socket write)
        │
        │  1. Resolve socket path
        │  2. Authenticate (ancestry check, password, or open)
        │  3. Write JSON-RPC request line
        │  4. Read JSON response line
        ▼
cmux.app  (socket listener in TerminalController)
        │
        │  Dispatch to workspace / surface / pane / notification handler
        ▼
UI updates, keystroke injection, sidebar notifications
```

A typical Claude Code session might run:

```bash
# Create a dedicated workspace for the agent's task
cmux new-workspace --name "feature-branch" --command "zsh"

# Send a command to the new workspace's terminal
cmux send --workspace workspace:3 --text "npm test\n"

# Post a sidebar notification when done
cmux notify --title "Tests complete" --body "All 142 tests passed" --workspace workspace:3
```

---

## The Remote Daemon: Driving cmux over SSH

When working inside a cloud VM or an SSH session, there is no direct path to the local Unix socket. The `cmuxd-remote` Go binary (at `daemon/remote/cmd/cmuxd-remote/`) bridges the gap.

### Two transport modes

**stdio (for cloud VMs):** The app spawns `cmuxd-remote serve --stdio` as a child process. JSON-RPC frames flow over stdin/stdout. The `cmuxd-remote` binary inside the VM is the RPC server; it manages PTY sessions and TCP proxy streams.

**WebSocket (for SSH relay):** `cmuxd-remote serve --ws --auth-lease-file <path>` starts a WebSocket server. The app connects to it as a client over the relay transport.

### Socket path resolution inside a remote session

In a remote (SSH) context the Go CLI cannot see the local Unix socket. It resolves the socket address in this order:

1. `--socket <path>` flag
2. `CMUX_SOCKET_PATH` env var
3. `~/.cmux/socket_addr` file — written by the app after a reverse SSH tunnel is established

If `socket_addr` contains a `host:port` value (no leading `/`), the Go CLI dials TCP. For TCP connections over the relay, it performs an HMAC-SHA256 challenge-response handshake:

```text
Server → {"protocol":"cmux-relay-auth","version":1,"relay_id":"…","nonce":"…"}
Client → {"relay_id":"…","mac":"HMAC-SHA256(token, "relay_id=…\nnonce=…\nversion=1")"}
```

Relay credentials come from `CMUX_RELAY_ID`/`CMUX_RELAY_TOKEN` env vars or from `~/.cmux/relay/<port>.auth`.

Sources: [daemon/remote/cmd/cmuxd-remote/cli.go:582-724](), [daemon/remote/go.mod:1-8]()

### Remote daemon RPC capabilities

The server side of `cmuxd-remote` advertises these capability strings on `hello`:

| Capability | What it covers |
|---|---|
| `session.basic` | Terminal window-size tracking across multiple clients |
| `session.resize.min` | Min-of-all-attachments dimension negotiation |
| `proxy.http_connect` | HTTP CONNECT tunnel through the daemon |
| `proxy.socks5` | SOCKS5 proxy tunnel |
| `proxy.stream` | Raw TCP stream proxy |
| `proxy.stream.push` | Server-initiated data push on a proxy stream |
| `pty.session` | Full PTY session management |
| `pty.session.token` | Per-attachment token auth for PTY sessions |

Sources: [daemon/remote/cmd/cmuxd-remote/main.go:330-380]()

---

## Security Considerations

- **Default mode is `cmuxOnly`** — only processes that cmux itself started can connect. An AI agent running in a cmux terminal inherits the right ancestry automatically; an agent started elsewhere must use `automation` or `password` mode.
- **Socket file permissions** are `0o600` for all modes except `allowAll` (`0o666`). Even in `automation` mode, no other macOS user can read or write the socket file.
- **Password is stored on disk**, not in the keychain (migrated from keychain in older versions). The file is mode `0o600` in a `0o700` directory. The env var `CMUX_SOCKET_PASSWORD` can override it without touching the file.
- **Relay auth uses HMAC-SHA256** with a per-connection nonce, so replay attacks are not viable.
- **The app refuses to start an untagged debug build** if another debug instance is running, preventing accidental socket theft between agents.

Sources: [Sources/SocketControlSettings.swift:8-62](), [Sources/SocketControlSettings.swift:64-291](), [daemon/remote/cmd/cmuxd-remote/cli.go:672-724]()

---

## Summary

The cmux socket is the control plane for programmatic automation. The app binds a Unix socket at a well-known path (primarily `~/Library/Application Support/cmux/cmux.sock` for release builds), and the CLI auto-discovers it via a live-probe mechanism that scans candidate paths and tagged debug sockets. Access is governed by five modes from locked-down ancestry checking to full open access. Two wire protocols coexist: a simple text protocol for legacy window commands and a newline-delimited JSON-RPC protocol for all modern workspace, surface, pane, notification, browser, and event-streaming operations. For remote sessions, the Go `cmuxd-remote` binary relays the same command set over stdio or WebSocket with HMAC-based relay authentication, allowing AI agents operating inside cloud VMs or SSH sessions to drive cmux as if they were local.

Sources: [Sources/SocketControlSettings.swift:760-780](), [daemon/remote/cmd/cmuxd-remote/main.go:24-80]()
