cmux
cmux Terminal & Browser Management
Use this skill when you need to run processes in separate terminals you can observe and control — dev servers, test watchers, build processes, or any long-running task. Also use it to open browser surfaces for testing web pages, taking screenshots, clicking elements, and reading page content.
Prerequisite: You must be running inside cmux (check for CMUX_SOCKET_PATH
in the environment). If it's not set, these commands won't work.
Default approach: Prefer creating surfaces (tabs) in the current
workspace over spawning new workspaces. Tabs keep everything grouped together
and are less disruptive. Only use new-workspace when you need full isolation
(e.g., a completely separate project).
Environment Variables
cmux auto-sets these in every shell it spawns:
| Variable | Purpose |
|---|---|
CMUX_WORKSPACE_ID |
UUID of the current workspace |
CMUX_SURFACE_ID |
UUID of the current surface/panel |
CMUX_SOCKET_PATH |
Unix socket path (usually /tmp/cmux.sock) |
Commands run inside a cmux shell automatically target the right workspace
without needing --workspace.
Core Commands
Create a new tab (surface) in the current workspace
cmux new-surface --type terminal
# Returns: OK surface:<n> pane:<n> workspace:<n>
This is the preferred way to spawn a new shell. It creates a tab next to the current terminal in the same workspace.
Create a new split pane
cmux new-split <left|right|up|down>
cmux new-pane --direction <left|right|up|down> [--type terminal]
Spawn a new workspace (for full isolation)
cmux new-workspace [--cwd <path>] [--command "<text>"]
# Returns: OK workspace:<n>
Send commands
cmux send --surface <ref> '<command>\n'
The \n sends Enter. Without it, text is typed but not executed.
Read terminal output
cmux read-screen --surface <ref> [--lines <n>] [--scrollback]
- Default: visible screen only
--scrollback: include scrollback buffer--lines <n>: limit to last N lines (implies scrollback)
Close a surface / workspace
cmux close-surface --surface <ref>
cmux close-workspace --workspace <ref>
List workspaces and surfaces
cmux list-workspaces --json
cmux list-panels # List surfaces in current workspace
cmux tree --json # Full layout with all details
Notifications
cmux notify --title "<text>" --body "<text>"
Send special keys
cmux send-key --surface <ref> ctrl+c # Interrupt
cmux send-key --surface <ref> ctrl+d # EOF
cmux send-key --surface <ref> escape # Escape
Patterns
Pattern 1: Start a dev server in a new tab
# Create a tab and capture its surface ref
SURFACE=$(cmux new-surface --type terminal | awk '{print $2}')
sleep 0.5
# Send the command
cmux send --surface $SURFACE 'cd /path/to/project && npm run dev\n'
# Poll until ready
for i in $(seq 1 30); do
OUTPUT=$(cmux read-screen --surface $SURFACE --lines 20)
if echo "$OUTPUT" | grep -qi "ready\|listening\|started\|compiled"; then
echo "Server is ready"
break
fi
sleep 1
done
# ... do work against the server ...
# Clean up when done
cmux close-surface --surface $SURFACE
Pattern 2: Run tests in a tab and read results
SURFACE=$(cmux new-surface --type terminal | awk '{print $2}')
sleep 0.5
cmux send --surface $SURFACE 'cd /path/to/project && npm test\n'
sleep 10
cmux read-screen --surface $SURFACE --scrollback --lines 200
cmux close-surface --surface $SURFACE
Pattern 3: Interactive session — send multiple commands
SURFACE=$(cmux new-surface --type terminal | awk '{print $2}')
sleep 0.5
cmux send --surface $SURFACE 'git status\n'
sleep 1
cmux read-screen --surface $SURFACE --lines 30
cmux send --surface $SURFACE 'git log --oneline -5\n'
sleep 1
cmux read-screen --surface $SURFACE --lines 30
cmux close-surface --surface $SURFACE
Pattern 4: Monitor multiple processes
S_API=$(cmux new-surface --type terminal | awk '{print $2}')
S_WEB=$(cmux new-surface --type terminal | awk '{print $2}')
sleep 0.5
cmux send --surface $S_API 'cd ./api && npm run dev\n'
cmux send --surface $S_WEB 'cd ./web && npm run dev\n'
# Check on any of them
sleep 3
cmux read-screen --surface $S_API --lines 20
cmux read-screen --surface $S_WEB --lines 20
# Clean up
cmux close-surface --surface $S_API
cmux close-surface --surface $S_WEB
Pattern 5: Split pane for side-by-side view
cmux new-split right # Terminal split to the right
cmux new-split down # Terminal split below
Browser Surfaces
cmux has a built-in browser powered by Playwright. You can open web pages, interact with them, take screenshots, and read content — all from the CLI.
Key rule: Browser commands require --surface <ref> to target the right
browser surface. The surface ref comes from the browser open command output.
Without it, commands may fail or target the wrong surface.
Opening a Browser
# Open a URL — creates a browser split in the current workspace
cmux browser open "http://localhost:3000"
# Returns: OK surface=surface:42 pane=pane:7 placement=split
# Capture the surface ref for subsequent commands
BROWSER=$(cmux browser open "http://localhost:3000" | grep -oP 'surface=\Ksurface:\d+')
You can also create a browser as a tab or pane:
cmux new-surface --type browser --url "http://localhost:3000"
cmux new-pane --type browser --url "http://localhost:3000" --direction right
Navigation
cmux browser --surface $BROWSER navigate "http://localhost:3000/page"
cmux browser --surface $BROWSER back
cmux browser --surface $BROWSER forward
cmux browser --surface $BROWSER reload
cmux browser --surface $BROWSER url # Get current URL
Screenshots (visual verification)
# Save to file, then read the image
cmux browser --surface $BROWSER screenshot --out /tmp/page.png
# Then use: read /tmp/page.png
Snapshots (DOM tree — lightweight alternative to screenshots)
# Compact accessibility tree — great for understanding page structure
cmux browser --surface $BROWSER snapshot --compact
# Output: - document "Page Title"
# - button "Submit" [ref=e1]
# - input [ref=e2]
# Interactive snapshot (includes ref= attributes for click targets)
cmux browser --surface $BROWSER snapshot --interactive
# Scoped to a specific element
cmux browser --surface $BROWSER snapshot --selector "#main" --compact
Clicking & Interacting
Selectors use standard CSS syntax. Playwright-style text selectors
(text=, :has-text()) do NOT work — they cause JS exceptions.
# Click by CSS selector (this works)
cmux browser --surface $BROWSER click "button"
cmux browser --surface $BROWSER click "#submit-btn"
cmux browser --surface $BROWSER click ".btn-primary"
cmux browser --surface $BROWSER click "button[type='submit']"
cmux browser --surface $BROWSER click "nav a:first-child"
# ⚠️ These DO NOT work — they throw JS exceptions:
# cmux browser --surface $BROWSER click "text=Submit" # ❌
# cmux browser --surface $BROWSER click "button:has-text('Submit')" # ❌
To click a button by its text content, use a snapshot first to find
the right CSS selector, or use eval:
# Option 1: Use snapshot to find the element, then click by CSS
cmux browser --surface $BROWSER snapshot --compact
# Shows: - button "Generate" [ref=e3]
# Then click by position or more specific CSS selector
# Option 2: Use eval for text-based matching
cmux browser --surface $BROWSER eval "document.querySelector('button').click()"
cmux browser --surface $BROWSER eval "
Array.from(document.querySelectorAll('button'))
.find(b => b.textContent.trim() === 'Generate')
?.click()
"
Other interactions
cmux browser --surface $BROWSER type "input#name" "John"
cmux browser --surface $BROWSER fill "input#email" "a@b.com"
cmux browser --surface $BROWSER fill "input#email" # empty = clear
cmux browser --surface $BROWSER check "input[type='checkbox']"
cmux browser --surface $BROWSER uncheck "input[type='checkbox']"
cmux browser --surface $BROWSER select "select#country" "US"
cmux browser --surface $BROWSER press "Enter"
cmux browser --surface $BROWSER scroll --dy 500 # scroll down
cmux browser --surface $BROWSER hover ".menu-item"
cmux browser --surface $BROWSER focus "#search"
Reading Page Content
cmux browser --surface $BROWSER get url
cmux browser --surface $BROWSER get title
cmux browser --surface $BROWSER get text "h1" # text of element
cmux browser --surface $BROWSER get html "p" # innerHTML
cmux browser --surface $BROWSER get count "li" # number of matches
cmux browser --surface $BROWSER get value "input#name" # form value
cmux browser --surface $BROWSER get attr "a" "href" # attribute value
Waiting for Content
cmux browser --surface $BROWSER wait --selector ".loaded"
cmux browser --surface $BROWSER wait --text "Success"
cmux browser --surface $BROWSER wait --url-contains "/dashboard"
cmux browser --surface $BROWSER wait --load-state complete
cmux browser --surface $BROWSER wait --timeout-ms 10000 --selector "#result"
Console & Errors
cmux browser --surface $BROWSER console list # Show console messages
cmux browser --surface $BROWSER console clear
cmux browser --surface $BROWSER errors list # Show JS errors
cmux browser --surface $BROWSER errors clear
Evaluating JavaScript
cmux browser --surface $BROWSER eval "document.title"
cmux browser --surface $BROWSER eval "localStorage.getItem('token')"
cmux browser --surface $BROWSER eval "JSON.stringify(performance.timing)"
Injecting Scripts & Styles
cmux browser --surface $BROWSER addscript "window.DEBUG = true"
cmux browser --surface $BROWSER addstyle "body { outline: 1px solid red; }"
Pattern: Start Dev Server + Verify in Browser
# 1. Start dev server in a terminal tab
TERMINAL=$(cmux new-surface --type terminal | awk '{print $2}')
sleep 0.5
cmux send --surface $TERMINAL 'cd /path/to/project && npm run dev\n'
# 2. Wait for server to be ready
for i in $(seq 1 30); do
OUTPUT=$(cmux read-screen --surface $TERMINAL --lines 20)
if echo "$OUTPUT" | grep -qi "ready\|listening\|started\|compiled"; then
break
fi
sleep 1
done
# 3. Open browser and verify
BROWSER=$(cmux browser open "http://localhost:5173" | grep -oP 'surface=\Ksurface:\d+')
sleep 2
# 4. Take a screenshot to verify visually
cmux browser --surface $BROWSER screenshot --out /tmp/verify.png
# 5. Or use snapshot for quick text-based check
cmux browser --surface $BROWSER snapshot --compact
# 6. Check for JS errors
cmux browser --surface $BROWSER errors list
# 7. Clean up
cmux close-surface --surface $BROWSER
cmux close-surface --surface $TERMINAL
Pattern: Click Through a Flow
BROWSER=$(cmux browser open "http://localhost:3000" | grep -oP 'surface=\Ksurface:\d+')
sleep 2
# Fill a form
cmux browser --surface $BROWSER fill "input[name='email']" "test@example.com"
cmux browser --surface $BROWSER fill "input[name='password']" "secret123"
cmux browser --surface $BROWSER click "button[type='submit']"
# Wait for navigation
cmux browser --surface $BROWSER wait --url-contains "/dashboard" --timeout-ms 5000
# Verify we landed on the right page
cmux browser --surface $BROWSER get title
cmux browser --surface $BROWSER screenshot --out /tmp/dashboard.png
cmux close-surface --surface $BROWSER
Important Notes
- Prefer tabs over workspaces — use
new-surfaceto keep things grouped - Always clean up surfaces when done — don't leave orphaned terminals
- Use
--lineswith read-screen to avoid dumping huge scrollback buffers - Surface refs are ephemeral —
surface:16may refer to a different surface next time. Always capture the ref from command output - Poll, don't guess — there's no "wait for output" command, so poll
read-screenin a loop when waiting for specific output \nis literal — the cmux CLI interprets\nas a newline character insendcommands, which presses Enter- Browser
--surfaceis required — always pass--surface <ref>to browser subcommands. Without it, commands fail or target nothing - Browser selectors are CSS only —
text=Fooand:has-text()throw JS exceptions. Use standard CSS selectors orevalfor text matching - Use
snapshot --compactbefore interacting — it shows the page structure with element refs, so you know what CSS selectors to use - Prefer
screenshot --outoversnapshotfor visual verification — screenshots show exactly what the user would see
More from hazat/pi-config
self-improve
End-of-session retrospective that identifies improvements to agent config, tests, docs, and code. Use when asked to "self-improve", "reflect on session", "what can we improve", "session retrospective", "end of session review". Creates actionable todos from findings.
13learn-codebase
Discover project conventions and surface security concerns. Use when starting work in a new or unfamiliar project, when asked to "learn the codebase", "check project rules", "what are the conventions", "onboard to this project", or "anything shady in this codebase". Scans agent config files (.claude/, .cursor/, CLAUDE.md, etc.) and runs a security/smell sweep for hardcoded secrets, insecure patterns, suspicious dependencies, and dangerous configurations.
13add-mcp-server
Add an MCP server to pi. Use when asked to "add mcp server", "configure mcp", "add mcp", "new mcp server", "setup mcp", "connect mcp server", or "register mcp server". Handles both global and project-local configurations.
2skill-creator
Create new agent skills following the Agent Skills specification. Use when asked to "create a skill", "add a new skill", "write a skill", "make a skill", "build a skill", or scaffold a new skill with SKILL.md. Guides through requirements, planning, writing, registration, and verification.
2code-simplifier
Simplifies and refines code for clarity, consistency, and maintainability while preserving all functionality. Use when asked to "simplify code", "clean up code", "refactor for clarity", "improve readability", or review recently modified code for elegance. Focuses on project-specific best practices.
1