| name | cmux |
| description | cmux Terminal Multiplexer — Agent Integration. Use when orchestrating terminal sessions, running parallel commands, monitoring output, or reporting progress inside cmux. |
cmux Terminal Multiplexer — Agent Integration
Use when orchestrating terminal sessions, running parallel commands, monitoring output, or reporting progress inside cmux. Works for Claude Code, Cursor, and Codex.
Detection
Check for the CMUX_WORKSPACE_ID environment variable. If set, you are inside cmux and can use the cmux CLI. If unset, do NOT attempt any cmux commands.
The CLI binary is at /Applications/cmux.app/Contents/Resources/bin/cmux (also available as cmux on PATH inside cmux terminals).
Environment variables automatically set in cmux terminals:
CMUX_WORKSPACE_ID — current workspace ref
CMUX_SURFACE_ID — current surface ref
CMUX_SOCKET_PATH — Unix socket path (usually /tmp/cmux.sock)
Hierarchy
Window > Workspace (sidebar tab) > Pane (split region) > Surface (terminal tab in pane).
Use short refs: workspace:1, pane:1, surface:2.
Core Commands
Orientation
cmux identify --json
cmux list-workspaces
cmux list-panes
cmux list-pane-surfaces --pane <ref>
Create Terminals
cmux new-workspace --command "cd /path && cmd"
cmux new-split <left|right|up|down>
cmux new-surface
cmux new-pane --direction <dir>
Send Input / Read Output
cmux send --surface <ref> "text\n"
cmux send-key --surface <ref> <key>
cmux read-screen --surface <ref> --lines <n>
Progress Reporting (shows in cmux sidebar)
cmux set-status <key> <value> --icon <name> --color <#hex>
cmux set-progress <0.0-1.0> --label "text"
cmux log --level <info|success|warning|error> --source "agent" -- "message"
cmux notify --title "Title" --body "Body"
cmux clear-status <key>
cmux clear-progress
cmux clear-log
Workspace Management
cmux rename-workspace "name"
cmux rename-tab --surface <ref> "name"
cmux close-surface --surface <ref>
cmux close-workspace --workspace <ref>
Browser Panel Commands
cmux has a built-in browser engine. You can open web pages in splits/panes and interact with them programmatically — navigate, click, type, read DOM, take screenshots, etc.
All browser commands use the form: cmux browser <surface> <subcommand> [args...]
Open & Navigate
IMPORTANT: open-split --url is unreliable — the URL often fails to load on initial creation.
Always use a two-step approach: create the split first, then navigate separately with a small delay:
cmux browser <surface> open-split --direction <dir>
sleep 1 && cmux browser <new-surface> navigate <url>
cmux browser <surface> open <url>
cmux browser <surface> navigate <url>
cmux browser <surface> back
cmux browser <surface> forward
cmux browser <surface> reload
cmux browser <surface> url
cmux browser <surface> get title
DOM Inspection
cmux browser <surface> snapshot [--selector <sel>] [--compact] [--max-depth <n>]
cmux browser <surface> get text <selector>
cmux browser <surface> get html <selector>
cmux browser <surface> get value <selector>
cmux browser <surface> get attr <selector> --attr <name>
cmux browser <surface> get count <selector>
cmux browser <surface> get box <selector>
cmux browser <surface> get styles <selector>
cmux browser <surface> is visible <selector>
cmux browser <surface> is enabled <selector>
cmux browser <surface> is checked <selector>
Element Interaction
cmux browser <surface> click <selector>
cmux browser <surface> dblclick <selector>
cmux browser <surface> hover <selector>
cmux browser <surface> focus <selector>
cmux browser <surface> scroll-into-view <selector>
cmux browser <surface> scroll [--dy <pixels>]
Form Input
cmux browser <surface> type <selector> "text"
cmux browser <surface> fill <selector> "text"
cmux browser <surface> check <selector>
cmux browser <surface> uncheck <selector>
cmux browser <surface> select <selector> "value"
cmux browser <surface> press <key>
Find Elements (Locators)
cmux browser <surface> find role <role> [--name <name>]
cmux browser <surface> find text "text" [--exact]
cmux browser <surface> find label "label" [--exact]
cmux browser <surface> find placeholder "text" [--exact]
cmux browser <surface> find testid "id"
cmux browser <surface> find first <selector>
cmux browser <surface> find nth <index> <selector>
JavaScript & Screenshots
cmux browser <surface> eval "document.title"
cmux browser <surface> screenshot [--out <path>]
Wait for Conditions
cmux browser <surface> wait <selector>
cmux browser <surface> wait --text "text"
cmux browser <surface> wait --url "url"
cmux browser <surface> wait --load-state <state>
cmux browser <surface> wait --function "js expr"
Console & Errors
cmux browser <surface> console list
cmux browser <surface> errors list
Tabs, Cookies & Storage
cmux browser <surface> tab list
cmux browser <surface> tab new [<url>]
cmux browser <surface> cookies get [--domain <d>]
cmux browser <surface> cookies set <name> <value>
cmux browser <surface> storage local get [<key>]
cmux browser <surface> storage local set <key> <val>
Network & Emulation
cmux browser <surface> viewport <width> <height>
cmux browser <surface> offline true|false
cmux browser <surface> geolocation <lat> <lng>
cmux browser <surface> network route <pattern> [--abort] [--body <resp>]
cmux browser <surface> network requests
Workflow Patterns
Fan out into splits (parallel tasks in one workspace)
cmux new-split right
cmux send --surface surface:2 "npm run dev\n"
cmux new-split down
cmux send --surface surface:3 "npm test -- --watch\n"
cmux set-status build "Running" --icon hammer --color "#1565C0"
cmux read-screen --surface surface:3 --lines 20
cmux set-status build "Done" --icon checkmark --color "#196F3D"
Fan out into workspace tabs (isolated environments)
cmux new-workspace --command "cd ~/project/backend && npm run build"
cmux new-workspace --command "cd ~/project/frontend && npm run build"
Run tests, read failures, fix, re-run
cmux new-split right
cmux send --surface surface:2 "npm test 2>&1\n"
cmux read-screen --surface surface:2 --lines 50
cmux send --surface surface:2 "npm test 2>&1\n"
Report progress throughout a task
cmux set-progress 0.0 --label "Starting build"
cmux set-progress 0.33 --label "Compiling"
cmux set-progress 0.66 --label "Running tests"
cmux set-progress 1.0 --label "Complete"
cmux clear-progress
cmux notify --title "Build Complete" --body "All tests passed"
Open a website, inspect it, interact with it
cmux browser surface:1 open-split --direction right
sleep 1 && cmux browser surface:2 navigate "https://example.com"
cmux browser surface:2 wait --load-state networkidle
cmux browser surface:2 snapshot --compact
cmux browser surface:2 click "button.submit"
cmux browser surface:2 get text ".result-message"
cmux browser surface:2 screenshot --out /tmp/result.png
Check a web app's state (e.g., verify a deploy)
cmux browser surface:1 open-split --direction right
sleep 1 && cmux browser surface:2 navigate "https://myapp.com"
cmux browser surface:2 wait --load-state networkidle
cmux browser surface:2 get title
cmux browser surface:2 eval "document.querySelector('.version')?.textContent"
cmux browser surface:2 console list
cmux browser surface:2 errors list
Markdown Preview in Browser Panel
When the user asks to open/view/preview a .md file in cmux (e.g., "open foo.md on the right", "show the plan"), render it as styled HTML in a cmux browser panel. Do NOT use less, cat, or file:// URLs.
File naming
Derive the HTML filename from the source markdown filename:
/path/to/my-plan.md → /tmp/my-plan.html
- Use
os.path.basename and replace .md with .html
Track which HTML file you created. When the user asks to update/refresh the preview, or when you modify the source markdown, regenerate the same HTML file and cmux browser <surface> reload. Do not create a second HTML file with a different name.
Steps
- Convert markdown to HTML using the Python script below. Write output to
/tmp/<basename>.html.
- Start a local HTTP server (if not already running — check with
lsof -ti:18923):
python3 -m http.server 18923 --directory /tmp --bind 127.0.0.1 &>/dev/null &
- Open in cmux browser panel using the direction the user requested:
cmux browser <your-surface> open-split "http://127.0.0.1:18923/<basename>.html"
HTML conversion script
Use this Python script (no external dependencies). It strips YAML frontmatter and converts headings, ordered/unordered lists, code blocks, inline code, bold, links, and horizontal rules:
python3 -c "
import re, sys, os, html as html_mod
src = sys.argv[1]
out_name = os.path.basename(src).rsplit('.', 1)[0] + '.html'
out_path = '/tmp/' + out_name
with open(src) as f:
content = f.read()
# Strip YAML frontmatter
if content.startswith('---'):
end = content.index('---', 3)
content = content[end+3:].strip()
DARK = '''
body { font-family: -apple-system, system-ui, sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; line-height: 1.6; color: #c9d1d9; background: #0d1117; }
h1 { border-bottom: 2px solid #30363d; padding-bottom: 8px; color: #e6edf3; }
h2 { border-bottom: 1px solid #30363d; padding-bottom: 4px; margin-top: 32px; color: #e6edf3; }
h3 { margin-top: 24px; color: #e6edf3; }
code { background: #161b22; padding: 2px 6px; border-radius: 3px; font-size: 0.9em; color: #f0883e; }
pre { background: #161b22; padding: 16px; border-radius: 6px; overflow-x: auto; color: #c9d1d9; }
pre code { background: none; padding: 0; color: #c9d1d9; }
ul { padding-left: 24px; margin: 8px 0; }
ol { padding-left: 24px; margin: 8px 0; }
li { margin-bottom: 6px; }
a { color: #58a6ff; }
p { margin: 4px 0; }
strong { color: #e6edf3; }
hr { border: none; border-top: 1px solid #30363d; margin: 32px 0; }
table { border-collapse: collapse; width: 100%; margin: 16px 0; }
th { background: #161b22; padding: 8px 12px; border: 1px solid #30363d; text-align: left; color: #e6edf3; font-weight: 600; }
td { padding: 8px 12px; border: 1px solid #30363d; }
tr:nth-child(even) td { background: rgba(22,27,34,0.5); }
'''
LIGHT = '''
body { font-family: -apple-system, system-ui, sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; line-height: 1.6; color: #1a1a1a; background: #fff; }
h1 { border-bottom: 2px solid #e1e4e8; padding-bottom: 8px; }
h2 { border-bottom: 1px solid #e1e4e8; padding-bottom: 4px; margin-top: 32px; }
h3 { margin-top: 24px; }
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 0.9em; }
pre { background: #f6f8fa; padding: 16px; border-radius: 6px; overflow-x: auto; }
pre code { background: none; padding: 0; }
ul { padding-left: 24px; margin: 8px 0; }
ol { padding-left: 24px; margin: 8px 0; }
li { margin-bottom: 6px; }
a { color: #0366d6; }
p { margin: 4px 0; }
hr { border: none; border-top: 1px solid #e1e4e8; margin: 32px 0; }
table { border-collapse: collapse; width: 100%; margin: 16px 0; }
th { background: #f6f8fa; padding: 8px 12px; border: 1px solid #e1e4e8; text-align: left; font-weight: 600; }
td { padding: 8px 12px; border: 1px solid #e1e4e8; }
tr:nth-child(even) td { background: #f9f9f9; }
'''
theme = DARK if '--dark' in sys.argv else LIGHT
def inline(t):
t = re.sub(r'\x60([^\x60]+)\x60', r'<code>\1</code>', t)
t = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<a href=\"\2\">\1</a>', t)
t = re.sub(r'\*\*([^*]+)\*\*', r'<strong>\1</strong>', t)
t = re.sub(r'(?<![\"=])https?://[^\s<>)]+', lambda m: f'<a href=\"{m.group()}\">{m.group()}</a>', t)
return t
def flush_tbl(rows, sep):
out = ['<table>']
started_body = False
for i, row in enumerate(rows):
if i == sep: continue
cells = [c.strip() for c in row.strip().strip('|').split('|')]
if sep >= 0 and i < sep:
out.append('<thead><tr>' + ''.join(f'<th>{inline(c)}</th>' for c in cells) + '</tr></thead>')
else:
if not started_body: out.append('<tbody>'); started_body = True
out.append('<tr>' + ''.join(f'<td>{inline(c)}</td>' for c in cells) + '</tr>')
if started_body: out.append('</tbody>')
out.append('</table>')
return out
lines = content.split('\n')
h, in_ul, in_ol, in_code, code_buf, in_tbl, tbl_rows, tbl_sep = [], False, False, False, [], False, [], -1
for line in lines:
s = line.strip()
if s.startswith('\x60\x60\x60'):
if in_code:
h.append('<pre><code>' + '\n'.join(code_buf) + '</code></pre>')
code_buf = []; in_code = False
else:
if in_ul: h.append('</ul>'); in_ul = False
if in_ol: h.append('</ol>'); in_ol = False
in_code = True
continue
if in_code:
code_buf.append(html_mod.escape(line)); continue
if s.startswith('|') and '|' in s[1:]:
if in_ul: h.append('</ul>'); in_ul = False
if in_ol: h.append('</ol>'); in_ol = False
if not in_tbl: in_tbl = True; tbl_rows = []; tbl_sep = -1
if all(re.match(r'^[-:]+$', c.strip()) for c in s.strip().strip('|').split('|') if c.strip()):
tbl_sep = len(tbl_rows)
tbl_rows.append(s)
continue
if in_tbl:
h.extend(flush_tbl(tbl_rows, tbl_sep)); in_tbl = False
if not s:
if in_ul: h.append('</ul>'); in_ul = False
if in_ol: h.append('</ol>'); in_ol = False
h.append('<br>'); continue
if s == '---':
if in_ul: h.append('</ul>'); in_ul = False
if in_ol: h.append('</ol>'); in_ol = False
h.append('<hr>'); continue
m = re.match(r'^(#{1,4})\s+(.*)', s)
if m:
if in_ul: h.append('</ul>'); in_ul = False
if in_ol: h.append('</ol>'); in_ol = False
n = len(m.group(1))
h.append(f'<h{n}>{inline(m.group(2))}</h{n}>'); continue
m = re.match(r'^(\d+)\.\s+(.*)', s)
if m:
if in_ul: h.append('</ul>'); in_ul = False
if not in_ol: h.append('<ol>'); in_ol = True
h.append(f'<li>{inline(m.group(2))}</li>'); continue
m = re.match(r'^[-*]\s+(.*)', s)
if m:
if in_ol: h.append('</ol>'); in_ol = False
if not in_ul: h.append('<ul>'); in_ul = True
h.append(f'<li>{inline(m.group(1))}</li>'); continue
if in_ul: h.append('</ul>'); in_ul = False
if in_ol: h.append('</ol>'); in_ol = False
h.append(f'<p>{inline(s)}</p>')
if in_ul: h.append('</ul>')
if in_ol: h.append('</ol>')
if in_tbl: h.extend(flush_tbl(tbl_rows, tbl_sep))
page = f'<!DOCTYPE html><html><head><meta charset=\"utf-8\"><style>{theme}</style></head><body>{chr(10).join(h)}</body></html>'
with open(out_path, 'w') as f:
f.write(page)
print(f'OK {out_name}')
" /path/to/file.md --dark
The script prints the output filename so you can use it in the browser URL.
Defaults
- Default to dark mode (
--dark). Only use light mode if the user explicitly asks for light mode.
- Use
open-split with a direction matching the user's request (right, down, etc.). Default to open-split (which splits below).
- When you modify the source markdown, always regenerate the same HTML file and
cmux browser <surface> reload. Never create a second HTML file.
- Reuse the same HTTP server port (18923) across previews. Before starting a new server, check:
lsof -ti:18923. If already running, skip.
Safety Rules
- Never
cmux send to surfaces you don't own — the user may be typing in them
- Always target surfaces you created with
--surface <ref>
- Don't use focus/select commands (
select-workspace, focus-pane, etc.) unless the user explicitly asked — don't steal focus
- Clean up when done — close surfaces and workspaces you created
- Use
identify --json first to understand your current context before creating new terminals