| name | cmux-steer |
| description | Read and steer another cmux terminal tab through the Unix socket. |
cmux-steer — Control another cmux terminal tab via the Unix socket
REQUIRED (2026-06-09): This skill is deprecated for runtime steering. The bare cmux send + cmux send-key enter pattern documented here does NOT include proof of submission. Use the canonical wrapper instead: python3 -c "from cmux_client import send_and_submit; print(send_and_submit('workspace:N', 'surface:M', 'text'))" and include the returned proof + proof_ts in your reply. See ~/.hermes_prod/skills/cmux-send-submit/SKILL.md. This skill is preserved for socket read operations (workspace.list, surface.read_text, tree, system.ping) only.
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.
Socket path
SOCK="$HOME/Library/Application Support/cmux/cmux.sock"
cat ~/Library/Application\ Support/cmux/dev-may-18-last-socket-path
lsof -p $(pgrep -f "cmux DEV may-18") | grep -E "\.sock"
CMUX_SOCKET_PATH=/tmp/cmux-debug-may-18.sock cmux list-workspaces
Rule 1: Always find workspace by NAME, not index
The user can switch workspaces at any time, shifting surface indices. Never
hardcode an index. Always look up by the workspace's display name:
printf "list_workspaces\n" | nc -U $SOCK
WS_UUID=$(printf "list_workspaces\n" | nc -U $SOCK \
| grep "cmux: ubuntu" | grep -oE '[A-F0-9-]{36}')
printf "list_surfaces $WS_UUID\n" | nc -U $SOCK
CODER_UUID=$(printf "list_surfaces $WS_UUID\n" | nc -U $SOCK \
| grep "cmux_coder" | grep -oE '[A-F0-9-]{36}')
Rule 2: Use JSON API for headless cross-workspace sends
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 |
Sending text (headless, cross-workspace)
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.
Checking if idle
result=$(printf '{"method":"surface.send_text","params":{"surface_id":"'"$CODER_UUID"'","text":""}}\n' | nc -U $SOCK)
Creating workspaces and surfaces
printf '{"method":"workspace.create","params":{"title":"my-workspace"}}\n' | nc -U $SOCK
printf '{"method":"workspace.rename","params":{"workspace_id":"UUID","title":"new name"}}\n' | nc -U $SOCK
printf '{"method":"surface.create","params":{"workspace_id":"UUID"}}\n' | nc -U $SOCK
Special keys (when \n in text isn't enough)
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
Reading a pane's screen (current workspace only)
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=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/
Full headless example
SOCK="/tmp/cmux-debug-appclick.sock"
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}')
printf '{"method":"surface.send_text","params":{"surface_id":"'"$CODER_UUID"'","text":"cargo build && cargo test\\n"}}\n' | nc -U $SOCK
sleep 30
cd /path/to/project && git log --oneline -3
Creating a fresh workspace with two tabs
SOCK="/tmp/cmux-debug-appclick.sock"
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'])")
printf '{"method":"workspace.rename","params":{"workspace_id":"'"$WS"'","title":"test: a/b"}}\n' | nc -U $SOCK
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'])")
TAB1=$(printf "list_surfaces $WS\n" | nc -U $SOCK | grep "0:" | grep -oE '[A-F0-9-]{36}')
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
Rules summary
- Never use
select_workspace — it switches the user's visible workspace.
- Find workspace by NAME (
list_workspaces → grep name → get UUID).
- Use JSON API for sends —
surface.send_text works headlessly cross-workspace.
- Append
\n to text — acts as Enter key submission.
- Read by index only —
read_screen requires being in the right workspace; use git/filesystem for cross-workspace monitoring.
- Create workspaces via JSON —
workspace.create + surface.create for multi-tab setups.