AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Using Cmux Cli

skill-jtsternberg-claude-plugins-using-cmux-cli · by jtsternberg

Drives cmux (macOS terminal multiplexer) via the `cmux` CLI — windows, workspaces, panes, tabs, sending keystrokes and reading terminal output, embedded browser, notifications, layouts.

No reviews yet
0 installs
20 views
0.0% view→install

Install

$ agentstack add skill-jtsternberg-claude-plugins-using-cmux-cli

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution Used
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-jtsternberg-claude-plugins-using-cmux-cli)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
5d ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Using Cmux Cli? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

cmux CLI

Drive cmux (cmux) from the command line: windows, workspaces, panes, surfaces, tabs, terminal I/O, notifications, and the embedded browser. The CLI talks to the running cmux.app over a Unix socket.

This skill only applies when the user is working with cmux specifically — not generic terminal multiplexers like tmux or screen.

The golden rule: let --help be the source of truth

cmux ships frequently and its flags evolve. Rather than mirroring option lists into this skill (which bitrots), run cmux --help before constructing any real invocation. The top-level overview below is inlined live; for everything else, call cmux --help on demand. That single rule replaces a dozen "remember to pass --foo" footnotes.

Current environment (resolved at skill load)

```! cmux identify --json 2>/dev/null || echo '{"error":"cmux identify failed — see Troubleshooting"}'


`identify` returns three things:

- `caller.*` — your own surface/workspace/window/pane refs (where the agent is running). Use these as defaults when the user says "here" / "this pane".
- `focused.*` — where the **user** is currently looking. Often *different* from `caller`. When the user says "do that in the other tab I'm looking at", target `focused` explicitly.
- `socket_path` — the actual socket the CLI is talking to.

If `identify` fails, you're either outside cmux or the socket is unreachable — see Troubleshooting. When outside cmux, every targeted command needs explicit handles; discover them with `cmux tree --all --json`.

## Is cmux actually running?

```!
cmux ping

Silent success means yes. An error means the app isn't running, the socket path is wrong, or auth is misconfigured. See Troubleshooting.

Overview: every subcommand at a glance

```! cmux --help


This is the master index. Every subcommand appears here with its full signature — enough to construct most invocations on sight. For anything non-obvious, drill in with `cmux  --help`.

Capabilities of the *running* build (what flags the current app actually supports):

```!
cmux capabilities

Handle conventions (read before passing IDs)

Target by UUID. Always resolve a handle to its UUID and pass that UUID to any command that acts on it. UUIDs are permanent identities for a window/workspace/pane/surface — they name the same object for the life of that object, across every command and every session.

cmux accepts three handle formats anywhere a window, workspace, pane, or surface flag appears. Understand all three, and reach for the first:

  • UUIDs — full identifiers, stable for the object's lifetime. This is what you pass to commands.
  • Short refswindow:1, workspace:2, pane:3, surface:4. These are positional display labels: an index within scope that cmux reassigns as objects open and close. A ref like surface:318 names whatever currently sits in that slot, which may be a different object a moment later. Treat refs as human-readable output to show the user, and as throwaway input only within a single read that you immediately act on — resolve to a UUID first whenever the handle outlives one command.
  • Bare indexes — same positional, reassignable nature as short refs.

tab-action additionally accepts tab: (also positional).

Get UUIDs like this:

  • Your own location: the CMUX_*_ID env vars are already UUIDs (CMUX_SURFACE_ID, CMUX_WORKSPACE_ID, CMUX_TAB_ID), and cmux identify --json returns your caller/focused UUIDs.
  • Anything else: cmux tree --all --json --id-format uuids (or --id-format both to see refs alongside for a human). Snapshot once, read the UUIDs you need, then target by those.

Destructive and bulk operations: resolve UUIDs up front

For anything that mutates or removes state — close-surface, close-workspace, close-window, swap-pane, move-surface, or any loop over several targets — collect every target UUID in one tree --json --id-format uuids snapshot first, then act by UUID. This is the affirmative rule that keeps bulk operations correct:

  1. cmux identify --json → note your own surface/pane UUID, so you can keep it out of the target set (closing the surface your agent runs in kills the agent's tty — the process goes down with the pane).
  2. cmux tree --all --json --id-format uuids → collect the target UUIDs.
  3. Exclude your own UUID from step 1.
  4. Act on each by UUID.

Because UUIDs are permanent, acting on one never changes what the others refer to — so a close-loop stays aimed at exactly the objects you chose. (Positional refs renumber as each object closes, so a ref captured before the loop can point somewhere new mid-loop — including your own pane. UUIDs are immune to that.)

--json is per-command, not global

The official API docs list --json under "CLI options" as if it were global. In the installed build it isn't: cmux tree --all --json and cmux identify --json return structured JSON; cmux list-workspaces --json silently ignores the flag and prints text. When you need JSON, prefer tree (hierarchy) or identify (current context) and filter with jq. Run --json as a quick sniff test — if the output looks like text, it's not supported there.

Environment variables

cmux auto-populates these in every terminal it spawns:

  • CMUX_WORKSPACE_ID — default --workspace for every command
  • CMUX_SURFACE_ID — default --surface
  • CMUX_TAB_ID — default --tab for tab-action
  • CMUX_SOCKET_PATH — override the socket location (default: ~/Library/Application Support/cmux/cmux.sock; the official docs also mention /tmp/cmux.sock on some builds — trust whatever cmux identify reports)
  • CMUX_SOCKET_PASSWORD — socket auth; --password flag > this env var > Settings-stored password
  • CMUX_SOCKET_ENABLE — force-enable or disable the socket entirely (1/0/true/false/on/off)
  • CMUX_SOCKET_MODE — access mode: cmuxOnly (default; only cmux-spawned processes connect), allowAll (any local process), or off. Also accepts cmux-only / allow-all. If you're invoking cmux from a process that wasn't spawned by cmux (CI, non-cmux shell, foreign wrapper) and getting connection refused, CMUX_SOCKET_MODE=allowAll is the usual fix — but understand the implication: any local process gains control of cmux.

Reference material (load on demand)

Two subsystems live in separate files to keep this skill lean. Read them only when a task actually involves them:

  • [references/browser.md](references/browser.md) — embedded browser automation (navigate, click, type, snapshot/screenshot, cookies, storage, eval, waits, locators).
  • [references/ssh.md](references/ssh.md) — cmux ssh remote workspaces (browser traffic routing, drag-drop upload, remote agents, reconnect, daemon troubleshooting).
  • [references/layouts.md](references/layouts.md) — the cmux layout geometry API (the split-tree JSON, save/get/open/delete, and rebuilding a whole workspace with new-workspace --layout). Read it whenever you need split orientation, divider ratios, nesting, or exact layout reproduction — tree cannot give you any of those.

Default principle: make new work visible to the user

When the user asks you to open anything — an ssh session, a new terminal, a dev server, a browser — they almost always want to see it alongside what they're already looking at. They're sitting in their cmux window watching you work. If you open the new thing in a separate workspace tab or a new OS window, they have to stop watching to go find it — and at that point, they might as well have done the work themselves.

The default routing

For any "open X" / "start X" / "ssh to X" / "run Y in a new terminal" request that doesn't specify a destination, route through the side-by-side workflow below. Don't reach for cmux new-workspace, cmux new-window, or bare cmux ssh — those spawn in places the user can't see without switching context.

**Fresh surfaces need a moment before their PTY accepts input — sending immediately can drop your \n (the shell's "Last login" banner prints after your typed command, swallowing the newline, leaving the command sitting at the prompt unexecuted).** Use --wait-ready on open-side-surface.sh and the script handles both this and the "Terminal surface not found" PTY-attach race internally.

The one-call recipe:

# 1. Open a sibling surface next to the user's current view, NAME it, AND wait
#    until its PTY is attached + shell is actually executing input. --title is
#    what makes the tab findable (see "Name it, then report it by name" below);
#    --wait-ready round-trips a probe (whose send is what attaches the PTY, with
#    no focus steal), exiting 3 with a diagnostic rather than returning a
#    non-ready ref.
# Codex: this path resolves under Claude Code; substitute the directory containing this SKILL.md.
SKILL_DIR="${CLAUDE_SKILL_DIR}"
OUT=$("$SKILL_DIR/scripts/open-side-surface.sh" \
        --wait-ready --title "dev server :3000" --json)
SID=$(jq -r '.surface_id'      **Manual fallback** (historical — most callers should use `--wait-ready`):
> if you can't use the script, poll for readiness yourself by sending an
> `echo ` probe and grepping `read-screen` for ≥2 hits of the
> marker (typed input + executed output). Don't rely on a `[$%#>]` prompt
> regex — it silently misses powerline / NerdFont prompts (`❯`, `➜`, custom
> glyphs) and spins to its retry ceiling. Clear the input line with a raw
> `Ctrl-U` (`cmux send --surface "$SID" $'\025'`) before each probe — the line is
> shared with the user, whose keystrokes would otherwise prepend to it. If
> `read-screen` errors before you have sent anything, that is expected: **the
> send is what attaches the PTY.** Probe first, then read; do not reach for
> `cmux focus-pane`, which attaches by stealing the user's focus.

Use `--focused` on `open-side-surface.sh` when the user says "next to the tab I'm looking at" instead of "next to yours" — the defaults diverge when the user is viewing a different tab than the one the agent lives in.

### Name it, then report it by name (required)

Visibility isn't just "the surface is on screen" — it's "the user can *find* the thing you opened." Two rules, both mandatory for **any** surface, pane, or workspace you create:

**1. Give it a meaningful human-visible title.** A fresh surface inherits a generic auto-title — `zsh`, the cwd basename, or the workspace's own name — which is indistinguishable from every other tab. Name it for the activity, 2–5 words, no trailing punctuation (`dev server :3000`, `ssh prod-web1`, `tail nginx logs`, `pytest watch`). Never leave it as the tool (`zsh`, `node`, `claude`) or as a placeholder word like `workspace` or `test`. The companion `auto-rename` skill has the full naming rubric.

```bash
# Codex: this path resolves under Claude Code; substitute the directory containing this SKILL.md.
SKILL_DIR="${CLAUDE_SKILL_DIR}"
# Preferred — one call, title applied at creation:
"$SKILL_DIR/scripts/open-side-surface.sh" --wait-ready --title "dev server :3000" --json

# Any other creation path — name it immediately afterward:
cmux rename-tab --workspace  --tab  "dev server :3000"
cmux rename-workspace --workspace  "lindris frontend"   # if you created the workspace

2. Report the title plus its workspace — never a bare positional ref. surface:258 is an internal handle: cmux's UI never displays it, it renumbers as tabs open and close, and the user has no way to search for it. What locates a tab for a human is its title + the workspace name it lives under (+ which window, when more than one cmux window is open — cmux list-windows; note cmux exposes no window title via the CLI, so anchor a window by the workspace it's currently showing).

✅ Opened "dev server :3000" in the "lindris frontend" workspace, right of your pane.
❌ Opened surface:258.

Real failure this prevents: an agent opened a test surface whose title and workspace were both the generic word "workspace", then reported only surface:258. Correct handle, zero information — the user could not find the tab, and the report was useless.

If the names themselves aren't distinguishing, that's still a failure. A title or workspace name that is generic (workspace, zsh, test) or duplicated across several tabs is not a locator. Fix it rather than reporting it: rename the surface, rename the workspace too if you created it, or — if it's a pre-existing workspace you shouldn't rename — give the user a concrete visible anchor instead ("the tab immediately right of the one you're in, in the window showing cmb security"). Verify with cmux tree --workspace that the title you intended is the title that's actually there before you report it.

When to break the default (escape hatches)

Flip to a separate-workspace path only when the user explicitly asks for it. Trigger phrases:

  • "in a new workspace" / "as its own tab" → cmux new-workspace --name "..." [--cwd ] [--command "..."]
  • "in a separate window" / "open a new cmux window" → cmux new-window
  • "open a full cmux ssh workspace to X" / "I need drag-drop / remote browser / relay for agent X" → cmux ssh (see [references/ssh.md](references/ssh.md))

When you pick a hidden path deliberately, tell the user the new surface isn't visible yet and how to reach it (tab switch, window focus). Surprise hiding is the failure mode this section exists to prevent. Naming matters more on these paths, not less — a hidden tab is only reachable if it has a name: always pass --name to new-workspace / cmux ssh, then name the surface inside it, and give the user both names in your report ("switch to the deploy staging workspace, tab ansible run").

The cmux ssh decision

cmux ssh does a lot — relay daemon, browser routing through the remote's network, drag-drop uploads via scp, remote cmux calls relayed to your local sidebar — but it always creates a new workspace, so the session is behind a tab the user has to switch to. Two paths:

| User's intent | Right tool | Why | |---------------|-----------|-----| | "ssh to host X to check a log / run a command / poke around" | side-by-side + plain ssh (default recipe above) | Visible immediately; plain SSH is enough for read/send/observe. | | "ssh to host X and run a coding agent" / "I need to drag files to the remote" / "I want the browser to hit the remote's localhost" | cmux ssh (+ warn user it opens in a new workspace) | Needs the relay daemon and workspace integration. Worth the tab-switch cost. |

If the user's request is ambiguous ("ssh into host X"), default to the visible side-by-side path. Plain ssh in a split gives you everything you need to read output and send commands, and the user can actually see it happen.


High-frequency subcommands (inlined --help)

These are the commands you'll reach for constantly. Live help is pinned below so you can construct correct calls without a second round-trip.

Read the screen

```! cmux read-screen --help


Use `--scrollback --lines ` to grab history. Without them you only get the visible viewport — often omitting the command output you actually care about.

#### Gotcha: Claude Code's grayed-out next-prompt suggestion looks like real input

When the surface you're reading is running **Claude Code** (the `claude` CLI in its REPL), Claude Code renders an autosuggest hint inside its input box — a slightly-grayed string showing what it predicts the user might type next. In `cmux read-screen` output that hint appears as plain text on the prompt line, **indistinguishable from text the user actually typed**.

This has burned previous agents who were watching another claude session via `read-screen`: they saw the ghost suggestion, assumed *the user* had entered that command, and started narrating phantom actions ("the user just asked you to do X, and you'r

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [jtsternberg](https://github.com/jtsternberg)
- **Source:** [jtsternberg/claude-plugins](https://github.com/jtsternberg/claude-plugins)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.