Install
$ agentstack add skill-boundsj-agent-skills-cmux ✓ 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 Used
- ✓ Shell / process execution No
- ✓ 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.
About
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 refCMUX_SURFACE_ID— current surface refCMUX_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 # get caller context (workspace/surface/pane refs)
cmux list-workspaces # list all workspaces
cmux list-panes # list panes in current workspace
cmux list-pane-surfaces --pane # list surfaces (tabs) in a pane
Create Terminals
cmux new-workspace --command "cd /path && cmd" # new workspace tab (does NOT switch to it)
cmux new-split # split current pane
cmux new-surface # new tab in current pane
cmux new-pane --direction # new pane
Send Input / Read Output
cmux send --surface "text\n" # send text to a surface (include \n for enter)
cmux send-key --surface # send key (enter, ctrl-c, etc.)
cmux read-screen --surface --lines # read terminal output (last n lines)
Progress Reporting (shows in cmux sidebar)
cmux set-status --icon --color
cmux set-progress --label "text"
cmux log --level --source "agent" -- "message"
cmux notify --title "Title" --body "Body" # desktop notification
cmux clear-status
cmux clear-progress
cmux clear-log
Workspace Management
cmux rename-workspace "name"
cmux rename-tab --surface "name"
cmux close-surface --surface
cmux close-workspace --workspace
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 [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:
# Two-step open (reliable)
cmux browser open-split --direction # 1. create the split (note the returned surface ref)
sleep 1 && cmux browser navigate # 2. navigate after surface is ready
# Single-surface commands
cmux browser open # open URL in existing surface
cmux browser navigate # navigate to URL
cmux browser back # go back
cmux browser forward # go forward
cmux browser reload # reload page
cmux browser url # get current URL
cmux browser get title # get page title
DOM Inspection
cmux browser snapshot [--selector ] [--compact] [--max-depth ] # get DOM snapshot
cmux browser get text # get element text
cmux browser get html # get element HTML
cmux browser get value # get element value
cmux browser get attr --attr # get attribute
cmux browser get count # count matching elements
cmux browser get box # get bounding box
cmux browser get styles # get CSS styles
cmux browser is visible # check visibility
cmux browser is enabled # check if enabled
cmux browser is checked # check if checked
Element Interaction
cmux browser click # click element
cmux browser dblclick # double-click
cmux browser hover # hover
cmux browser focus # focus element
cmux browser scroll-into-view # scroll element into view
cmux browser scroll [--dy ] # scroll page
Form Input
cmux browser type "text" # type into element (appends)
cmux browser fill "text" # fill element (clears first)
cmux browser check # check checkbox
cmux browser uncheck # uncheck checkbox
cmux browser select "value" # select dropdown value
cmux browser press # press key (Enter, Tab, Escape, etc.)
Find Elements (Locators)
cmux browser find role [--name ] # find by ARIA role
cmux browser find text "text" [--exact] # find by text content
cmux browser find label "label" [--exact] # find by label
cmux browser find placeholder "text" [--exact] # find by placeholder
cmux browser find testid "id" # find by test ID
cmux browser find first # first match
cmux browser find nth # nth match
JavaScript & Screenshots
cmux browser eval "document.title" # evaluate JavaScript
cmux browser screenshot [--out ] # take screenshot
Wait for Conditions
cmux browser wait # wait for element
cmux browser wait --text "text" # wait for text
cmux browser wait --url "url" # wait for URL
cmux browser wait --load-state # wait for load state
cmux browser wait --function "js expr" # wait for JS condition
Console & Errors
cmux browser console list # list console messages
cmux browser errors list # list page errors
Tabs, Cookies & Storage
cmux browser tab list # list browser tabs
cmux browser tab new [] # new browser tab
cmux browser cookies get [--domain ] # get cookies
cmux browser cookies set # set cookie
cmux browser storage local get [] # get localStorage
cmux browser storage local set # set localStorage
Network & Emulation
cmux browser viewport # set viewport size
cmux browser offline true|false # toggle offline mode
cmux browser geolocation # set geolocation
cmux browser network route [--abort] [--body ] # mock network
cmux browser network requests # get network requests
Workflow Patterns
Fan out into splits (parallel tasks in one workspace)
# Create splits for build and test
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"
# Report progress
cmux set-status build "Running" --icon hammer --color "#1565C0"
# ... do work ...
# Check results
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"
# wait, then read output
cmux read-screen --surface surface:2 --lines 50
# fix code based on output, then re-run
cmux send --surface surface:2 "npm test 2>&1\n"
Report progress throughout a task
cmux set-progress 0.0 --label "Starting build"
# ... step 1 ...
cmux set-progress 0.33 --label "Compiling"
# ... step 2 ...
cmux set-progress 0.66 --label "Running tests"
# ... step 3 ...
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
# Open a browser panel in a split (two-step for reliability)
cmux browser surface:1 open-split --direction right
sleep 1 && cmux browser surface:2 navigate "https://example.com"
# Wait for it to load
cmux browser surface:2 wait --load-state networkidle
# Get a DOM snapshot to understand the page
cmux browser surface:2 snapshot --compact
# Find and click a button
cmux browser surface:2 click "button.submit"
# Read resulting text
cmux browser surface:2 get text ".result-message"
# Take a screenshot
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 # check for errors
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.basenameand replace.mdwith.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 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/.html. - Start a local HTTP server (if not already running — check with
lsof -ti:18923):
``bash 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:
``bash cmux browser open-split "http://127.0.0.1:18923/.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'\1', t)
t = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'\1', t)
t = re.sub(r'\*\*([^*]+)\*\*', r'\1', t)
t = re.sub(r'(?)]+', lambda m: f'{m.group()}', t)
return t
def flush_tbl(rows, sep):
out = ['']
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 ' + ''.join(f'{inline(c)}' for c in cells) + '')
else:
if not started_body: out.append(''); started_body = True
out.append('' + ''.join(f'{inline(c)}' for c in cells) + '')
if started_body: out.append('')
out.append('')
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('' + '\n'.join(code_buf) + '')
code_buf = []; in_code = False
else:
if in_ul: h.append(''); in_ul = False
if in_ol: h.append(''); 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(''); in_ul = False
if in_ol: h.append(''); 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(''); in_ul = False
if in_ol: h.append(''); in_ol = False
h.append(''); continue
if s == '---':
if in_ul: h.append(''); in_ul = False
if in_ol: h.append(''); in_ol = False
h.append(''); continue
m = re.match(r'^(#{1,4})\s+(.*)', s)
if m:
if in_ul: h.append(''); in_ul = False
if in_ol: h.append(''); in_ol = False
n = len(m.group(1))
h.append(f'{inline(m.group(2))}'); continue
m = re.match(r'^(\d+)\.\s+(.*)', s)
if m:
if in_ul: h.append(''); in_ul = False
if not in_ol: h.append(''); in_ol = True
h.append(f'{inline(m.group(2))}'); continue
m = re.match(r'^[-*]\s+(.*)', s)
if m:
if in_ol: h.append(''); in_ol = False
if not in_ul: h.append(''); in_ul = True
h.append(f'{inline(m.group(1))}'); continue
if in_ul: h.append(''); in_ul = False
if in_ol: h.append(''); in_ol = False
h.append(f'{inline(s)}')
if in_ul: h.append('')
if in_ol: h.append('')
if in_tbl: h.extend(flush_tbl(tbl_rows, tbl_sep))
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [boundsj](https://github.com/boundsj)
- **Source:** [boundsj/agent-skills](https://github.com/boundsj/agent-skills)
- **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.