cmux Cloud machines Use an existing machine and a workspace for the task. Machine terminals persist when panes close or the Mac disconnects. Work headlessly; open a pane or share a URL when there is a result for the user to inspect. Start with discovery On the Mac, host operations need the app, sign-in and its private tunnel. Inside a machine, use and the guest's help; read guest operations for its supported subset and auth. Installed help is authoritative when versions differ. inspects placement without creating a machine. Read and the backend's / ; do not assume a plan cap, memory size or p…

--timeout 600 && cmux vm terminal read \u003cid> \u003cterm>\n# from inside the machine (an agent's own hooks/scripts); default target = its own terminal\ncmux send-key --terminal \u003cterm> enter\ncmux terminal read \u003cterm>\n```\n\nAcross machines, discover the owner's available peer routes with `cmux self peers`\nor the legacy `cmux vm ls` fallback when the guest's help exposes it, then use the guest's supported verbs. There is no current Mac\nenrollment/grant command; older peer-route files remain compatible. See\n[guest operations](guest.md) for the host/guest boundary.\n\n```bash\n# inside \u003cbuilder>:\ncmux vm agent --machine reviewer --agent codex --name \"review\" --cwd work/app -- \"review the diff on branch feat/x and write REVIEW.md\"\ncmux vm terminal wait-exit reviewer \u003cterm> --timeout 1800 # the agent's process ended\ncmux vm terminal output reviewer \u003cterm> | tail -n 40 # what it said\ncmux vm exec reviewer -- cat work/app/REVIEW.md\ncmux vm env set reviewer GITHUB_REPO=org/app # settings for the peer's shells\ncmux vm push reviewer ./deploy_key ~/.ssh/deploy_key --mode 600 # one file over the link into the peer's `cmux file receive`; never through exec\ncmux vm agent reviewer --agent codex --wait --output -- \"summarize REVIEW.md in three lines\" # until-done on the peer\ncmux vm layout apply reviewer review-layout.json --name review # a workspace on the peer, ready for the human\n```\n\nA machine holds no control-plane credential and reaches peers through its owner's\nprivate routes. The earlier Mac enrollment broker is no longer implemented.\n\n### Queue a message into a Codex session\n\n`codex queue` talks to the Codex app-server session store. Run it as the same\nuser on the target machine that owns the session, with that user's original\n`CODEX_HOME`; do not run it from a copied home directory. The queue is local to\nthat app-server instance, so a successful enqueue in another home does not\nreach the visible session.\n\nUse the session's exact UUID (or an exact session name) and quote the message:\n\n```bash\ncmux vm exec \u003cid> -- codex queue \\\n --thread \u003ccodex-thread-uuid> \\\n --message 'Please re-check the failing test and report the result.'\n```\n\nFor a session on the current machine, the equivalent is:\n\n```bash\ncodex queue --thread \u003ccodex-thread-uuid> --message 'Continue with the next step.'\n```\n\n`Queued` only means the app-server accepted an item into its queue database; it\ndoes not mean that the agent has read, started, or completed the message. Verify\ndelivery by inspecting the target session's next turn and visible terminal\nstate, then read its output through `cmux vm terminal read`/`output` or the\nsession's normal UI. If the target is currently busy, the message waits for the\nsession's queue semantics rather than interrupting an in-flight turn.\nIf the target thread is not loaded, the message remains queued until you open or\nresume that thread.\n\nDo not copy `CODEX_HOME`, manually edit `queue_*.sqlite`, or treat a copied\ndatabase's `Queued` response as delivery evidence. Those operations enqueue for\nthe copy only and can race the app-server's writer. If the local control socket\nis inaccessible (for example, a cloud container may report `Operation not\npermitted` for `/home/\u003cuser>/.codex/app-server-control/app-server-control.sock`),\nuse cmux's terminal path instead:\n\n```bash\ncmux vm terminal send \u003cid> \u003cterm> 'Continue with the next step.' --keys enter\ncmux vm terminal read \u003cid> \u003cterm>\n```\n\nWhen a remote app-server endpoint is intentionally exposed, use the installed\nCLI's `--remote \u003cwss://…>` together with\n`--remote-auth-token-env \u003cENV_VAR>` and the endpoint's authenticated token. Do\nnot put an app-server token in a terminal command, prompt, or repository file.\nUse `ws://` only for loopback endpoints.\n\n## 3. Repo with history (private repos, no credentials on the machine)\n\n```bash\ngit bundle create /tmp/repo.bundle --all\ncmux vm push \u003cid> /tmp/repo.bundle work/repo.bundle\ncmux vm exec \u003cid> -- sh -c 'cd work && git clone repo.bundle app && cd app && git checkout main'\n```\n\nPublic repos can just clone on the machine: `cmux vm exec \u003cid> -- git clone https://github.com/org/repo work/repo`.\n\n## 4. Builds and tests in the cloud instead of the local Mac\n\n```bash\nt=$(cmux surface new-terminal --machine \u003cid> --no-open --json -- sh -c 'cd work/app && make test' | jq -r .terminal_id)\ncmux vm terminal wait-exit \u003cid> \"$t\" --timeout 900 # exited code=\u003cn> | exited signal=\u003cs> | pending (exit 1)\ncmux vm terminal output \u003cid> \"$t\" > test.log # everything the run printed, not just the visible screen\ncmux vm pull \u003cid> work/app/dist ./dist-from-cloud\n```\n\nA durable terminal outlives the CLI call and the Mac; `wait-exit` returns the exit code and `output` the full log (`--json` gives `next_offset`, so a long run can be read incrementally with `--after`). For a quick command that finishes in seconds, `cmux vm exec \u003cid> --timeout 300 -- \u003ccmd>` is enough. Report the real outcome from the exit code and the log — a finished wait is not a passed test.\n\n## 5. Parallel experiments with checkpoints and forks\n\n```bash\ncmux vm snapshot \u003cid> --name pre-experiment\nfork_a=$(cmux vm fork \u003cid> --name try-approach-a --detach --json | jq -r '.id')\nfork_b=$(cmux vm fork \u003cid> --name try-approach-b --detach --json | jq -r '.id')\ncmux vm agent --agent codex --machine \"$fork_a\" --no-open -- exec \"try approach A in work/app\"\ncmux vm agent --agent codex --machine \"$fork_b\" --no-open -- exec \"try approach B in work/app\"\ncmux vm tree # both agents, side by side\ncmux vm rm \"$fork_a\"; cmux vm rm \"$fork_b\" # only the forks you created\n```\n\n## 6. Desktop and browser tasks\n\nNew machines (`cmux vm new`) include TigerVNC with an openbox session and noVNC on 6901; shells get `DISPLAY=:1` while the desktop is up. Drive it from inside the machine (`vm agent` with a computer-use-capable agent — `cua-driver` is preinstalled) and show the human the screen. Historical shell-only machines still have no screen; inspect `vm status` before opening one:\n\n```bash\ncmux vm open \u003cid>:desktop # the screen as a browser pane beside the shell\ncmux vm exec \u003cid> -- sh -c 'DISPLAY=:1 xdotool key ctrl+l' # quick desktop pokes\n```\n\n## 6b. Publish a service for a person to open\n\nUse a domain publication when the result must be reachable over HTTPS outside the\nowner's private tunnel. Keep `vm open \u003cid>:port/\u003cn>` for private previews; it is\nnot a shareable public URL.\n\n```bash\n# 1. Confirm the VM and port, then create a generated cmux hostname.\ncmux vm status \u003cid>\ncmux vm ports \u003cid>\npublication=$(cmux cloud domains publish \u003cid> 3000 --access personal --json)\necho \"$publication\" | jq -r '.publication.url'\n\n# 2. For a custom zone, start/continue verification and follow every record shown.\ncmux cloud domains verify example.com\n# Add the ownership TXT, apex/wildcard routing, and _acme-challenge NS records,\n# wait for DNS propagation, then run the same verify command again.\ncmux cloud domains publish \u003cid> 3000 --domain app.example.com --access team --team \u003cteam-id>\n\n# 3. Check activation and hand off only the URL and intended policy.\ncmux cloud domains list --json | jq '.publications[] | {url,hostname,accessMode,state,verification}'\ncmux notify --title \"Cloud preview ready\" --body \"Open the URL from cmux cloud domains list\"\n```\n\n`personal` is the default and requires the publication owner to sign in; `team`\nrequires a current member of the selected team and a `--team` id; `public` permits\nanyone with the URL. `cmux cloud domains access` changes that policy and `rm`\nunpublishes it. A custom zone is verified once and can then serve its apex or a\nsingle-label child; a generated cmux hostname needs no customer DNS records. Do\nnot paste a public URL into logs or agent prompts unless the user intentionally\nchose `public`.\n\n## 7. Showing the human\n\n```bash\ncmux vm tree \u003cid> # the map: which terminal is which, what is already open\ncmux vm open \u003cid>/\u003cws>/\u003cterm> # one terminal as a pane (reuses an open pane)\ncmux vm open \u003cid> # shell (+ screen on desktop machines)\ncmux vm open \u003cid>:desktop # the screen\ncmux vm open \u003cid>:port/3000 # the app they should look at\ncmux vm handoff \u003cid> # attach block another human/agent can follow\n```\n\nPair with `cmux notify` so they know why a pane appeared. Prefer `--print`/`--detach`/`--no-open` until the moment you intend the user to look; open commands run from an agent never steal focus unless you pass `--focus`; the pane appears in the background marked unread.\n\n## 8. Cleanup etiquette\n\n- New cmux-created machines normally remain available until explicitly paused or stopped; older/provider-managed machines may sleep. Opening or running a command wakes a sleeper, so leaving one for the user to inspect is fine (say so in your handoff).\n- If you only opened an existing workspace, close your local view. `vm workspace close` changes the remote workspace: it closes the workspace but leaves its terminals running in the Terminals pool.\n- `vm terminal close` ends a terminal; `vm workspace rm` kills every terminal in the workspace. Limit these to resources created for this task, confirm the affected terminals all belong to it, and stay within the authorized cleanup. The same ownership rule applies to deleting scratch machines and forks.\n- Never `vm rm` or `vm base reset` a machine you didn't create without explicit user confirmation — `vm rm` deletes it permanently; `vm base reset` creates a new Base generation and retains the old machine.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":17822,"content_sha256":"90dfdd57d53033860de6e5f7157b0aa4b15f8ff80b167b924ab279fb226b442e"},{"filename":"references/commands.md","content":"# cmux Cloud CLI reference\n\nThis reference covers the cmux Cloud CLI in this source revision. `cmux cloud` is an alias for `cmux vm` (`cmux cloud ls` == `cmux vm ls`). `tests/test_cloud_vm_skill_coverage.py` checks command coverage against the CLI source.\n\nFind the needed section: [machines](#machines), [execution](#execution),\n[files](#files), [routing](#routing), [workspaces/terminals](#workspaces-and-terminals-the-machines-cmux-tui-session),\n[layouts](#layouts-the-shape-of-a-machine-workspace), [environment](#environment-project-secrets-and-settings-on-a-machine),\n[domains](#public-https-domains), [limits](#plan-meter-and-limits).\nGuest auth, peer access, notifications and browser opening are in [guest operations](guest.md).\nInstalled `cmux vm \u003cverb> --help` owns rollout availability; this reference documents current source.\n\n## Conventions\n\n- **Requires** the cmux app running on the Mac, a signed-in account (`cmux auth status`), and the WireGuard tunnel up (`cmux vpn up`) — machines live on a private per-user network with no public ports, so attach/exec/port verbs need it. Every verb talks to the app over its Unix socket (`CMUX_SOCKET_PATH` when set; the app's default socket otherwise) — the app, not the CLI, holds the cloud credentials. The guest `cmux self` and `cmux vm ls` commands are the exception: they run inside a machine through its edge-injected credential.\n- **`--json`** is a global flag: it may appear before or after the subcommand and prints the socket payload (or the CLI's own summary object, noted per verb) instead of text. Parse JSON, never the human tables.\n- **`--help` / `-h`** works offline (no app needed). `cmux vm --help` is the overview; `cmux vm run --help`, `route`, `agent`, `push`, `pull`, `wait`, `open`, `tree`, `workspace`, `terminal`, `tui`, `prompt`, `base`, and `domains` print that verb's own options (`cmux vm terminal --help` covers close, send, read, and wait). `resize` is documented in the overview because its compact validation usage is emitted inline. Anything after `--` is never treated as a help flag (`cmux vm exec \u003cid> -- --help` runs `--help` on the machine).\n- **Exit codes:** `0` success; `1` any error (socket missing, backend error, usage error, unknown `vm` verb); `2` missing or unknown top-level command. `cmux vm run` exits with the **remote command's exit code**; `cmux vm exec` prints `exit \u003cn>` to stderr and exits `1` when the remote command fails; `cmux vm wait` and `cmux vm terminal wait` exit `1` on timeout (or a failed machine).\n- **Ids:** a machine's generated name (`brave-otter`) is its id everywhere; the display label from `vm rename` is cosmetic. Workspace ids are `ws_…`, terminal ids `term_…` (both from `cmux vm tree`). `--window \u003cid|ref|index>` on the opening verbs picks the local window; `--workspace \u003cid|ref|index>` the local workspace.\n- **Env:** `CMUX_VM_API_BASE_URL` overrides the backend origin (dev stacks). `HOME` is honored for the router's state files (`~/.cmuxterm/vm-run-pool.json`, `~/.cmuxterm/vm-run-bindings.json`).\n\nFor a first run, use this read-only preflight before provisioning anything:\n\n```bash\ncmux auth status\ncmux vm ls --json\ncmux vm route --json\ncmux vm tree --json\n```\n\n### Command catalog\n\nThese are individual examples, not a sequence to run. VPN and workspace commands\nbelow can change state; choose only the operation needed for the task.\n\n```bash\ncmux auth status # signed in?\ncmux vm ls # NAME / LABEL / STATE / PROVIDER / IMAGE + plan meter (+ free-window countdown)\ncmux vm ls --json # {vms: [{id, status, image, createdAt, freeAccessExpiresAt, capabilities: {ports, …}}], limits: {maxActiveVms, planId, memoryOptionsMb, freeAccessWindowDays, freeAccessExpiresAt}}\ncmux vpn status # this build's WireGuard tunnel to its private machine network (machines open no public port): up, down, or up for another enrollment (stale)\ncmux vpn up # enroll this Mac and bring the tunnel up (sudo); a stale tunnel (rotated keys) is replaced. One tunnel per deployment (`cmux` for production, `cmux-staging`/`cmux-dev` for dev builds), so a dev build and the production app can both be up\ncmux vpn down # take this build's tunnel down (sudo)\ncmux self # INSIDE a machine: this machine's name, id, status, team (--json: {schema, machine, team, machines}); guest `cmux vm ls` lists the team's machines with this one marked *\ncmux vm tree # the surface catalog: This Mac (terminals by workspace, browsers), then every machine → Workspaces, Ports, VNC Displays, Terminals\ncmux vm tree \u003cid> --refresh # one machine (`local` for This Mac), re-synced first\ncmux vm workspace new \u003cid> [--name n] # a new cmux-tui workspace on the machine (⌘N there), opened as a new local workspace\ncmux vm workspace open \u003cid> \u003cws-id> # open a machine workspace as a NEW local workspace: one pane per terminal/browser (clicking its row)\ncmux vm workspace open \u003cid> \u003cws-id> --here [--workspace \u003clocal>] # into the current local workspace: one pane + the rest as tabs (drop a workspace row onto a pane)\ncmux vm workspace open \u003cid> \u003cws-id> --tabs [--pane \u003cp>] # all as tabs of the focused/--pane pane (CLI placement)\ncmux vm workspace open \u003cid> \u003cws-id> --pane \u003cp> --left|--right|--up|--down # what dropping the row on that pane edge does\ncmux vm workspace rename \u003cid> \u003cws-id> \u003cname> # rename that workspace (the row's \"Rename…\")\ncmux vm workspace close \u003cid> \u003cws-id> # CLI-only: close that workspace but keep its terminals running in the Terminals pool\ncmux vm workspace rm \u003cid> \u003cws-id> # close that workspace AND kill every terminal in it (the row's \"Close Workspace…\" / hover ×). Permanent.\ncmux vm terminal close \u003cid> \u003cterm-id> # end one terminal on the machine (the sidebar's ×); its local panes close too\ncmux vm terminal send \u003cid> \u003cterm-id> [text] [--keys enter,ctrl+c,…] # type into the terminal headlessly (as-is, no newline), then press named keys (chords join with +); no pane, no focus\ncmux vm terminal read \u003cid> \u003cterm-id> # the visible screen as text (--json: + rows, cols, cursor)\ncmux vm terminal wait \u003cid> \u003cterm-id> --pattern \u003cregex> [--timeout \u003cs>] # block until the screen matches (default 30 s); exit 1 on timeout\ncmux vm tree --json # {machines: [{id, local, name, status, link_state, …}], resources: [{id, machine, kind, key, title, detail, lifecycle, agent, remote_workspace, port, url, open, open_surface_ids}], projections: […]}\ncmux surface ls [--json] # same catalog; `surface open \u003cresource>` / `surface new-terminal --machine \u003cm>` / `surface new-display --machine \u003cm>` are the generic verbs\ncmux vm status \u003cid> # provider, status, image\ncmux vm stats \u003cid> # CPU/mem/disk now; sleeping machines stay asleep\ncmux vm clean \u003cid> # dry-run: what disk cleanup would free (removes nothing); --apply removes it\ncmux vm resize \u003cid> --disk 40G # grow persistent disk in 4 GiB steps (never shrinks)\ncmux vm network \u003cid> # outbound policy: mode, presets, domains, ranges, applied state\ncmux vm agent-updates \u003cid> # latest (updated on attach) or image (baked pins)\ncmux vm tools \u003cid> # which tools are installed\ncmux vm ports \u003cid> # listening TCP ports inside the machine\ncmux vm handoff \u003cid> # short attach block to paste to a human or another agent\n```\n\nOnly then choose `vm run`/`vm agent` (router-managed work), `vm base open`\n(the persistent personal machine), or an explicit `vm new`. Use `--json` for\nautomation and the human output when teaching a person what to click or copy.\n\n## Machines\n\n### `cmux vm ls`\n\n```bash\ncmux vm ls [--json] # alias: cmux vm list\n```\n\nSocket `vm.list`. Text: a `NAME LABEL STATE PROVIDER IMAGE` table, then the plan meter (`N of M machines on the \u003cplan> plan` when `limits.maxActiveVms` is set, `N machines on the \u003cplan> plan, no limit` when it is absent) and, on free plans, when free cloud access expires. Empty: `No cloud VMs. Try: cmux vm new`.\n`--json`: `{vms: [{id, displayName?, status, provider, image, kind?, capabilities?: {snapshot, fork}, resources?: {vcpus, memoryMb, diskMb?}, createdAt?, freeAccessExpiresAt?}], limits: {maxActiveVms, planId, memoryOptionsMb?, maxDiskMb?, maxMemoryMb?, maxVcpus?, poolVcpus?, poolMemoryMb?, usedVcpus?, usedMemoryMb?, freeAccessWindowDays?, freeAccessExpiresAt?}, imageKinds?}`. Sidebar: the Machines panel list.\n\n### Lifecycle and safety\n\nCreate a machine only after `vm ls` shows the available plan capacity. A bare\n`vm new` provisions the devbox with displays. The legacy `--base`,\n`--no-desktop`, and `--desktop` flags remain accepted but all select that same devbox. Use\n`--detach` for a headless create, then wait for readiness before the first\ncommand:\n\n```bash\ncmux vm ls --json\ncmux vm new --detach --name \"build box\" --size 8g --json\ncmux vm wait \u003cmachine> --wake\ncmux vm status \u003cmachine>\n```\n\nNamed sizes are `4g`, `8g`, `16g`, `24g`, `32g`, and `64g`; a raw memory value\nin MB is also parsed. Pro and Team support 4g, 8g, and 16g (2, 4, and 8 vCPU).\nMax additionally unlocks 24g and 32g (12 and 16 vCPU), plus 64g (32 vCPU).\nRead `vm ls --json` → `limits.memoryOptionsMb` for the current plan's choices.\nThe server uses its plan default when a parsed request is not allowed; the\nchosen image supplies the matching CPU and initial disk.\n`--name` changes the display label, never the generated machine id. Prefer a\nworkspace on an existing machine for another task; use `vm fork` for an\nisolated experiment. `vm rm` permanently deletes the machine and its volume,\nso only remove a machine created for the current task and ask before touching\nsomeone else's machine or Base.\n\n### `cmux vm new`\n\n```bash\ncmux vm new [--desktop|--base] [--size \u003c4g|8g|16g|24g|32g|64g|MB>] [--name \u003clabel>] [--provider \u003cp>] [--image \u003cid>] [--workspace \u003cid>] [--window \u003cid|ref|index>] [--focus|--no-focus] [--detach|-d] [--json]\n# alias: cmux vm create\n```\n\nSocket `vm.create` with `kind: desktop` for every new machine. The legacy `--base`/`--no-desktop` and `--desktop` flags all select the same devbox; contradictory flags are rejected. The backend selects the image from its manifest; `--image \u003cid>` is the explicit override and the only way an image id leaves the client. If the requested kind is not offered, the server fails closed with an image-config error rather than silently returning the wrong shape. `--size` accepts `4g`, `8g`, `16g`, `24g`, `32g`, `64g`, or raw MB ≥ 512. Pro and Team can create through 16g (8 vCPU); 24g and 32g (12 and 16 vCPU) and 64g (32 vCPU) require Max. `vm ls --json` → `limits.memoryOptionsMb` is authoritative for the current plan; the backend selects its default when a parsed request is unavailable, and the chosen image supplies the matching CPU and initial disk. `--name` applies a display label through `vm.rename` after the create. Positional arguments are rejected (`cmux vm new myvm` errors instead of provisioning). Retries of a failed create reuse an idempotency key so a transient failure never mints two machines.\nWithout `--detach`, opens a plain terminal on the machine (the same open path as `vm shell`); it switches to its workspace only when run interactively from a terminal (an agent or script gets it in the background, marked unread); `--focus` / `--no-focus` (also `--focus true|false`) override that, and `--no-focus` is what the New Machine sheet does — the app's Create returns control immediately and the pane appears in the background); desktop machines also get their screen in a split. Text output carries the stable `OK machine=\u003cid>` marker after the localized created line; `--detach` prints `\u003cid> is ready` and the follow-up commands. `--json`: the `vm.create` payload (`{id, provider, image, kind?, …}`) and no pane. Sidebar: Machines panel + / \"New Cloud Machine…\" sheet (name, size, plan meter). On a free or unknown plan the backend returns `vm_requires_pro` (exit 1); paid-plan machine caps come from the backend (`vm ls --json` → `limits.maxActiveVms`; absent means uncapped). The current CLI accepts `--provider freestyle`; omit it to let the server choose the configured default. If a deployment adds another provider, read that tagged app's `vm new --help` before using it.\n\n### `cmux vm rename`\n\n```bash\ncmux vm rename \u003cid> \u003clabel> [--json]\ncmux vm rename \u003cid> --clear [--json]\n```\n\nSets or clears the machine's display-only label through `vm.rename`; the generated machine id remains its address. The response is JSON when requested, otherwise it confirms the stored label.\n\n### `cmux vm rm`\n\n```bash\ncmux vm rm \u003cid> [--json]\n# aliases: cmux vm destroy \u003cid>, cmux vm delete \u003cid>\n```\n\nPermanently deletes the machine and its data through `vm.destroy`. Confirm the machine id before running this destructive command.\n\n### `cmux vm status`\n\n```bash\ncmux vm status \u003cid> [--json] # alias: cmux vm info\n```\n\nSocket `vm.status`. Text: `\u003cid> [\u003cprovider>] \u003cstatus>` and `image: \u003cimage>`. `--json`: `{id, provider, image, status, kind?, …}`. Sidebar: machine row › Status.\n\n### `cmux vm stats`\n\n```bash\ncmux vm stats \u003cid> [--json] # alias: cmux vm top\n```\n\nSocket `vm.stats`. CPU, memory, and disk right now; a sleeping machine reports `asleep` and is not woken. `--json`: `{id, state: awake|asleep, cpu_percent, cpus, memory_used_mb, memory_total_mb, disk_used_mb, disk_total_mb}`. The router uses this to pick the least-loaded pool machine.\n\n### `cmux vm clean`\n\n```bash\ncmux vm clean \u003cid> [--json] # dry-run: list what would be removed and how much space that frees\ncmux vm clean \u003cid> --apply [--json] # remove it\n```\n\nSocket `vm.hygiene {id, apply?}` calls `POST /api/vm/\u003cid>/hygiene`, which runs the machine-hygiene script on the machine. Without `--apply` it is a dry-run that deletes nothing. It reclaims only what a rebuild or re-download brings back: build output of projects idle for 7 days (`node_modules`, `target`, `.next`, …), package caches when the disk is at least 85% full, agent logs older than 14 days, idle temp files, and records of deleted git worktrees. It never touches source, git data, `~/.ssh`, credentials or agent sessions, and skips anything in use. Only the machine's owner can run it. A paused machine answers `vm_not_running` and is not woken, and a second `--apply` while one runs answers `vm_hygiene_busy`. `--json`: `{id, version, mode: dry-run|apply, home, diskUsedPctBefore, diskUsedPctAfter, reclaimableKb, items: [{category, path, kb, action: would-delete|deleted|skipped, reason?}]}`.\n\n### `cmux vm restart`\n\n```bash\ncmux vm restart \u003cid> [--json]\n```\n\nRequests hard recovery through a provider control-plane operation that discards a frozen guest's memory and boots its existing disk. The server currently rejects this action when the provider does not advertise a verified restart capability; the Freestyle API in this release exposes pause/start only, and pause/start resumes the same memory rather than recovering a frozen guest. The machine's disk and identity are preserved when a provider advertises the operation.\n\nSocket `vm.restart` is the same capability-gated recovery action used by the macOS machine menu and iOS Cloud controls. It does not run a guest command or infer guest health from provider-running state.\n\n### `cmux vm resize`\n\n```bash\ncmux vm resize \u003cid> [--cpu \u003c1|2|…|32>] [--memory \u003c4|5|…|64>G] [--disk \u003c4|8|…|256>G] [--json]\n```\n\nGrows CPU, memory, and/or persistent disk on the existing machine. CPU accepts\n1–32 vCPUs; memory accepts 4–64 GiB in whole-GiB steps; disk accepts 4–256 GiB\nin 4 GiB steps. Supply at least one dimension. Omitted dimensions stay unchanged,\nand every requested dimension must be at least its current size. `vm ls --json`\npublishes the current plan's `maxDiskMb`, `maxMemoryMb`, and `maxVcpus`; the CLI\nchecks those ceilings before sending the resize and the server checks them again.\nPro and Team stop at 16 GiB / 8 vCPU. Max permits the 24 GiB / 12 vCPU and\n32 GiB / 16 vCPU rows and reaches 64 GiB / 32 vCPU. The shared CPU and memory\npool is reserved transactionally for every growth request.\n\nSocket `vm.resize` accepts `{id, cpu?, memory_mb?, storage_mb?}` and returns the\nprovider-confirmed `VMStats` object, including `cpus`, `memory_total_mb`, and\n`disk_total_mb`. Text is `OK \u003cid> cpu=\u003cn> memory=\u003cn> GiB disk=\u003cn> GiB`; `--json`\nreturns the stats object. A resize can take a provider minute and consumes plan\nresources, so confirm the machine and desired sizes before running it, then use\n`cmux vm stats \u003cid>` to verify the result. Sidebar: machine row › Resize machine\n› Increase CPU / Increase Memory / Increase Disk uses the same action path.\n\n### `cmux vm network`\n\n```bash\ncmux vm network \u003cid> [--json] # show the policy and preset ids\ncmux vm network \u003cid> set --mode \u003cfull|allowlist|none> [--dns \u003con|off>]\ncmux vm network \u003cid> preset add anthropic openai # quick-add exact HTTPS hosts\ncmux vm network \u003cid> add-domain api.example.com # exact HTTPS name, no wildcards\ncmux vm network \u003cid> add-range 203.0.113.0/24 --port 443 --protocol tcp\ncmux vm network \u003cid> remove-domain api.example.com\ncmux vm new --network allowlist # or --network-policy '\u003cjson>' to choose at create\n```\n\nControls where the machine can connect out. `full` (the default) reaches any\npublic address. `allowlist` reaches only listed domains and IP ranges; `none`\nreaches only the hosts cmux needs (`files.cmux.com` and the GitHub release hosts).\nModel traffic through CodeRouter keeps working in every mode. Changes apply to a\nrunning machine in a few seconds with no restart, and the text result ends with\n`applied=applied` once the provider confirmed them.\n\nDomains are exact names on HTTPS/443, steered through the machine's\n`/etc/hosts`; tools that bypass `/etc/hosts` (a container with its own DNS) or pin\ncertificates are not steered, so give them an IP range instead. `--dns on` lets\ntools resolve names for IP ranges but is itself an outbound channel. Before\nrestricting a machine an agent is using, add the hosts its task needs (package\nregistries, the git remote, model providers), then check with\n`cmux vm exec \u003cid> -- curl -sS -o /dev/null -w '%{http_code}' https://\u003chost>`.\nSocket methods `vm.network_get {id}` and `vm.network_update {id, edits}`; the\nmachine row's Network… menu, the web dashboard, and `PUT /api/vm/\u003cid>/network`\nuse the same policy.\n\n### `cmux vm agent-updates`\n\n```bash\ncmux vm agent-updates \u003cid> [--json] # show: latest or image\ncmux vm agent-updates \u003cid> latest # update coding agents on attach, at most once a day\ncmux vm agent-updates \u003cid> image # keep the image's versions (the default)\ncmux vm new --agent-updates latest # choose at create\n```\n\n`latest` makes each attach start a detached updater on the machine that installs\nthe newest Claude Code, Codex, OpenCode, Pi, and agent-browser releases that have\nbeen public for 3 days, at most once a day; attach never waits for it. Each comes\nfrom the tool's own GitHub release asset, checked against its sha256 digest; npm\nis never used, and a machine baked with npm installs migrates on its first\nupdate. The New Machine sheet checks it by default. Switching back to `image`\nstops further updates but does not downgrade what is installed. Updates reach\nonly `api.github.com`, `github.com` and GitHub's release-asset host, which every\n`cmux vm network` mode allows; a failed check (for example a GitHub rate limit)\nis retried on the next attach.\nOn the machine, `/etc/cmux/agent-updates.state` records the last check and\n`/var/log/cmux-agent-updates.log` its output. Socket methods\n`vm.agent_updates_get {id}` and `vm.agent_updates_set {id, agent_updates}`; the\nmachine row's Keep Agents Up to Date menu item and\n`PUT /api/vm/\u003cid>/agent-updates` use the same setting.\n\n### `cmux vm wait`\n\n```bash\ncmux vm wait \u003cid> [--timeout \u003cseconds>] [--wake] [--json]\n```\n\nPolls `vm.status` until the machine reports a ready status (`running`, `ready`, `standby`, `paused` — the same set the Machines panel calls ready); `--wake` then runs a trivial `vm.exec` so a sleeper is awake on return. Default timeout 180 s; exit 1 on timeout or a failed state. `--json`: the last status payload plus `{ok: true, waited_seconds, woke}`. Use this instead of polling `vm status` yourself.\n\n### `cmux vm tools`\n\n```bash\ncmux vm tools \u003cid> [--json] # alias: cmux vm tool-inspector\n```\n\nA `vm.exec` probe: shell, and whether `zsh git gh htop btop node bun python3` are installed. `--json`: the exec payload `{stdout, stderr, exit_code}`.\n\n### `cmux vm handoff`\n\n```bash\ncmux vm handoff \u003cid> [--json]\n```\n\nSocket `vm.status`, printed as a short block (id, provider, status, `attach: cmux vm shell \u003cid>`, `inspect: cmux vm tools \u003cid>`) to paste to a person or another agent. `--json`: the status payload.\n\n### Base: `cmux vm base open` / `cmux vm base reset`\n\n```bash\ncmux vm base [open] [--desktop|--base] [--workspace \u003cworkspace-id>] [--window \u003cid|ref|index>] [--focus|--no-focus] [--detach|-d] [--json]\ncmux vm base reset [--desktop|--base] [--reason \u003ctext>] [--workspace \u003cworkspace-id>] [--window \u003cid|ref|index>] [--detach|-d] [--json]\n```\n\nBase is the one pinned persistent machine per user. `open` (`vm.base_open`) reuses the same VM every time, creating it on first use with the devbox and displays (legacy kind flags are accepted without changing that choice); an existing Base keeps its image. `reset` (`vm.base_reset`) mints a new Base generation and retains the previous VM so an accidental reset is recoverable. Both open a plain terminal unless `--detach`; text `OK \u003cid>`, `--json` the payload. Sidebar: Open Base / Set Up Base sheet.\n\n## Public HTTPS domains\n\n`cmux cloud` is an alias for `cmux vm`, so every command below also works with\n`cmux vm domains`. A domain publication is the public-URL path; it is distinct\nfrom `cmux vm open \u003cid> \u003cport>`, whose URL is private to the owner's WireGuard\ntunnel.\n\n### `cmux cloud domains`\n\n```bash\ncmux cloud domains --help\ncmux cloud domains [list] [--json]\ncmux cloud domains zones [--json]\ncmux cloud domains verify \u003cdomain> [--json]\ncmux cloud domains publish \u003cvm> \u003cport> [--domain \u003chostname>] [--access personal|team|public] [--team \u003cid>] [--json]\ncmux cloud domains access \u003chostname> \u003cpersonal|team|public> [--team \u003cid>] [--json]\ncmux cloud domains rm \u003chostname> [--json]\n```\n\nAll domain commands require the cmux app and a signed-in account. `list` (the\ndefault) calls `vm.publication_list` and prints each publication's HTTPS URL,\nVM/port, access mode, lifecycle state, routing revision, and verification state.\n`--json` returns a stable `{publications: [...]}` object. `zones` calls\n`vm.domain_list` and lists the custom zones owned by the account separately from\ntheir publications; `--json` returns `{domains: [...]}`.\n\n`publish` maps one VM port to one hostname. Omitting `--domain` asks cmux to\nreserve a generated cmux hostname; it needs no customer DNS proof. Supplying a\ncustom hostname requires that its base zone has been verified first (the zone\nitself or one immediate child is accepted). `port` must be 1–65535. Access\ndefaults to `personal`; `team` requires `--team \u003cid>` and checks current team\nmembership; `public` allows anyone who has the URL. The command returns the\npublication object, including `verification.dnsInstructions` when setup is not\ncomplete.\n\n`verify` is a zone-level operation, not a publication activation switch. The\nfirst call creates or retrieves the pending ownership challenge and prints a\nlabelled DNS checklist. Add every record exactly as printed, wait for DNS and\ncertificate propagation, then run the same command again. The checklist normally\ncontains: an ownership TXT record, an apex routing alias/CNAME-flattening record,\nthe wildcard routing CNAME, and `_acme-challenge` NS delegation. A publication\nhostname or publication id resolves to its owning zone; a generated cmux name\nhas nothing to verify and is rejected. A verified zone can serve the apex or one\nlabel (`example.com` or `app.example.com`); deeper names need another covering\nzone.\n\n`access` changes an existing publication's policy in place. Its first argument\nis the publication hostname (or id), not a VM id; pass `--team` only with\n`team`. `rm` permanently unpublishes the hostname and removes its provider\nroute. Treat `public` URLs as bearer credentials: do not put them in logs,\ncommits, or unattended prompts. Policy changes take effect on subsequent\nrequests; they do not create per-viewer grants.\n\n## Files\n\n### `cmux vm push`\n\n```bash\ncmux vm push \u003cid> \u003clocal-path> [remote-path] [--exclude \u003cpattern>]... [--no-default-excludes] [--json]\n# alias: cmux vm upload\n```\n\nThe CLI calls `vm.scp_info` with a locally generated Ed25519 public key, then transfers bytes with OpenSSH/SFTP through the app's userspace WireGuard hub. Each grant request uses a fresh authenticated control connection. The guest host key comes from the authenticated backend response and is pinned for SSH. File content never enters exec commands. SHA-256 is checked before finalization; files preserve modes and directories merge into the destination. `.git`, `node_modules`, `.venv`, `__pycache__`, and `.DS_Store` are skipped unless `--no-default-excludes`; `--exclude` adds patterns. Relative paths use the `cmux` guest home. The 256 MiB limit remains. `vm.file_transfer_failure` sends only a phase, bounded failure category, and optional subprocess exit code to the signed-in app, which returns a copyable diagnostic reference. Text output gives a summary; `--json` returns `{ok, direction: \"push\", vm, local, remote, kind: file|directory, bytes, sha256, seconds, excluded?}`.\n\n### Watch and secret files\n\n```sh\ncmux vm push \u003cid> ./site work/site --watch --interval 1\ncmux vm push --secret \u003cid> ./deploy_key .ssh/deploy_key --mode 600\n```\n\n`--watch` reuses the normal transfer and exclude rules when local files change;\nremote-only files remain. Ctrl-C stops watching. `--secret` sends one file (up to\n256 KiB) over the cmux-tui link to a receiver that disables echo, writes with the\nchosen mode and atomically moves it into place. It bypasses exec and refuses\ndirectories, `--watch` and excludes. Use it for an authorized key or token file.\nRemote-relative paths resolve under the work user's home. Keep values out of argv\nand logs; use `vm env set --from-file` or stdin for environment values.\n\n### `cmux vm pull`\n\n```bash\ncmux vm pull \u003cid> \u003cremote-path> [local-path] [--json]\n# alias: cmux vm download\n```\n\nThe reverse: file or directory back to local disk (defaults to the remote basename in the current directory). `--json`: `{ok, direction: \"pull\", vm, remote, local, kind, bytes, sha256, seconds}`.\n\n## Execution\n\n### `cmux vm exec`\n\n```bash\ncmux vm exec \u003cid> [--timeout \u003cseconds>] [--json] -- \u003ccommand...>\n```\n\nSocket `vm.exec {id, command, timeout_ms}`. Each argv element is shell-quoted, then joined, so `-- printf '%s\\n' \"a b\"` means what it says; wrap shell constructs as `-- sh -c '\u003cscript>'`. No TTY or stdin. The default timeout is 30 seconds; `--timeout` accepts up to 900 seconds and the client waits ten seconds longer for the response. Use a durable terminal or agent for work that must survive the call. stdout and stderr pass through; a non-zero remote exit prints `exit \u003cn>` and exits 1. `--json`: `{stdout, stderr, exit_code}` (still exit 1 when `exit_code != 0`). Sidebar: none (a person types into a pane).\n\n### `cmux vm run`\n\n```bash\ncmux vm run [--sync] [--pull \u003cremote-path>] [--machine \u003cid>] [--new] [--size \u003c4g|8g|16g|24g|32g|64g|MB>] [--timeout \u003cseconds>] [--json] -- \u003ccommand...>\n```\n\nRuns a command on a cloud machine **without naming one**: sticky binding for the caller's directory (`~/.cmuxterm/vm-run-bindings.json`, 14-day TTL) → idle awake pool machine, least-loaded by `vm.stats` → sleeping pool machine (exec wakes it) → provision a fresh pool machine (`vm.create {kind: base}`; the current manifest maps both kinds to the devbox with displays, labeled `agent-pool` via `vm.rename`, recorded in `~/.cmuxterm/vm-run-pool.json` under a cross-process `flock`, waited to ready) → at the plan cap, the least-loaded busy pool machine. Only machines the router itself provisioned are drafted; `--machine \u003cid>` pins any machine, `--new` forces a fresh pool machine, `--size` applies to a machine this run creates. `--sync` pushes the current directory to `work/\u003cbasename>` first and runs there; `--pull \u003cremote>` fetches that path back afterwards. `--timeout` default 600 s, max 15 minutes.\nThe routing decision goes to **stderr** (`[cmux vm run] \u003cid> (\u003creason>)`); stdout is the command's own stdout; the remote **exit code passes through**. `--json`: `{ok, machine, created, exit_code, stdout, stderr, seconds, synced_to?, pulled_to?}`. Socket calls: `vm.list`, `vm.stats`, `vm.exec`, and on provision `vm.create`, `vm.rename`, `vm.status`.\n\n### `vm dev`: a folder to a running dev layout in one verb\n\n```bash\ncmux vm dev \u003cid> # check machine + optional sync + detect command/port + layout apply --name + open\ncmux vm dev \u003cid> ~/src/app --name app --port 3000 # explicit folder, workspace name, and port\ncmux vm dev \u003cid> --command \"make serve\" --no-open # override command; stage without opening a local pane\ncmux vm dev \u003cid> --layout dev.json --no-sync # custom layout; skip the push\ncmux vm dev \u003cid> --dry-run --json # print the plan and JSON; zero socket traffic\ncmux vm dev \u003cid> --sync # force the folder push; --no-sync forces a machine-side checkout\n```\n\nDetection: `--command` wins; otherwise `package.json` (lockfile selects bun, pnpm, yarn, or npm; `dev` then `start`), `Cargo.toml` → `cargo run`, `go.mod` → `go run .`, `Makefile` with `dev:` → `make dev`, `manage.py` → `python manage.py runserver 0.0.0.0:8000`, `uv.lock` → `uv sync`, `pyproject.toml` → `pip install -e .`, `requirements.txt` → `pip install -r requirements.txt`, or `index.html` → `python3 -m http.server 8000`; otherwise the layout is shell-only. The port comes from `--port`, then `-p`/`--port`/`PORT=` in the script, then a framework default (Next/Nuxt/react-scripts 3000; Vite/SvelteKit/Remix 5173; Astro 4321; Angular 4200; Expo 8081; Wrangler 8787). The built-in layout is a horizontal split (0.62): the dev command on the left, a focused shell on the right, and a browser tab on `http://localhost:\u003cport>` when a port is known. `vm dev` creates or reuses the workspace through `vm layout apply --name`; if it already has live terminals, the layout is kept and a second dev server is not started. `--no-open` prints the workspace-open command. The `next:` lines are the `terminal output` / `terminal send` commands for the dev terminal.\n\n## Routing\n\n### `cmux vm route`\n\n```bash\ncmux vm route [--cwd \u003cdir>] [--new] [--provision] [--size \u003c4g|8g|16g|24g|32g|64g|MB>] [--json]\n```\n\nPrints the machine `vm run` / `vm agent` would use for a directory and why, without running anything (same policy, same `vm.list` + `vm.stats` calls). Text: `machine=\u003cid> created=\u003cbool>` and `reason: …`; when the pool is empty or busy it prints that `cmux vm run` would provision and stops — unless `--provision`, which creates the machine now. `--json`: `{machine (null when it would provision), created, reason, would_provision, directory}`. Exit 0 in every routed case.\n\n### `cmux vm agent`\n\n```bash\ncmux vm agent --agent \u003cclaude|codex|opencode|pi> [--machine \u003cid>] [--sync] [--cwd \u003cdir>] [--name \u003cname>] [--no-open] [--remote-workspace \u003cws>] [--wait [--output] [--timeout \u003cseconds>]] [--new] [--size \u003cs>] [--json] -- \u003cprompt or args...>\n```\n\nStarts a coding agent on a cloud machine chosen like `vm run` (or pinned with `--machine`) as a **detached terminal in the machine's cmux-tui session**: `surface.new_terminal {machine, command, name, open}`, where the command is a login shell that starts in the remote `$HOME` and puts `$HOME/.npm-global/bin`, `$HOME/.bun/bin`, and `$HOME/.local/bin` first. The daemon supplies its own home; the CLI does not send a hard-coded `cwd`. A bare prompt uses the agent's one-shot form (`claude -p`, `codex exec`, `opencode run`, `pi -p`); args that start with a flag or a known subcommand (`codex exec …`, `claude --resume …`) pass through verbatim. `--sync` pushes `--cwd` (default: the current directory) to `work/\u003cbasename>` first and starts the agent there; `--name` sets the terminal's name in the tree (default `\u003cagent>: \u003cprompt…>`); `--no-open` starts it without a pane. The command returns as soon as the terminal starts.\nText: `Started \u003cagent> on \u003cmachine> — terminal \u003cterm> in workspace \u003cws> …`, `Reattach: cmux vm open \u003cmachine>/\u003cws>/\u003cterm>`, and `OK surface=… terminal=… workspace=…` when a pane opened. `--json`: `{ok, machine, created, reason, agent, command, name, terminal_id, workspace_id, cwd, reattach, surface_id?}`. Credentials: the agent authenticates inside the machine the way it would locally (its own login under the remote `$HOME`, or `cmux ai-accounts upload` for the team's subrouter).\n\n`--remote-workspace \u003cws>` places the terminal in an existing machine workspace.\n`--wait` blocks until the process exits; `--output` then reads its full scrollback.\nThe exit code passes through (1 on timeout/signal); Ctrl-C ends the wait, not the\nremote agent. With waiting, launch diagnostics go to stderr and stdout carries\nthe result. `--json` emits one final object. Without `--wait`, launch returns immediately.\n\n## Workspaces and terminals (the machine's cmux-tui session)\n\nEvery machine runs the cmux-tui remote daemon: its own workspaces (`ws_…`) → terminals (`term_…`). Terminals keep running detached; panes on the Mac merely project them.\n\n### `cmux vm tab rename` and `cmux vm terminal rename`\n\n```bash\ncmux vm tab rename \u003cmachine> \u003ctab-id> \u003cname>\ncmux vm terminal rename \u003cmachine> \u003cterminal-id> \u003cname>\n```\n\n`vm.tab_rename` changes one exact tab placement; `vm.terminal_rename` fans a\nlabel change out to every placement of that terminal. Pass an empty quoted name\n(`\"\"`) to clear the custom label. These are the shared rename paths used by\nthe sidebar and preserve the distinction between a tab and a terminal identity.\n\n### `cmux vm tree`\n\n```bash\ncmux vm tree [\u003cmachine>|local] [--refresh] [--json]\n```\n\nSocket `surface.catalog {machine?, refresh?}` (plus `workspace.list` to name local workspaces). The Finder-style view of every surface: **This Mac** first (terminals grouped by workspace, then browsers), then each cloud machine — its **Workspaces**, **Ports**, **VNC Displays**, and final **Terminals** index. Workspace rows carry each terminal's title, cwd, lifecycle, agent state, and existing pane projections; every line carries an address `cmux vm open` or `cmux surface open` accepts. `--refresh` re-syncs every provider first. `--json`: `{machines: [{id, local, name, status, image, has_desktop, memory_mb, disk_mb, link_state, link_error, cpu_percent, memory_used_mb, disk_used_mb}], resources: [{id, machine, kind: terminal|display|browser, key, title, detail, lifecycle, agent, remote_workspace, port, url, open, open_surface_ids, open_workspace_ids}], projections: [{resource, workspace_id, surface_id}]}`. Same as `cmux surface ls`. Sidebar: the Cloud tree itself; machine row › Refresh.\n\n```\nvivid-newt running · 20 GB · 200 GB disk · link connected\n workspaces/ ← one machine, many workspaces: what you open and drag\n main ws_3c1… * (cmux vm open vivid-newt/ws_3c1…)\n ● term_2f9… bun test ~/work/app [agent claude running] (open: surface:4)\n ○ term_88a… bash ← exited\n tests ws_9ab… (cmux vm open vivid-newt/ws_9ab…) ← a second workspace on the same machine\n ports/\n 3000 http (cmux vm open vivid-newt:port/3000)\n VNC Displays/\n ● display:1 Desktop noVNC (cmux surface open vivid-newt/display/display:1)\n terminals/ ← every terminal resource the machine owns\n ● term_2f9… bun test ~/work/app ← shown in a workspace\n (detached — no tab on the machine shows these)\n ● term_c04… sleep 1000\n```\n\nThe sidebar shows the same tree in the same order: the machine's **Workspaces**\ngroup first (always its own row, with a + that is `vm workspace new`), then\n**Ports**, **VNC Displays**, and the final **Terminals** index (attached rows\nplus a greyed detached subgroup). Every sidebar verb has a CLI verb — see\n[sidebar-parity.md](sidebar-parity.md). `\u003cmachine>/\u003cworkspace>` addresses take\nthe `ws_…` id, or the workspace name only when exactly one workspace has it\n(colliding names need the id); an empty workspace still resolves, and `vm open`\nstarts a shell in it.\n\n### `cmux vm workspace new`\n\n```bash\ncmux vm workspace new \u003cmachine> [--name \u003cname>] [--reuse] [--no-open] [--json]\n```\n\nSocket `vm.workspace_new`: creates a workspace on the machine (its ⌘N, with a first\nterminal) and normally opens a local view. `--no-open` stages it headlessly;\n`--reuse` reuses the named workspace. A starter terminal means this is not an\nempty target for `layout apply`; use `--name` or `vm dev` for layout creation.\nText `OK workspace=\u003clocal id> remote_workspace=\u003cws id> machine=\u003cid>`.\nSidebar: machine row › New Workspace; Workspaces +.\n\n### `cmux vm workspace open`\n\n```bash\ncmux vm workspace open \u003cmachine> \u003cworkspace-id> [--here] [--tabs] [--workspace \u003clocal>] [--pane \u003cid|ref> [--left|--right|--up|--down]] [--json]\n```\n\nSocket `vm.workspace_open`: the machine workspace's terminals and browsers as a **new local workspace**, one pane each (what clicking the row does). `--here` projects them into the current (or `--workspace`) local workspace instead — one pane at the destination, the rest as tabs (\"Open All Here\"); `--tabs` makes all of them tabs of the focused (or `--pane`) pane; `--pane \u003cp>` + a side splits that pane on that side (dropping the row on a pane edge). Text `OK workspace=\u003clocal> opened=\u003cn> machine=\u003cid> [here]`. `\u003cworkspace-id>` is the `ws_…` id or an unambiguous workspace name, resolved exactly like the sidebar row; the payload's `remote_workspace_id` is the resolved id. An existing workspace with nothing in it opens nothing and answers `Nothing to open: …` with a `cmux vm open \u003cmachine>/\u003cws>` hint (that verb starts a terminal there). Also `cmux vm open \u003cmachine>/\u003cws>` for the workspace's focused terminal only.\n\n### `cmux vm workspace rename`\n\n```bash\ncmux vm workspace rename \u003cmachine> \u003cworkspace-id> \u003cname> [--json]\n```\n\nSocket `vm.workspace_rename`. Sidebar: workspace row › Rename….\n\n### `cmux vm workspace close`\n\n```bash\ncmux vm workspace close \u003cmachine> \u003cworkspace-id> [--json]\n```\n\nSocket `vm.workspace_close`: closes the workspace; its terminals **keep running** in the machine's Terminals pool (plain rows there). CLI-only — the sidebar's single \"Close Workspace…\" is the full close (`vm workspace rm`).\n\n### `cmux vm workspace rm`\n\n```bash\ncmux vm workspace rm \u003cmachine> \u003cworkspace-id> [--json] # alias: cmux vm workspace delete\n```\n\nSocket `vm.workspace_delete`: kills every terminal viewed in the workspace, then closes it. Permanent. Text `OK deleted workspace \u003cws> on \u003cmachine> (\u003cn> terminals closed)`. Sidebar: workspace row › Close Workspace… and its hover × (confirms only when there is something to kill).\n\n### `cmux vm terminal close`\n\n```bash\ncmux vm terminal close \u003cmachine> \u003cterminal-id> [--json]\n```\n\nSocket `vm.terminal_close`: ends a terminal on the machine (the process and its tab); every local pane showing it closes too. Sidebar: terminal row › Close Terminal / hover ×.\n\n### `cmux vm terminal send`\n\n```bash\ncmux vm terminal send \u003cmachine> \u003cterminal-id> [text] [--keys \u003ck1,k2,…>] [--json] # alias: cmux vm terminal write\ncmux vm terminal send \u003cmachine> \u003cterminal-id> -- 'text that starts with --keys'\n```\n\nSocket `vm.terminal_write {id, terminal_id, text?, keys?}` (cmux-tui `terminal \u003cid> write` / `keys`): types `text` into the machine terminal exactly as given (no newline), then presses the named keys — `enter`, `tab`, `escape`, `up`, `down`, …; chords join with `+` (`ctrl+c`). `--keys enter` alone presses Enter; give text and/or `--keys`. Headless: no pane is attached or focused, and every pane already projecting the terminal shows the input. Put `--` before text that contains this command's own flags. Text `OK sent \u003cn> chars [+ keys …] to \u003cterm> on \u003cmachine>`; `--json`: the payload (`{wrote, …}`). Sidebar: none by design (a person types into the pane).\n\n### `cmux vm terminal read`\n\n```bash\ncmux vm terminal read \u003cmachine> \u003cterminal-id> [--json] # alias: cmux vm terminal screen\n```\n\nSocket `vm.terminal_read {id, terminal_id}` (cmux-tui `terminal \u003cid> screen read`): the terminal's visible screen as text — what a person at that terminal sees. `--json`: `{text, rows, cols, cursor_row, cursor_col, cursor_visible}`.\n\n### `cmux vm terminal wait`\n\n```bash\ncmux vm terminal wait \u003cmachine> \u003cterminal-id> --pattern \u003cregex> [--timeout \u003cseconds>] [--json]\n```\n\nSocket `vm.terminal_wait {id, terminal_id, pattern, timeout_ms}` (cmux-tui `terminal \u003cid> screen wait`): blocks until the screen text matches the regex. `--timeout` is seconds (default 30, 0.001–3600; out of range is an error). Text `OK matched /\u003cpattern>/ on \u003cterm>`; `--json`: `{matched, text, …}`. Exit 1 with the screen tail on timeout.\n\nThe headless loop for any interactive program on a machine (a REPL, a TUI, a long test run, another agent's session): `cmux surface new-terminal --machine \u003cm> --no-open -- \u003ccmd>` (or `vm agent --no-open`), then `terminal send … --keys enter`, `terminal wait … --pattern '…'`, `terminal read …`. Open a pane for the person only when there is something to show.\n\n### Process exit and full output\n\n```sh\ncmux vm terminal wait-exit \u003cid> \u003cterm> --timeout 900\ncmux vm terminal output \u003cid> \u003cterm> --after \u003coffset> --max-bytes \u003cn> --json\n```\n\n`wait-exit` observes process completion (`exited code=\u003cn>`, `exited signal=\u003cs>`, or\n`pending`, which exits 1). `output` returns scrollback beyond the visible screen;\nresume with the returned `next_offset`. A screen-pattern match from `terminal wait`\nis a separate condition and does not prove process exit or test success.\n\n### `cmux vm prompt`\n\n```bash\ncmux vm prompt [--json] # alias: cmux vm skill\ncmux vm prompt --open \u003cclaude|codex|opencode>\n```\n\nBootstraps an agent that has **no skill loaded**: `vm.cloud_prompt` installs the app-bundled cmux-cloud skill file at `~/.config/cmux/skills/cmux-cloud.md` and prints the kickoff prompt that points any agent at it (the skill path goes to stderr; `--json`: `{prompt, skill_path}`). `--open \u003cagent>` (`vm.cloud_agent_open`) opens a local terminal running that agent with the prompt (`OK opened \u003cagent> … (terminal=\u003csurface>)`; `--json`: `{surface_id|terminal_id, …}`). Sidebar: control bar › Copy Cloud Prompt / Open Cloud Agent.\n\n## Layouts (the shape of a machine workspace)\n\n```bash\ncmux vm layout export \u003cid> [\u003cws-id|name>] [--raw] [--json] # {\"name\",\"cwd\",\"layout\": Node}; default: the focused workspace; --raw: the daemon LayoutDocument (pane/tab ids, split ids)\ncmux vm layout apply \u003cid> \u003cfile>|- [--name \u003cn>] [--cwd \u003cdir>] [--open] [--json] # build a NEW workspace from the document; --open shows it here with the same geometry\ncmux vm layout apply \u003cid> \u003cfile> --workspace \u003cws-id> # into an already-empty workspace; a non-empty one is refused. `vm workspace new --no-open` creates a starter shell, so prefer `--name` or `vm dev`.\ncmux vm layout apply \u003cid> --from-saved \u003cname> [--open] # a Mac saved layout (`cmux layout save \u003cname>`), applied in the cloud\n```\n\nDocument (identical to `cmux new-workspace --layout`, `cmux layout get`, cmux.json workspaces):\n\n```json\n{\"name\": \"app\", \"cwd\": \"work/app\",\n \"layout\": {\"direction\": \"horizontal\", \"split\": 0.6, \"children\": [\n {\"pane\": {\"surfaces\": [{\"type\": \"terminal\", \"name\": \"agent\", \"command\": \"claude\"}]}},\n {\"direction\": \"vertical\", \"split\": 0.5, \"children\": [\n {\"pane\": {\"surfaces\": [{\"type\": \"terminal\", \"name\": \"tests\", \"command\": \"bun test --watch\"},\n {\"type\": \"terminal\", \"name\": \"logs\", \"cwd\": \"logs\"}]}},\n {\"pane\": {\"surfaces\": [{\"type\": \"browser\", \"url\": \"http://localhost:3000\"}]}}]}]}}\n```\n\n- Wrappers accepted: the bare `layout` node, `{\"name\",\"cwd\",\"env\",\"layout\"}`, or a saved layout `{\"name\",\"description\",\"workspace\":{…}}`.\n- `horizontal` = side by side (first child left), `vertical` = stacked (first child top); `split` = the first child's share, 0.1–0.9 (default 0.5).\n- Surface: `type` terminal|browser (`project` is Mac-only and skipped with a warning), `name` (tab name), `cwd` (relative to the document `cwd`, default: the work user's home), `env` (process environment of that shell), `command` (typed into the shell, then Enter — the shell survives it), `url` (browser), `focus`.\n- Every terminal is a login shell (`bash -l`), so `vm env` values and the agents' PATH apply. Output: `OK workspace=ws_… name=… panes=N surfaces=M` or `--json` `{workspace_id, workspace_name, panes:[{pane_id, surfaces:[{type, terminal_id|browser_id, tab_id, name}]}], warnings}`.\n- The same verb exists inside the machine (`cmux layout export|apply`) and toward linked peers (`cmux vm layout … \u003cpeer>`); the Mac form runs that implementation over the exec channel. A machine whose shim predates it says so (reconnect: `cmux vm tree \u003cid> --refresh`).\n- Exit codes: 0 built; 1 daemon refused (message names the op); 2 invalid document (message names the JSON path, e.g. `$.children[1]`) — nothing is created on a 2.\n\n## Environment (project secrets and settings on a machine)\n\n```bash\ncmux vm env set \u003cid> KEY=VALUE [KEY2=VALUE2 …] # ~/.config/cmux/env (0600) in the work user's home on the persistent volume\ncmux vm env set \u003cid> --from-file .env # dotenv rules: blank and # lines skipped, optional `export `, matching quotes stripped\ncmux vm env set \u003cid> - # KEY=VALUE lines on stdin (preferred for scripts: nothing in argv)\ncmux vm env ls \u003cid> [--show] [--json] # names; --show adds values; --json {path, keys, values?}\ncmux vm env rm \u003cid> KEY [KEY2 …]\n```\n\nValues are sourced by every login/interactive shell on the machine (a one-line hook in `~/.profile` and `~/.bashrc`, installed on first `set`), so every terminal cmux starts (`vm open`, `surface new-terminal`, `vm agent`, layout panes), `vm exec`, and the in-VM `cmux agent …` see them. Keys must match `[A-Za-z_][A-Za-z0-9_]*`.\n\nTransport: `vm env set` uses the secret-safe receiver path rather than `vm.exec`. Values go to the app over the local socket and from there over the machine's cmux-tui link (Noise-authenticated end to end, on the private WireGuard network) into the machine's `cmux env receive`: a receiver terminal turns PTY echo off, prints `CMUX-ENV-READY`, reads base64 lines until `CMUX-ENV-END`, writes `~/.config/cmux/env` (0600), and answers `CMUX-ENV-OK keys=\u003cn>`; the sender closes the terminal. For `--from-file` and stdin, the value is not placed in the command line, control plane, or provider API, and it is not shown on a screen or in scrollback (the daemon does not journal input). The direct `KEY=VALUE` form can appear in local shell history or process arguments, so do not use it for secrets. `ls` never prints a value without `--show`. Inside a machine, `cmux vm env set \u003cpeer> …` uses the same handshake toward a linked peer. Snapshots, forks, and templates carry the file (it lives in the work user's home): `cmux vm env rm` what must not travel before `vm promote-template`. A machine whose shim predates the verb is reported as such (reconnect: `cmux vm tree \u003cid> --refresh`).\n\n## Surfaces and display\n\nA **surface** is a terminal, VNC screen, or browser on This Mac or on a machine, with a stable id `\u003cmachine>/\u003ckind>/\u003ckey>` (`local/terminal/\u003cuuid>`, `vivid-newt/terminal/term_2f9c…`, `vivid-newt/display/display:1`, `vivid-newt/browser/port:3000`). Panes project surfaces; closing a pane never kills a machine's terminal.\n\n### `cmux vm shell`\n\n```bash\ncmux vm shell \u003cid> [--window \u003cid|ref|index>] [--json] # alias: cmux vm attach\n```\n\nA **plain terminal** on the machine, like an ssh session (not the cmux-tui client): one shared open path — `vm.cmux_remote_info` (availability and protocol check), `workspace.create` (or `workspace.cloud_vm_terminal_ready` for `--workspace`), `workspace.cloud_vm_bind`, then `surface.new_terminal {machine, open: true, name: \"shell\"}`, which creates a `bash -l` terminal in the machine's session and projects it as a pane; the placeholder pane is closed with `surface.close`. Desktop machines also get their screen in a split (`vm.desktop_open`). Text `OK workspace=\u003cws> transport=cmux-remote terminal=\u003cterm>` plus `Reattach: cmux vm open \u003cm>/\u003cws>/\u003cterm>`; `--json` adds `terminal_id`, `remote_workspace_id`, `surface_id`. Every other cloud open (`vm new`, `vm fork`, `vm restore`, `vm base open`, the Machines panel, the sidebar cloud button) uses this path. Older deployments without a cmux-tui daemon fall back to the websocket/SSH transports (`vm.attach_info`, `vm.session_attach_info`, `vm.sessions`); a machine that answers `vm_attach_transport_unsupported` is cmux-tui only. Sidebar: machine row › Open Shell / click.\n\n### `cmux vm tui`\n\n```bash\ncmux vm tui \u003cid> [--window \u003cid|ref|index>] [--json]\n```\n\nOpens the full cmux-tui client in a pane, with its own workspaces, panes, and tabs. It dials the machine's trusted-carrier listener over the private network, so it does not perform device enrollment or approval. The hidden `vm-tui-connect --config \u003cfile>` helper is used only by this command. Use `vm shell` for a plain terminal.\n\n### `cmux vm open`\n\n```bash\ncmux vm open \u003ctarget> [--workspace \u003cid|ref|index>] [--focus|--no-focus] [--print] [--json]\ncmux vm open \u003cid> \u003cport> [--print] [--json]\n```\n\nOne resolver, several target shapes (copy them from `cmux vm tree`):\n\n| Target | Does | Socket |\n|---|---|---|\n| `\u003cmachine>` | the machine's shell — exactly `cmux vm shell \u003cmachine>` | see `vm shell` |\n| `\u003cmachine>/\u003cws>` (`ws_…` id or workspace name) | that workspace's focused/first live terminal, or a new terminal there when it is empty (`OK terminal=… workspace=… surface=…`) | `surface.catalog`, `surface.project` / `surface.new_terminal {machine, remote_workspace_id, open}` |\n| `\u003cmachine>/\u003cws>/\u003cterm_…>` | one terminal; reuses the pane already showing it (`reused=true`) | `surface.project {resource: \"\u003cm>/terminal/\u003cterm>\", workspace_id?, focus?}` |\n| `\u003cmachine>:desktop` | the noVNC screen as a browser pane — same as `cmux vm desktop` (desktop-kind machines only) | `vm.desktop_open` |\n| `\u003cmachine>:port/\u003cn>` and `\u003cmachine> \u003cn>` | an HTTP port on the machine, as a browser pane — the URL is the machine's private VPC address, so it needs `cmux vpn up` | `vm.port_open {id, port, workspace_id?}` |\n| `… --print` | ports only: mint and print the URL, no pane | `vm.open_port {id, port}` → `{open_url, …}` |\n\n`--workspace` targets a local workspace (default: the machine's open workspace, else where you are); focus follows the open-command default: interactive runs switch to the pane, agents and scripts open it beside you in the background (marked unread) without stealing typing; `--focus` / `--no-focus` override it and `CMUX_FOCUS_NEW=1|0` sets it for a whole environment. Text `OK surface=… workspace=… terminal=… [reused=true]`; ports print `\u003cid>:\u003cport>` and the URL. Anything else is a usage error (exit 1). `cmux vm port` is an alias for the verb. Sidebar: row click / Open; Port row click.\n\n### `cmux vm desktop`\n\n```bash\ncmux vm desktop \u003cid> [--workspace \u003cid|ref|index>] [--json] # alias: cmux vm vnc\n```\n\nSocket `vm.desktop_open {id, workspace_id?, focus: false}`: the machine's noVNC desktop as a browser pane in the machine's open workspace, else the one you name, else where you are (focus defaults to false so the pane never steals typing from the shell). The pane opens the machine's **private address on 6901** over the owner's private network — `cmux vpn up` first, like every other access verb. Text `OK surface=… url=…`. Machines with a screen only; historical shell-only images have none (exit 1). New machines include displays even when created with a legacy `--base` flag. Sidebar: machine row › Open Desktop; Displays › Open Desktop.\n\n### `cmux vm ssh`\n\n```bash\ncmux vm ssh \u003cid> [--window \u003cid|ref|index>] [--json]\n```\n\nOpens the provider SSH-backed workspace through the app's SSH attach path. This compatibility path is provider-dependent; the normal cloud terminal path is `vm shell`.\n\n### Provider attach diagnostics\n\n```bash\ncmux vm ssh-info \u003cid> [--json]\ncmux vm ssh-attach \u003cid>\n```\n\n`ssh-info` reports provider SSH details when an image exposes them; the default\ncmux transport may have no SSH endpoint. The app also exposes the internal\n`vm.diagnostics` socket method for provider attach diagnostics. `ssh-attach` is an internal helper\nused by the app's attach surface and is not normally invoked by an agent.\n\n### `cmux surface ls`\n\n```bash\ncmux surface ls [\u003cmachine>|local] [--refresh] [--json] # aliases: cmux surface list, cmux surface tree, cmux surface catalog\n```\n\nSocket `surface.catalog` — exactly `cmux vm tree`, including This Mac.\n\n### `cmux surface open`\n\n```bash\ncmux surface open \u003cresource> [--workspace \u003cid|ref|index>] [--pane \u003cid|ref>] [--left|--right|--up|--down|--tab] [--new] [--focus|--no-focus] [--json]\n# alias: cmux surface project\n```\n\nSocket `surface.project {resource, workspace_id?, pane_id?, direction?, placement?, reuse?, focus?}` → `{surface_id, workspace_id, reused, resource}`: puts one surface in a pane through the single open path. Reuses the pane already showing the resource unless `--new`; `--pane` + a side splits that pane on that side, `--tab` adds a tab to it, otherwise the workspace's focused pane; a local terminal moves to the destination (it can be shown once). Text `OK surface=… workspace=… resource=… [reused=true]`. Sidebar: row click, Open in New Tab, Open in New Pane, drag onto a pane edge.\n\n### `cmux surface new-terminal`\n\n```bash\ncmux surface new-terminal --machine \u003cid|local> [--cwd \u003cdir>] [--name \u003cname>] [--remote-workspace \u003cws_…>] [--workspace \u003cid|ref|index>] [--no-open] [--focus|--no-focus] [--json] [-- \u003ccommand...>]\n# alias: cmux surface new\n```\n\nSocket `surface.new_terminal {machine, command?, cwd?, name?, remote_workspace_id?, open?, workspace_id?}` → `{resource, terminal_id, machine, remote_workspace_id, workspace_id?, surface_id?}`: a terminal on the machine through its provider (cloud terminals land in the machine's cmux-tui session; `--remote-workspace` picks which; `local` is a new shell on This Mac), opened as a pane unless `--no-open`. Sidebar: Terminals / Workspaces › New Terminal; workspace row › New Terminal Here.\n\n### `cmux surface new-display`\n\n```bash\ncmux surface new-display --machine \u003ccloud-id> [--workspace \u003cid|ref|index>] [--no-open] [--focus|--no-focus] [--json]\n```\n\nSocket `surface.new_display {machine, open?, workspace_id?, focus?}` → the created display resource (`resource`, `id`, `machine`, `kind`, `title`, `url`, and related catalog fields), plus `workspace_id` / `surface_id` when opened. Creates a new VNC display on a cloud machine through the guest display helper. It opens the display in the selected workspace unless `--no-open`; `--focus` and `--no-focus` control whether that pane is focused. `local` is rejected because display creation is a cloud-machine operation. If the display is created but cannot be opened, the result has `projection_failed: true`, `open: false` and a `projection_error`; the exit status stays 0 and `cmux surface open \u003cresource>` retries the open. A second creation on the same machine while one is running fails with code `busy`. The method is available on the local cmux socket and is intentionally not relayed through a remote machine.\n\n## Checkpoints and forks\n\nProvider-dependent: `cmux vm ls --json` → `capabilities.snapshot` / `capabilities.fork` say whether a machine supports them; the sidebar hides the verbs on providers that cannot.\n\n### `cmux vm snapshot`\n\n```bash\ncmux vm snapshot \u003cid> [--name \u003cname>] [--json] # alias: cmux vm checkpoint\n```\n\nSocket `vm.snapshot {id, name?}`. Text `OK snapshot=\u003csnapshot id>`; `--json` the payload (`snapshot_id` or `id`). Sidebar: machine row › Checkpoint.\n\n### `cmux vm fork`\n\n```bash\ncmux vm fork \u003cid> [--name \u003cname>] [--window \u003cid|ref|index>] [--detach|-d] [--json]\n```\n\nSocket `vm.fork {id, name?, idempotency_key}`: clones a machine as a new tracked machine for a parallel experiment. `--detach` prints `OK \u003cid>` with provider, image, and snapshot (`native fork` when the provider forks without one); otherwise opens the new machine's shell. Sidebar: machine row › Fork.\n\n### `cmux vm restore`\n\n```bash\ncmux vm restore \u003csnapshot-id> [--provider \u003cprovider>] [--window \u003cid|ref|index>] [--detach|-d] [--json]\n```\n\nSocket `vm.restore {snapshot_id, provider?, idempotency_key}`: a snapshot as a new tracked machine; opens its shell unless `--detach`.\n\n### `cmux vm promote-template`\n\n```bash\ncmux vm promote-template \u003cid> [--json]\n```\n\nSocket `vm.snapshot` with a template-oriented name (`template-\u003cid>-\u003cunix time>`). Text `OK template=\u003csnapshot id>`.\n\n## Networking and ports\n\n### `cmux vm ports`\n\n```bash\ncmux vm ports \u003cid> [--json]\n```\n\nA `vm.exec` of `ss -ltnp` (or `netstat -ltnp`): the TCP ports listening **inside** the machine. `--json`: the exec payload.\n\n### Port URLs: `cmux vm open \u003cid> \u003cport>`\n\nSee `vm open`: `cmux vm open \u003cid> 3000` opens an HTTP port on the machine as a browser pane (`vm.port_open`); `--print` only mints and prints the URL (`vm.open_port`, `--json` → `{open_url, …}`). The URL points at the machine's **private VPC address** — reachable only through the owner's WireGuard tunnel (`cmux vpn up`), never a public ingress; the daemon's own port is refused, and the lease carries an expiry. Only share URLs minted this way; never guess provider URLs (they resolve for the machine's owner only).\n\n## Account and plan\n\n### `cmux vpn`\n\n```bash\ncmux vpn up # enroll this Mac on first run, bring the WireGuard tunnel up (wg-quick, prompts for sudo; brew install wireguard-tools)\ncmux vpn down # tunnel down; enrollment kept\ncmux vpn status # tunnel state, config path, backend\ncmux vpn on # alias for up\ncmux vpn off # alias for down\ncmux vpn revoke # tunnel down + unenroll (server deletes its side)\n```\n\nThe tunnel between this Mac and the private Cloud VM network. Config lives under `~/.cmuxterm/wireguard/` — **one tunnel per deployment** (interface `cmux` for production, `cmux-staging`/`cmux-local`/`cmux-dev` for other API origins), so a dev build and the production app can both be up side by side. The private key is generated on this Mac and never leaves it. Run `up` once per boot before the attach/exec/port verbs; a stale tunnel (another enrollment's keys — `vpn status` reports it) is replaced by `up` instead of read as up.\n\n`hosts` reads `vm.list` and publishes `\u003cmachine>.internal` names (id slug, plus the display label when it differs) system-wide, so browsers and curl resolve them whenever the tunnel is up — a manual convenience for you and the user; the verbs themselves use raw addresses. `up` re-syncs the block quietly on success, so run `hosts` yourself mainly right after `cmux vm new`; `--json` answers `{hosts_changed, machine_count}`.\n\n### `cmux auth`\n\n```bash\ncmux auth status [--json] # {signed_in, …}; socket auth.status\ncmux auth login # alias: cmux login — opens the sign-in popup (auth.begin_sign_in / auth.sign_in_url) and waits\ncmux auth logout # alias: cmux logout — auth.sign_out\n```\n\nEvery `cmux vm` verb requires a signed-in app.\n\n### Plan meter and limits\n\n`cmux vm ls` prints `N of M machines on the \u003cplan> plan` when the plan carries a cap, or `N machines on the \u003cplan> plan, no limit` when it does not — the cap is whatever the backend sends (`cmux vm ls --json` → `limits`: `maxActiveVms?`, `planId`, `memoryOptionsMb?`, `freeAccessExpiresAt` when a free window applies); plan tiers and their caps change on the pricing page, so read them from `limits`, never from memory. **Provisioning is gated to paid plans**: `vm new`, a first `vm base open`, `base reset`, `fork`, `restore`, and the router's provisioning path return the `vm_requires_pro` error (with the pricing link) on free or unknown plans. Report caps and gates — never delete machines to make room without asking. A parsed size not present in `memoryOptionsMb` resolves to the plan default; inspect the create result or `vm stats` rather than assuming the request was honored.\n\n### `cmux ai-accounts`\n\n```bash\ncmux ai-accounts list [--team \u003cid>] [--json]\ncmux ai-accounts upload \u003cclaude|codex|anthropic-key|openai-key> [--label \u003cs>] [--key \u003cs>] [--team \u003cid>] [--validate] [--json]\ncmux ai-accounts remove \u003caccount-id> [--team \u003cid>] [--json]\n```\n\nSockets `aiAccounts.list` / `aiAccounts.upload` / `aiAccounts.remove`: uploads local AI credentials to the team's subrouter tenant so agents started with `vm agent` can authenticate on a machine without copying tokens onto it. Only on the user's say-so.\n\n### `cmux capabilities` and `cmux rpc`\n\n```bash\ncmux capabilities # socket system.capabilities: every method the app serves, including the vm.* and surface.* set below\ncmux rpc \u003cmethod> [json-params] # call any v2 method directly, e.g. cmux rpc vm.stats '{\"id\":\"brave-otter\"}'\n```\n\n## Socket methods (the app's `vm.*` / cloud `surface.*` set)\n\n| Method | CLI verb |\n|---|---|\n| `vm.list` | `vm ls` |\n| `vm.create` | `vm new`, and `vm run` / `vm route --provision` / `vm agent` when they provision |\n| `vm.base_open`, `vm.base_reset` | `vm base open`, `vm base reset` |\n| `vm.status` | `vm status`, `vm handoff`, `vm wait` |\n| `vm.stats` | `vm stats`; the router's load scoring |\n| `vm.hygiene` | `vm clean`, `vm clean --apply` |\n| `vm.env_set` | `vm env set` |\n| `vm.file_put` | `vm push` |\n| `vm.pause`, `vm.resume`, `vm.restart` | `vm pause`, `vm resume`, `vm restart` |\n| `vm.reflection` | `vm self` |\n| `vm.snapshot_list`, `vm.snapshot_delete` | `cmux rpc vm.snapshot_list`, `cmux rpc vm.snapshot_delete` |\n| `vm.terminal_output`, `vm.terminal_wait_exit` | `vm terminal output`, `vm terminal wait-exit` |\n| `vm.diagnostics` | `cmux rpc vm.diagnostics '{}'` returns the app's cloud-operation report; `{\"show\":true}` also opens the diagnostics window |\n| `vm.resize` | `vm resize \u003cid> [--cpu …] [--memory …] [--disk …]`; machine row › Resize machine |\n| `vm.rename` | `vm new --name` and the router's `agent-pool` label; direct machine-label editing is currently a sidebar action |\n| `vm.tab_rename` | `vm tab rename` |\n| `vm.terminal_rename` | `vm terminal rename` |\n| `vm.publication_grant`, `vm.publication_grants`, `vm.publication_ungrant` | publication viewer grant management exposed by the Cloud publication controller |\n| `vm.tunnel_config`, `vm.tunnel_up`, `vm.tunnel_down`, `vm.tunnel_status`, `vm.tunnel_wait`, `vm.tunnel_revoke` | WireGuard enrollment and lifecycle behind `cmux vpn up|down|status|on|off|revoke` |\n\n| `vm.snapshot` | `vm snapshot`, `vm promote-template` |\n| `vm.fork`, `vm.restore` | `vm fork`, `vm restore` |\n| `vm.destroy` | Cloud sidebar machine deletion |\n| `vm.exec` | `vm exec`, `vm run`, `vm push`, `vm pull`, `vm wait --wake`, `vm tools`, `vm ports` |\n| `vm.open_port`, `vm.port_open` | `vm open \u003cid> \u003cport> --print`, `vm open \u003cid> \u003cport>` / `\u003cid>:port/\u003cn>` |\n| `vm.desktop_open` | `vm desktop`, `vm open \u003cid>:desktop`, the split beside `vm shell` |\n| `vm.cmux_remote_info`, `vm.link_socket` | the shared machine shell and surface open path |\n| `vm.ssh_info` | provider-specific attach diagnostics surfaced by the app |\n| `vm.diagnostics` | machine and cloud-operation diagnostics returned by the app for support and failure inspection |\n| `vm.scp_info` | SCP endpoint setup and public-key diagnostics used by `vm push` / `vm pull` |\n| `vm.attach_info`, `vm.session_attach_info`, `vm.sessions` | legacy websocket/SSH attach transports the open path falls back to on deployments without a cmux-tui daemon (`cmux rpc` reaches them directly) |\n| `vm.tree` | the pre-catalog tree; `vm tree` uses `surface.catalog` |\n| `vm.terminal_open`, `vm.terminal_new` | older terminal verbs; `vm open \u003cm>/\u003cws>/\u003cterm>` and `surface new-terminal` use `surface.project` / `surface.new_terminal` |\n| `vm.workspace_new`, `vm.workspace_open`, `vm.workspace_rename`, `vm.workspace_close`, `vm.workspace_delete` | `vm workspace new|open|rename|close|rm` |\n| `vm.terminal_close` | `vm terminal close` |\n| `vm.terminal_write`, `vm.terminal_read`, `vm.terminal_wait` | `vm terminal send`, `vm terminal read`, `vm terminal wait` |\n| `vm.cloud_prompt`, `vm.cloud_agent_open` | `vm prompt`, `vm prompt --open` |\n| `vm.publication_list`, `vm.publication_create`, `vm.publication_verify`, `vm.publication_update`, `vm.publication_delete` | `cloud domains list`, `publish`, `access`, `rm`; `vm.publication_verify` is the app-side publication retry path |\n| `vm.domain_list`, `vm.domain_verify` | `cloud domains zones`, `cloud domains verify` |\n| `surface.catalog`, `surface.project`, `surface.new_terminal`, `surface.new_display` | `vm tree` / `surface ls`, `surface open` / `vm open`, `surface new-terminal` / `vm agent`, `surface new-display` |\n\nThe authenticated public-domain workflow is **shipped on this branch**: use\n`cmux cloud domains` for generated or custom HTTPS publications. `cmux vm open\n\u003cid> \u003cport>` remains the private WireGuard preview path; it is not a substitute\nfor a domain publication.\n\n### Current VM primitives\n\nThe current CLI also exposes these machine-scoped operations:\n\n```bash\ncmux vm self \u003cid> [\u003cpath>] [--json]\ncmux vm pause \u003cid>\ncmux vm resume \u003cid>\ncmux vm restart \u003cid>\ncmux vm dev \u003cid> [\u003cfolder>] [--name \u003cworkspace>] [--layout \u003cfile>] [--no-open]\ncmux vm layout export \u003cid> [\u003cworkspace>] [--raw]\ncmux vm layout apply \u003cid> \u003cfile>|- [--name \u003cworkspace>] [--workspace \u003cempty-workspace>]\ncmux vm env set \u003cid> KEY=VALUE…\ncmux vm terminal wait-exit \u003cid> \u003cterminal> [--timeout \u003cseconds>]\ncmux vm terminal output \u003cid> \u003cterminal> [--after \u003coffset>]\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":66860,"content_sha256":"0e917826038bad1356b3572e572ffb23526ad2aa560646dd4c5fe1453333a869"},{"filename":"references/guest.md","content":"# Working inside a Cloud machine\n\nRead this for guest auth, peer access or presentation behavior.\n\n## Guest auth and CodeRouter\n\nStart with `cmux self --json` to identify the current machine, then use\n`cmux self peers` to discover the owner's reachable machines. On an older guest\nwhose help exposes it, `cmux vm ls` is the compatibility inventory fallback.\nBoth paths read through the VM-bound TLS edge without a Mac account token. Host\nlifecycle verbs still run on the Mac; read the guest's `cmux --help` for the\nsubset its image supports.\n\nInside a Cloud machine, the guest `cmux` adapter can report route health and run\nan agent through the shared CodeRouter without exposing the Mac's Stack session:\n\n```bash\ncmux auth status --json\ncmux coderouter status --json\ncmux coderouter usage\ncmux coderouter models\ncmux coderouter agent claude \"summarize the current checkout\"\ncmux agent codex \"run the tests\"\n```\n\nThese commands describe the machine's daemon, TLS edge, and VM-bound route.\nHost account login and upstream credential management remain on the Mac; do not\ncopy those tokens into a VM. `vm agent` still starts a detached terminal on the\nselected machine, while the guest `cmux agent` form runs through CodeRouter.\n\n## The grammar (one spelling per concept)\n\n| Where you are | What you address | Spelling |\n|---|---|---|\n| Mac | a machine | `cmux vm \u003cverb> \u003cmachine> …` — everything about machines lives here |\n| Mac | this Mac's own session | the unprefixed local verbs (`cmux send-key`, `cmux new-workspace`, …) |\n| inside a machine | **this** machine's session | the same unprefixed local verbs as on a Mac (`cmux send-key`, `cmux terminal send`, `cmux layout apply`, `cmux env set`, `cmux notify`) |\n| inside a machine | **another** machine | `cmux vm \u003cverb> \u003cmachine> …` — the same grammar as on the Mac |\n| inside a machine | the owner's machines | `cmux self peers` (with reachability); legacy fallback `cmux vm ls` when supported |\n| inside a machine | myself | `cmux self [peers\\|integrations\\|owner\\|machine] [--json]` (aliases: `cmux whoami`, `cmux reflect [\u003cpath>]`) |\n| Mac | a machine's identity | `cmux vm self \u003cmachine> [\u003cpath>] [--json]` — the same reflection payloads through your session |\n\n## Inside a machine: the same verbs, and other machines\n\nEvery machine has its own `cmux` (a shim over its cmux-tui daemon). An agent running *in* the machine drives its own session with the Mac spellings — the target defaults to its own terminal (`$CMUX_TUI_TERMINAL_ID`):\n\n```bash\ncmux self # who am I: name, id, status, team, owner, plan (reflection; no credential in the guest)\ncmux self peers # the owner's other machines and their routes; `cmux self integrations` = what I can use, with help commands\ncmux tree --json # this machine's workspaces/terminals\ncmux new-workspace --name tests # a workspace here\ncmux terminal send \u003cterm> 'bun test' --keys enter ; cmux terminal wait \u003cterm> --pattern 'pass|fail' ; cmux terminal read \u003cterm>\ncmux terminal wait-exit \u003cterm> --timeout 600 ; cmux terminal output \u003cterm> # block until the process exits, then read the full output (not just the screen)\ncmux send-key --terminal \u003cterm> ctrl+c # keys into another terminal on this machine\ncmux layout apply --name app app.json # the same layout verb, locally\ncmux env ls # the same env file\ncmux notify --title \"done\" --body \"…\" # lands on the Mac pane showing this terminal\ncmux agent claude --timeout 600 \"fix the tests\" # runs in this terminal until it exits (it is the wait; exit code passes through; --timeout caps it)\n```\n\nTo talk to **another** machine (a second agent, a service box), a machine discovers its peers through reflection (`cmux self peers`; use the legacy `cmux vm ls` inventory only when the guest help exposes it; the owner's private network is the trust boundary, so no Mac step is needed — older Mac-written route files still work). Inside a machine, `cmux vm …` takes the peer as its first argument with the same grammar the Mac uses: `cmux vm tree \u003cdst>`, `cmux vm exec \u003cdst> -- \u003ccmd>`, `cmux vm terminal send|read|wait|close \u003cdst> \u003cterm> …`, `cmux vm terminal send \u003cdst> \u003cterm> enter`, `cmux vm workspace new|rename|close|rm \u003cdst> …`, `cmux vm agent --machine \u003cdst> --agent codex -- \"review work/app\"` (a durable terminal on the peer running the peer's own agent config), `cmux vm layout export|apply \u003cdst> …`, `cmux vm env set|ls|rm \u003cdst> …`, `cmux vm push \u003cdst> \u003cfile> \u003cremote-path>` (one file over the link, secret-safe), `cmux vm agent --machine \u003cdst> … --wait --output` (until the peer's agent exits). No control-plane credential lives in any VM; a machine reaches only machines of its own owner.\n\nA pane showing a machine surface is an ordinary local pane: move, split, reorder, or close it with the local topology verbs ([local topology](../../cmux/SKILL.md)) and the surface catalog follows the pane; closing a pane never kills the machine's terminal. A local workspace that *mirrors* a machine workspace (opened with `cmux vm workspace open`, or bound with `workspace.cloud_vm_bind`) is that workspace seen from the Mac, so its structure is the machine's: a pane moved into it takes its tab there (a pool terminal gets one), a terminal pane closed in it closes that tab (the terminal detaches into the Terminals pool, still running), and a tab or workspace renamed there is renamed on the machine. Closing the local workspace itself (⌘⇧W) only ends the view: the machine workspace and its terminals stay exactly as they were. Panes in any other local workspace are viewers and never touch the machine's layout. Rearranging the machine's topology in full is what `cmux vm tui \u003cid>` is for.\n\n## Notifications\n\n`cmux notify` run inside a machine reaches the user's Mac as data: the machine's daemon records it and the Mac shows it on the pane displaying the terminal it ran in (or at workspace level wherever the machine is open; nowhere if nothing of the machine is on screen). Keep `--title`/`--body` short (128 B / 1 KiB caps, 5 per burst then 1 per second); `--subtitle` folds into the body; Mac selectors (`--workspace`, `--surface`, `--window`, `--tab`, `--panel`), `--reply`, and `--desktop` are ignored there, and nothing can be typed back into the machine from the notification.\n\n## Arrange the view from inside the machine\n\nUse these commands in a daemon terminal (`cmux tree --json` supplies workspace,\nscreen, pane, split and tab IDs):\n\n```bash\ncmux workspace rename \u003cws> \"Review ready\"\ncmux terminal rename current \"Builder\" --json\ncmux tab rename \u003ctab> \"Test results\" --json\ncmux pane split \u003cpane> right --ratio 0.6 --json\ncmux tab move \u003ctab> --workspace \u003cws> --screen \u003cscreen> --pane \u003cdestination-pane> --index 0 --json\ncmux pane swap \u003cpane> --other-workspace \u003cws> --other-screen \u003cscreen> --other-pane \u003cother-pane> --json\ncmux pane resize \u003cpane> --split \u003csplit> --ratio 0.65 --json\ncmux workspace move \u003cws> --index 0\ncmux tab focus \u003ctab>\ncmux notify --title \"Review ready\" --body \"The workspace has the app, logs, and test results.\"\n```\n\n`tab rename` labels one placement; `terminal rename` labels every current placement\nof that terminal. Names, including spaces and an empty string, are passed as exact\narguments. A terminal with several views should be moved by its tab ID. Moving,\nrenaming, swapping, and changing split ratios preserve running terminal processes.\n`pane resize` changes layout geometry. Machine resource resizing uses `vm resize` and preserves the existing VM identity and data.\n\nResource commands use `cmux \u003cresource> \u003cverb> …`; a peer uses\n`cmux vm \u003cresource> \u003cverb> \u003cmachine> …` (for example,\n`cmux vm tab rename \u003cmachine> \u003ctab> \"Logs\"`). Top-level verbs retain their\nexisting forms, such as `cmux send-key`, `cmux new-workspace`, `cmux agent`, and\n`cmux notify`. Existing ID-first daemon syntax is also supported. `cmux workspace\nhelp` lists the full topology grammar.\n\nArrange the daemon workspace before presenting it. The Mac's\n`cmux vm workspace open \u003cmachine> \u003cws>` reads that layout when creating its local\nview. These guest commands do not force focus or rearrange an already-open Mac\nprojection. `layout apply` is for new/empty workspaces; use the commands above to\nchange an occupied workspace without restarting its agents.\n\n## Browser authentication from guest terminals\n\n`cmux open-url \u003chttp-or-https-url>` asks the Mac projecting that exact terminal\nto open the URL using its terminal-link preference, without changing workspace\nor keyboard focus. Create/heal installs `cmux-open-url`, PATH wrappers for\n`xdg-open`, `x-www-browser`, and `sensible-browser`, and Bash/zsh/fish defaults\nfor `BROWSER` and `GH_BROWSER`. Explicit browser environment overrides survive.\nDirect Chrome, `agent-browser`, and CUA keep their existing `DISPLAY=:1` behavior.\n\nThe opener uses a bounded, transient request over the authenticated cmux-tui\nlink, with a frontend delivery acknowledgement. No attached projection, old\nbinaries, denied placement, disconnect, or timeout prints `Open this URL: \u003curl>`\nand exits successfully so the auth CLI keeps polling. URLs are never stored as\nnotifications or replayed on reconnect. This requires the updated daemon and\nMac client; older combinations safely use the printable fallback.\n\nブラウザー認証: `cmux open-url \u003cURL>` は、その端末を表示している Mac の\nリンク設定に従って URL を開き、ワークスペースや入力フォーカスを変更しません。\n接続されていない場合、旧バージョンの場合、配信失敗やタイムアウトの場合は\nURL を表示して正常終了します。Chrome、`agent-browser`、CUA の `DISPLAY=:1`\nでの動作は変わりません。URL は通知として保存されず、再接続時にも再実行されません。\n\nHTTP(S) MIME handlers also use `cmux-open-url`, covering absolute and CLI-bundled\n`xdg-open` and GIO. File associations and direct Chrome launchers are unchanged.\nHTTP(S) の MIME ハンドラーも cmux を使用します。ファイルの関連付けと\nChrome の直接起動は変更しません。\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":10199,"content_sha256":"336b25532f4d4b58ef9f6daefafef88d3f5e1fa15081cdca1685364edcef9798"},{"filename":"references/sidebar-parity.md","content":"# Sidebar ↔ CLI parity (1:1)\n\nEvery verb in the Cloud sidebar has a CLI verb that goes through the **same socket method** and the same app code path (`SurfaceCatalog`, the machine's `CmuxTuiSurfaceProvider`). An agent can do anything a person can do from the sidebar, and a sidebar action never does something the CLI cannot. Ids come from `cmux vm tree --json` / `cmux surface ls --json` (`\u003cmachine>/\u003ckind>/\u003ckey>`, `ws_…`, `term_…`).\n\nSidebar (human) | CLI (agent) | Socket method | Verified\n--- | --- | --- | ---\n**Machines panel + / palette \"New Cloud Machine…\"** (name, Base size; Create closes the sheet and the create runs in the background) | `cmux vm new [--desktop\\|--base] [--size 4g\\|8g\\|16g\\|24g\\|32g\\|64g] [--name \u003clabel>] [--focus false] [--detach] [--json]` — the sheet passes `--focus false`; `vm ls --json` → `limits.memoryOptionsMb` supplies its choices | `vm.create` | ✅ the pending row in the tree (\"Creating…\", then failure with Retry / Show Error / Dismiss) is `MachineCreateCoordinator`; `--detach` is the agent's way to get the same non-blocking outcome without a workspace\n**Open Base / Set Up Base** (first setup closes its sheet immediately and continues in the background) | `cmux vm base open [--desktop\\|--base] [--focus false]` — the sheet passes `--focus false` | `vm.base_open` | ✅ pending row reads \"Setting up Base…\"\nControl bar › **Open Cloud Agent** (Claude/Codex/OpenCode/Pi) | `cmux vm prompt --open \u003cagent>` | `vm.cloud_agent_open` | ✅ installs the bundled cmux-cloud skill file (`~/.config/cmux/skills/cmux-cloud.md`), opens a local agent terminal with the kickoff prompt\nControl bar › **Copy Cloud Prompt** | `cmux vm prompt` | `vm.cloud_prompt` | ✅ prints the same prompt (skill path on stderr) — bootstraps ANY agent/harness\nMachine row › **Open Shell** / click | `cmux surface new-terminal --machine \u003cm>` (into the current workspace, like the row) · `cmux vm open \u003cm> [--workspace \u003cref>]` (a shell, its own workspace by default) | `vm.terminal_new` / `workspace.cloud_vm_terminal_ready` | ✅\nMachine row › **New Workspace**, Workspaces + | `cmux vm workspace new \u003cm> [--name n]` | `vm.workspace_new` | ✅\nMachine row › **Open Desktop**, VNC Displays › Open Desktop, screen row click (one row per screen, `display:1`, …) | `cmux vm open \u003cm>:desktop` / `cmux surface open \u003cm>/display/display:1` | `vm.desktop_open` / `surface.project` | ✅\nMachine row › **Open Full cmux-tui Client** | `cmux vm tui \u003cm>` | (pane command) | ✅\nMachine row › **Refresh**, any group › Refresh | `cmux vm tree --refresh` / `cmux surface ls --refresh` | `vm.tree {refresh}` | ✅\nMachine row › **Rename…** | `cmux vm rename \u003cm> \u003clabel>` | `vm.rename` | ✅\nMachine row › **Status** | `cmux vm status \u003cm>` (+ `vm stats`) | `vm.status` / `vm.stats` | ✅\nMachine row › **Checkpoint** (only when `capabilities.snapshot`) | `cmux vm snapshot \u003cm> [--name n]` | `vm.snapshot` | ✅ hidden on providers that cannot; `vm ls --json` → `capabilities`\nMachine row › **Fork** (only when `capabilities.fork`) | `cmux vm fork \u003cm> [--name n]` | `vm.fork` | ✅ hidden on providers that cannot\nMachine row › **Delete…** | `cmux vm rm \u003cm>` | `vm.destroy` | ✅\nTerminals / Workspaces group › **New Terminal** | `cmux surface new-terminal --machine \u003cm> [-- \u003ccmd>]` | `vm.terminal_new` | ✅\nWorkspace row › **New Terminal Here** | `cmux surface new-terminal --machine \u003cm> --remote-workspace \u003cws>` | `vm.terminal_new {workspace_id}` | ✅\nWorkspace row › **Go to Workspace** (the open verb's label once the workspace is showing locally), click, Return | `cmux workspace select \u003clocal-id>` (the local workspace from `vm tree --json` projections) | `workspace.select` | ✅ one open verb; never opens a second copy\nWorkspace row › **Open Workspace** (not open yet), click, Return | `cmux vm workspace open \u003cm> \u003cws>` — every member of the workspace (terminals, browsers, pinned displays) as its own local workspace | `vm.workspace_open` | ✅ resolved exactly like the row (`ws_…` id or an unambiguous name; every view counted, so a terminal viewed in two workspaces opens from both); an empty workspace opens nothing (D9) — the CLI says so and names `cmux vm open \u003cm>/\u003cws>` — and **with the machine screen's geometry**: split directions, divider ratios and the tabs of each pane come from the daemon's LayoutDocument (`screens[].layout`), so what the agent arranged in the cloud is what appears; a workspace with no layout falls back to one pane per terminal (right/down alternation)\n(no menu verb — one terminal, not the whole workspace) | `cmux vm open \u003cm>/\u003cws>` — the workspace's focused/first live terminal, or a new shell in it when nothing is running | `surface.project` / `surface.new_terminal` | ✅ same id-or-unambiguous-name resolution; this is the verb that creates in an empty workspace\n(no menu verb — drop onto the current pane) | `cmux vm workspace open \u003cm> \u003cws> --here [--workspace \u003clocal>]` | `vm.workspace_open {here}` | ✅\n(no menu verb — CLI placement only) | `cmux vm workspace open \u003cm> \u003cws> --tabs [--pane \u003cp>]` | `vm.workspace_open {here, placement: tab}` | ✅\nDrag a workspace row onto a pane edge | `cmux vm workspace open \u003cm> \u003cws> --pane \u003cp> --left\\|--right\\|--up\\|--down` | `vm.workspace_open {here, pane_id, direction}` | ✅\nWorkspace row › **Close Workspace…**, hover × (confirms when it has terminals) | `cmux vm workspace rm \u003cm> \u003cws>` | `vm.workspace_delete` | ✅ same `CloudTreeNodeActions.deleteWorkspaceAndTerminals`: kills every terminal viewed there, then closes it — a closed workspace never leaves stray pool rows\n(no menu verb — CLI only) | `cmux vm workspace close \u003cm> \u003cws>` | `vm.workspace_close` | ✅ the protocol's keep-terminals close: they keep running (only `terminal close` kills) and, their workspace being gone, show in Terminals greyed as \"detached\"\nWorkspace row › **Rename…** | `cmux vm workspace rename \u003cm> \u003cws> \u003cname>` | `vm.workspace_rename` | ✅ same `SurfaceCatalog` machine-scoped rename lane\nTerminal row / tab view › **Rename…** | `cmux vm tab rename \u003cm> \u003ctab> \u003cname>` or `cmux vm terminal rename \u003cm> \u003cterm> \u003cname>` (`\"\"` clears the custom label) | `vm.tab_rename` / `vm.terminal_rename` | ✅ exact placement or explicit all-views fan-out through `SurfaceCatalog`\nWorkspace or item row › **Pin / Unpin**, **Move Up / Move Down**, drag between siblings | `cmux vm tree --sidebar list`, then `cmux vm tree --sidebar pin\\|unpin\\|up\\|down \u003cnode-id>` or `before\\|after \u003cnode-id> \u003ctarget-id>` | `vm.tree {sidebar: true, action, node_id?, target_id?}` | The catalog's organization store owns Mac-local persistent order and pins. Row IDs come from `--sidebar list`; moves stay within one group and pin section and do not mutate daemon layouts or sessions.\nWorkspace row › **Copy Workspace ID** | `cmux vm tree --json` (`remote_workspace.id`) | `vm.tree` | ✅\nTerminal / browser / display row click, **Open** | `cmux surface open \u003cresource>` (reuses an open pane) / `cmux vm open \u003cm>/\u003cws>/\u003cterm>` | `surface.project` | ✅\nRow › **Open in New Tab** | `cmux surface open \u003cresource> --pane \u003cp> --tab` | `surface.project {placement: tab}` | ✅\nRow › **Open in New Pane** (a second pane) | `cmux surface open \u003cresource> --new` | `surface.project {reuse: false}` | ✅\nDrag a row onto a pane edge | `cmux surface open \u003cresource> --pane \u003cp> --left\\|…` | `surface.project {pane_id, direction}` | ✅\nTerminal row › **Kill Terminal…**, hover × | `cmux vm terminal close \u003cm> \u003cterm>` | `vm.terminal_close` | ✅ also closes every local pane showing it\nTerminals › detached row (greyed, \"detached\": still running, no tab on the machine shows it, so it is in no workspace folder); click re-attaches it in a pane, right-click › **Kill Terminal…** | `cmux vm tree` lists it in the final `terminals/` section, under its `(detached — …)` subgroup (`remote_views: []` in `--json`), `cmux surface open \u003cm>/terminal/\u003cterm>` / `cmux vm terminal close \u003cm> \u003cterm>` | `surface.project` / `vm.terminal_close` | ✅\nRow › **Copy Surface ID** / **Copy Port** | `cmux surface ls --json` (`id`, `port`) | `surface.ls` | ✅\nPort row (when shown) click | `cmux vm open \u003cm>:port/\u003cn>` / `cmux vm open \u003cm> \u003cn> [--print]` | `vm.port_open` | ✅\n(no menu verb — the workspace's shape as data) | `cmux vm layout export \u003cm> \u003cws>` | `vm.exec` → in-VM `cmux layout export` | ✅ the same LayoutDocument the row renders, in the `cmux new-workspace --layout` / `cmux layout get` schema\n(no menu verb — an agent builds the workspace the row will open) | `cmux vm layout apply \u003cm> \u003cfile> [--name n] [--open]` | `vm.exec` → in-VM `cmux layout apply`, then `vm.workspace_open` for `--open` | ✅ builds panes/splits/tabs on the machine; `--open` is exactly the row click\n(no menu verb — project secrets) | `cmux vm env set\\|ls\\|rm \u003cm> …` | `vm.exec` → in-VM `cmux env` | ✅ machine-local, sourced by every shell cmux starts\n(no menu verb — CLI only) | `cmux vm pause \u003cm>` / `cmux vm resume \u003cm>` | `vm.pause` / `vm.resume` | ✅ parks and wakes a machine; the sidebar shows the state\n(no menu verb — agent-only) | `cmux vm terminal wait-exit\\|output \u003cm> \u003cterm> …` | `vm.terminal_wait_exit` / `vm.terminal_output` | ✅ exit code and full output stream, headless\n\nRules that keep it 1:1:\n\n- **One machine, many workspaces; four groups.** A machine is the big box; its cmux-tui workspaces are rows under it, never machines of their own. Under a connected machine, in this order: **Workspaces** (always its own row — with one workspace, with none, a \"No workspaces yet\" line under it — so its +, `cmux vm workspace new`, is one click away; a workspace row shows its own name, never a folded \"Workspaces / name\" breadcrumb, and lists exactly its layout: the terminals with a tab in it, its browsers, its screen), **Ports**, **VNC Displays** (one row per screen), and last, its own section, **Terminals** (every terminal resource the machine owns, one row per identity, badge = daemon tabs, detached ones greyed; always present so its + is New Terminal, \"No terminals yet\" under it when empty). `cmux vm tree` prints the same sequence — workspaces, ports, one row per VNC display, then the final `terminals/` section (with its detached subgroup); the Terminals section is the sidebar's flat index of what those lines already name. A daemon browser with neither a workspace tab nor a port has no group of its own.\n- **A mirrored local workspace edits its machine workspace.** A local workspace bound to a machine workspace (`vm workspace open`, `workspace.cloud_vm_bind`) is that workspace's view: moving a pane into it re-parents the tab (`tab.move`; a pool terminal gets a tab with `terminal project`), closing a terminal pane in it closes the tab (`tab.close`: the terminal detaches, only `terminal close` kills), and closing the local workspace (⌘⇧W, window close) leaves the machine workspace untouched. Viewer panes in unbound workspaces never touch the layout (`CloudPlacementCoordinator`).\n- **A workspace folder is its layout.** A terminal whose tab in the workspace closed (or whose workspace closed with `vm workspace close`) has left the workspace: no row lingers under it. It keeps running on the machine, so the Terminals group still lists it, greyed as \"detached\", where a click re-attaches it and Kill Terminal… ends it. Exited records with stale unresolved tab ids remain ordinary exited rows, not detached ones.\n- A sidebar verb is implemented as a closure in `CloudTreeNodeActions` that calls the catalog/provider; the matching socket handler in `SurfaceSocketCommands` calls the same catalog/provider method. Adding a sidebar verb without a socket method is a parity bug.\n- `--focus false` (same as `--no-focus`, and the default when an agent or script runs the command) means the same on `vm new`, `vm base open`, and `vm open`: open the surface where it belongs, never select its workspace or move keyboard focus out of the workspace the person is in (a pane is still focused when its workspace is the one already on screen). The New Machine / Set Up Base sheets always create this way; the success notification's click goes to the new workspace.\n- Placement flags mean the same everywhere: `--pane \u003cp>` + side = split that pane on that side; `--tab` / `--tabs` = tabs in that pane; nothing = the focused pane of the current (or `--workspace`) local workspace; `--new` = never reuse a pane that already shows the surface.\n- Ports are a first-class group in the tree when a machine exposes listening ports; the CLI and sidebar use the same port-open path. A canonical `browser/port:\u003cn>` resource stays in the owning machine's Ports group even when the daemon also reports it inside a cloud workspace, where the workspace pointer uses the same resource id.\n- Agent-only primitives (`cmux vm terminal send|read|wait` → `vm.terminal_write|read|wait`, plus `exec`, `push`, `pull`, `route`, `run`, `agent`, `layout export|apply`, `env`) have no sidebar verb by design: a person does those things by typing into a pane or by arranging panes. They still go through the machine's `CmuxTuiSurfaceProvider`, so what an agent types headlessly shows up in every pane projecting that terminal.\n\nExisting machine-to-machine links are agent-only: the source machine's in-VM\n`cmux vm` shim uses the remote-daemon verbs without a control-plane credential.\nThis build cannot create new peer grants; the old `vm link` enrollment broker\nis not part of the trusted private-network listener flow.\nInside a machine the shim also speaks the Mac's own\nspellings for its local session (`cmux send-key`, `cmux terminal send|read|wait`,\n`cmux new-workspace`, `cmux layout …`, `cmux env …`, `cmux tree`), so an agent in the\ncloud drives its machine — and, through a link, a peer machine — with the verbs it\nalready knows from the Mac.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":13925,"content_sha256":"3b10b685ae55df1210efc3f08d271341b01b0926e91002073f957b04e86de059"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"cmux Cloud machines","type":"text"}]},{"type":"paragraph","content":[{"text":"Use an existing machine and a workspace for the task. Machine terminals persist when panes close or the Mac disconnects. Work headlessly; open a pane or share a URL when there is a result for the user to inspect.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Start with discovery","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"sh"},"content":[{"text":"cmux vm --help\ncmux auth status\ncmux vm ls --json\ncmux vm route --json","type":"text"}]},{"type":"paragraph","content":[{"text":"On the Mac, host operations need the app, sign-in and its private tunnel. Inside a machine, use ","type":"text"},{"text":"cmux self --json","type":"text","marks":[{"type":"code_inline"}]},{"text":" and the guest's help; read ","type":"text"},{"text":"guest operations","type":"text","marks":[{"type":"link","attrs":{"href":"references/guest.md","title":null}}]},{"text":" for its supported subset and auth. Installed help is authoritative when versions differ.","type":"text"}]},{"type":"paragraph","content":[{"text":"route","type":"text","marks":[{"type":"code_inline"}]},{"text":" inspects placement without creating a machine. Read ","type":"text"},{"text":"would_provision","type":"text","marks":[{"type":"code_inline"}]},{"text":" and the backend's ","type":"text"},{"text":"limits","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"capabilities","type":"text","marks":[{"type":"code_inline"}]},{"text":"; do not assume a plan cap, memory size or provider feature. Reuse the router's pool machine, or explicitly pin an existing target with ","type":"text"},{"text":"--machine \u003cid>","type":"text","marks":[{"type":"code_inline"}]},{"text":". Base is the user's persistent work. Prefer another workspace on a machine to another machine.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Run and observe","type":"text"}]},{"type":"paragraph","content":[{"text":"After identifying an authorized target, choose the operation:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Need","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Start here","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bounded command and exit code","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cmux vm run --machine \u003cid> -- \u003ccommand>","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Detached coding agent","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cmux vm agent --machine \u003cid> --agent codex --no-open -- \"\u003ctask>\"","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Project with a dev layout","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cmux vm dev \u003cid> --dry-run --json","type":"text","marks":[{"type":"code_inline"}]},{"text":", then the approved plan","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Observe without opening panes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cmux vm tree \u003cid> --json","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"cmux vm terminal read \u003cid> \u003cterm>","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Completion and full output","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cmux vm terminal wait-exit \u003cid> \u003cterm>","type":"text","marks":[{"type":"code_inline"}]},{"text":", then ","type":"text"},{"text":"terminal output","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"paragraph","content":[{"text":"Read the selected verb's ","type":"text"},{"text":"--help","type":"text","marks":[{"type":"code_inline"}]},{"text":" before adding flags. Retain the machine, workspace/terminal identity and actual exit result. A finished wait is not proof that tests passed. Use ","type":"text"},{"text":"vm open","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"workspace open","type":"text","marks":[{"type":"code_inline"}]},{"text":" to present verified results; closing a view does not stop the machine terminal.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Constraints that apply throughout","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Provisioning, forking, resetting, resizing and destructive cleanup need the user's authorization for the target and effect; retain authorization already given. Do not delete machines to make capacity or reset Base as a workaround.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Keep account/upstream tokens on the host. Do not copy the user's credentials into a machine unless requested. Use the secret/env transfer paths when authorized; values do not belong in layout JSON, logs or command arguments.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"--no-open","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--detach","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"--print","type":"text","marks":[{"type":"code_inline"}]},{"text":" while working. Focus changes are intentional. ","type":"text"},{"text":"workspace rm","type":"text","marks":[{"type":"code_inline"}]},{"text":" kills its terminals; ","type":"text"},{"text":"workspace close","type":"text","marks":[{"type":"code_inline"}]},{"text":" detaches them.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Read only the relevant detail","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Workflow recipes","type":"text","marks":[{"type":"link","attrs":{"href":"references/agent-workflows.md","title":null}}]},{"text":": project setup, interactive terminals, long runs, peer agents, forks and service publication.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Command reference","type":"text","marks":[{"type":"link","attrs":{"href":"references/commands.md","title":null}}]},{"text":": exact flags, JSON/exit contracts, file/env transfer, layouts, limits and capabilities. Search its task index first.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Guest operations","type":"text","marks":[{"type":"link","attrs":{"href":"references/guest.md","title":null}}]},{"text":": host/guest grammar, CodeRouter, peer access, mirror behavior, notifications and browser authentication.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Sidebar parity","type":"text","marks":[{"type":"link","attrs":{"href":"references/sidebar-parity.md","title":null}}]},{"text":": map a specific UI action to CLI.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Local workspace rules","type":"text","marks":[{"type":"link","attrs":{"href":"../cmux-workspace/SKILL.md","title":null}}]},{"text":": presenting cloud work without disrupting the caller's workspace.","type":"text"}]}]}]}]},"metadata":{"date":"2026-11-02","name":"cmux-cloud-vm","author":"@skillopedia","source":{"stars":28651,"repo_name":"cmux","origin_url":"https://github.com/manaflow-ai/cmux/blob/HEAD/skills/cmux-cloud-vm/SKILL.md","repo_owner":"manaflow-ai","body_sha256":"a3642e47e8c208dd6fd4ed4e0954b14db4bed5732fb45d59d893e4a6d45e8199","cluster_key":"14cc95891cc2d7d1cba70bc642ea87b904d801301bfe0a79b3fc59d43b7bbc84","clean_bundle":{"format":"clean-skill-bundle-v1","source":"manaflow-ai/cmux/skills/cmux-cloud-vm/SKILL.md","text_bytes":109308,"source_commit":"21dcc2b27b2ed014d723067ed253702e914d97ca","skills_sh_rank":14642,"attachment_count":5,"text_attachments":5,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"skills/cmux-cloud-vm/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"devops-infrastructure","category_label":"DevOps"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"devops-infrastructure","import_tag":"skills-sh-top20k-2026-11","description":"Operate cmux Cloud machines, run durable remote commands or agents, and present their workspaces or services. Use for cmux vm/cloud tasks; backend implementation belongs to cmux-backend."}},"renderedAt":1793671541417}

cmux Cloud machines Use an existing machine and a workspace for the task. Machine terminals persist when panes close or the Mac disconnects. Work headlessly; open a pane or share a URL when there is a result for the user to inspect. Start with discovery On the Mac, host operations need the app, sign-in and its private tunnel. Inside a machine, use and the guest's help; read guest operations for its supported subset and auth. Installed help is authoritative when versions differ. inspects placement without creating a machine. Read and the backend's / ; do not assume a plan cap, memory size or p…