Help us improve
Share bugs, ideas, or general feedback.
From claude-commands
Controls another cmux terminal tab via Unix socket API: sends text, looks up workspaces and surfaces by name (not index), checks idle state, and creates workspaces. Useful for steering a headless coding agent without switching focus.
npx claudepluginhub jleechanorg/claude-commands --plugin claude-commandsHow this skill is triggered — by the user, by Claude, or both
Slash command
/claude-commands:cmux-steerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
**Usage**: Read and follow this skill directly; no `/cmux-steer` slash command is defined.
Controls cmux tabs, workspaces, and terminal panes via Unix socket. Use for reading terminal output, sending commands to another agent's pane, switching tabs, or monitoring coder progress.
Controls cmux terminal topology (windows, workspaces, panes, surfaces), sends notifications, and updates sidebar metadata. Useful for automation needing deterministic placement, progress reporting, or navigation in multi-pane cmux layouts.
Controls cmux macOS terminal multiplexer: manages workspaces/panes/surfaces/panels, sends commands to terminals/browsers, automates browsers, notifications, sidebar metadata. Use on /cmux or cmux requests.
Share bugs, ideas, or general feedback.
Usage: Read and follow this skill directly; no /cmux-steer slash command is defined.
Purpose: Read and steer another agent's terminal pane (e.g. a coding agent) from within cmux, without disrupting the user's active workspace navigation.
# Release build
SOCK="$HOME/Library/Application Support/cmux/cmux.sock"
# Dev builds — find via saved path files or lsof:
cat ~/Library/Application\ Support/cmux/dev-may-18-last-socket-path
# → /tmp/cmux-debug-may-18.sock
lsof -p $(pgrep -f "cmux DEV may-18") | grep -E "\.sock"
# Use CMUX_SOCKET_PATH to target a specific build with the CLI:
CMUX_SOCKET_PATH=/tmp/cmux-debug-may-18.sock cmux list-workspaces
The user can switch workspaces at any time, shifting surface indices. Never hardcode an index. Always look up by the workspace's display name:
# List all workspaces with names
printf "list_workspaces\n" | nc -U $SOCK
# Example output:
# 0: D267DC10-... cmux: ubuntu
# * 1: 9075D919-... exp: statusline ← user is here; doesn't matter
# 2: 258EB4B4-... o: mctrl
# Find target workspace UUID by name
WS_UUID=$(printf "list_workspaces\n" | nc -U $SOCK \
| grep "cmux: ubuntu" | grep -oE '[A-F0-9-]{36}')
# List surfaces in that workspace
printf "list_surfaces $WS_UUID\n" | nc -U $SOCK
# Output:
# * 0: 87DB76A9-... supervisor (cmux)
# 1: F05FCE84-... cmux_coder
# Extract coder surface UUID by label (never by index — indices shift)
CODER_UUID=$(printf "list_surfaces $WS_UUID\n" | nc -U $SOCK \
| grep "cmux_coder" | grep -oE '[A-F0-9-]{36}')
The plain-text send_surface command FAILS cross-workspace. Always use the JSON API:
| Method | Cross-workspace? | Notes |
|---|---|---|
surface.send_text (JSON) | ✅ always works | Include \n in text for enter |
send_surface (plain text) | ❌ fails cross-workspace | Only works if workspace is selected |
read_screen | ❌ current workspace only | Use index, not UUID |
# Single command: type + enter (append \n to submit)
printf '{"method":"surface.send_text","params":{"surface_id":"'"$CODER_UUID"'","text":"your instruction here\\n"}}\n' | nc -U $SOCK
This works from any workspace without switching focus. The \n at end acts as Enter.
# Send a harmless probe — if queued=false and ok=true, surface accepted input
result=$(printf '{"method":"surface.send_text","params":{"surface_id":"'"$CODER_UUID"'","text":""}}\n' | nc -U $SOCK)
# Check result.ok and result.queued
# Create workspace
printf '{"method":"workspace.create","params":{"title":"my-workspace"}}\n' | nc -U $SOCK
# Returns workspace_id
# Rename workspace
printf '{"method":"workspace.rename","params":{"workspace_id":"UUID","title":"new name"}}\n' | nc -U $SOCK
# Add a second tab/surface
printf '{"method":"surface.create","params":{"workspace_id":"UUID"}}\n' | nc -U $SOCK
# Returns surface_id
For key combos, fall back to plain text protocol (requires workspace focus):
printf "send_key_surface $CODER_UUID ctrl-c\n" | nc -U $SOCK
printf "send_key_surface $CODER_UUID ctrl-a\n" | nc -U $SOCK
printf "send_key_surface $CODER_UUID ctrl-k\n" | nc -U $SOCK
Available: ctrl-c, ctrl-d, enter, tab, escape, up, down, left, right
read_screen requires a surface index (not UUID) and only works when the target is in the user's currently focused workspace. Obtain the index from list_surfaces output (e.g. 1: F05FCE84-... cmux_coder → index is 1).
# IDX = index from list_surfaces for the focused workspace (replace with actual value)
IDX=1
printf "read_screen $IDX --lines 40\n" | nc -U $SOCK
printf "read_screen $IDX --lines 80 --scrollback\n" | nc -U $SOCK
If the user is in a different workspace, infer state from the filesystem instead:
cd /path/to/project && git log --oneline -5
ls -lt src/ # recently modified files
SOCK="/tmp/cmux-debug-appclick.sock"
# 1. Find coder surface by workspace name
WS_UUID=$(printf "list_workspaces\n" | nc -U $SOCK \
| grep "cmux: ubuntu" | grep -oE '[A-F0-9-]{36}')
CODER_UUID=$(printf "list_surfaces $WS_UUID\n" | nc -U $SOCK \
| grep "cmux_coder" | grep -oE '[A-F0-9-]{36}')
# 2. Send task headlessly (no workspace switch needed)
printf '{"method":"surface.send_text","params":{"surface_id":"'"$CODER_UUID"'","text":"cargo build && cargo test\\n"}}\n' | nc -U $SOCK
# 3. Monitor progress via filesystem (no read_screen needed)
sleep 30
cd /path/to/project && git log --oneline -3
SOCK="/tmp/cmux-debug-appclick.sock"
# Create workspace
WS=$(printf '{"method":"workspace.create","params":{"title":"my-test"}}\n' | nc -U $SOCK \
| python3 -c "import sys,json; print(json.loads(sys.stdin.read())['result']['workspace_id'])")
# Rename it
printf '{"method":"workspace.rename","params":{"workspace_id":"'"$WS"'","title":"test: a/b"}}\n' | nc -U $SOCK
# Add second tab
TAB2=$(printf '{"method":"surface.create","params":{"workspace_id":"'"$WS"'"}}\n' | nc -U $SOCK \
| python3 -c "import sys,json; print(json.loads(sys.stdin.read())['result']['surface_id'])")
# Get first tab
TAB1=$(printf "list_surfaces $WS\n" | nc -U $SOCK | grep "0:" | grep -oE '[A-F0-9-]{36}')
# Send commands to both tabs headlessly
printf '{"method":"surface.send_text","params":{"surface_id":"'"$TAB1"'","text":"echo tab1\\n"}}\n' | nc -U $SOCK
printf '{"method":"surface.send_text","params":{"surface_id":"'"$TAB2"'","text":"echo tab2\\n"}}\n' | nc -U $SOCK
select_workspace — it switches the user's visible workspace.list_workspaces → grep name → get UUID).surface.send_text works headlessly cross-workspace.\n to text — acts as Enter key submission.read_screen requires being in the right workspace; use git/filesystem for cross-workspace monitoring.workspace.create + surface.create for multi-tab setups.