Install
$ agentstack add skill-jtsternberg-claude-plugins-using-cmux-cli ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
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 →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 refs —
window: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 likesurface:318names 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_*_IDenv vars are already UUIDs (CMUX_SURFACE_ID,CMUX_WORKSPACE_ID,CMUX_TAB_ID), andcmux identify --jsonreturns your caller/focused UUIDs. - Anything else:
cmux tree --all --json --id-format uuids(or--id-format bothto 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:
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).cmux tree --all --json --id-format uuids→ collect the target UUIDs.- Exclude your own UUID from step 1.
- 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--workspacefor every commandCMUX_SURFACE_ID— default--surfaceCMUX_TAB_ID— default--tabfortab-actionCMUX_SOCKET_PATH— override the socket location (default:~/Library/Application Support/cmux/cmux.sock; the official docs also mention/tmp/cmux.sockon some builds — trust whatevercmux identifyreports)CMUX_SOCKET_PASSWORD— socket auth;--passwordflag > this env var > Settings-stored passwordCMUX_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), oroff. Also acceptscmux-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=allowAllis 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 sshremote workspaces (browser traffic routing, drag-drop upload, remote agents, reconnect, daemon troubleshooting). - [references/layouts.md](references/layouts.md) — the
cmux layoutgeometry API (the split-tree JSON, save/get/open/delete, and rebuilding a whole workspace withnew-workspace --layout). Read it whenever you need split orientation, divider ratios, nesting, or exact layout reproduction —treecannot 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.
Write a review
Versions
- v0.1.0 Imported from the upstream source.