Install any skill in seconds. Free to start, no credit card required.
Get Started Free →End-user browser automation with cmux. Use when you need to open sites, interact with pages, wait for state changes, and extract data from cmux browser surfaces.
.claude/skills/manaflow-ai-cmux-browser/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 49% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 96% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 47% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 162% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 240% | 0% |
Check the binary that will actually run before giving an exact command:
bashcmux browser --help cmux --version
There are two deliberately different command shapes:
open, open-split, or new. These commands may be workspace-scoped and do not need a surface handle.--surface <handle> or as the first positional token.Prefer the flag form in scripts because it makes the target unmissable:
bashSURFACE="surface:7" # use a ref returned by discovery; do not guess an index cmux browser --surface "$SURFACE" get url cmux browser --surface "$SURFACE" get-url # accepted alias cmux browser --surface "$SURFACE" snapshot --interactive cmux browser --surface "$SURFACE" snapshot -i # accepted alias cmux browser --surface "$SURFACE" url # accepted alias cmux browser --surface "$SURFACE" tab list cmux browser --surface "$SURFACE" click e1 --snapshot-after
The positional form is equivalent (cmux browser "$SURFACE" get url). url and get-url are accepted URL aliases, and the short interactive snapshot flag is accepted when a surface is already present; use get url and snapshot --interactive in new documentation so the target and operation are clear. Surface-bound operations have no unscoped form. The current CLI's explicitly global browser verbs (open, open-split, new, identify, import, profile, profiles, react-grab, reactgrab, devtools, dev-tools, focus-mode, design-mode, zoom, and history) may omit the handle and use caller/workspace routing; do not infer a target from visible focus for any other verb.
identify, tree, and list commands are read-only and do not select a workspace, pane, or browser. Do not infer that the visually focused surface is the one the user wants.
First inspect the caller context (useful for the default workspace):
bashcmux identify --json
To discover browser surfaces in the caller or another workspace/window, use the all-window tree. It includes parent refs, so a browser in a different workspace can be targeted directly without selecting that workspace:
bashcmux tree --all --json \ | jq -r ' .windows[]? as $window | $window.workspaces[]? as $workspace | $workspace.panes[]? as $pane | $pane.surfaces[]? | select(.type == "browser") | [$window.ref, $workspace.ref, $pane.ref, .ref] | @tsv'
The filtered output is window, workspace, pane, and surface refs. Keep the surface ref, then target it explicitly:
bashSURFACE="surface:N" # copied from the filtered tree output cmux browser --surface "$SURFACE" get url cmux browser --surface "$SURFACE" snapshot --interactive
If the user gives a URL or title instead of a workspace/pane, match that metadata locally and emit only the unique surface ref. This never prints the matched URL or title:
bashMATCH_FIELD="url" # use "title" when matching a page title MATCH_VALUE="${BROWSER_URL_OR_TITLE:?set BROWSER_URL_OR_TITLE without logging it}" SURFACE="$( cmux tree --all --json | jq -r --arg field "$MATCH_FIELD" --arg value "$MATCH_VALUE" ' [ .windows[]? as $window | $window.workspaces[]? as $workspace | $workspace.panes[]? as $pane | $pane.surfaces[]? | select(.type == "browser") | select((if $field == "url" then (.url // "") else (.title // "") end) == $value) | .ref ] as $matches | if ($matches | length) == 1 then $matches[0] elif ($matches | length) == 0 then error("no matching browser surface") else error("multiple matches; use workspace/pane context") end' )" if [[ -z "$SURFACE" ]]; then printf '%s\n' 'no uniquely matching browser surface; provide workspace/pane context' >&2 exit 1 fi cmux browser --surface "$SURFACE" get url
For one known workspace, cmux --json list-pane-surfaces --workspace <workspace> is a smaller read-only query. Raw tree/list payloads can contain page URLs and titles; filter or redact them before logging or pasting them. Never use a focus/select command merely to discover a surface.
Open (or create) a surface without stealing focus, capture the returned ref, then use that ref for every existing-surface operation:
bashOPEN_JSON="$(cmux --json browser open https://example.com --focus false)" SURFACE="$(printf '%s' "$OPEN_JSON" | jq -r '.surface_ref // .surface_id // empty')" [ -n "$SURFACE" ] || { printf '%s\n' 'browser open did not return a surface ref' >&2; exit 1; } cmux browser --surface "$SURFACE" get url cmux browser --surface "$SURFACE" wait --load-state complete --timeout-ms 15000 cmux browser --surface "$SURFACE" snapshot --interactive cmux browser --surface "$SURFACE" fill e1 "hello" cmux browser --surface "$SURFACE" click e2 --snapshot-after cmux browser --surface "$SURFACE" snapshot --interactive
After a browser download finishes, inspect the same surface's bounded history without opening the file or consuming a waiter:
bashcmux browser --surface "$SURFACE" download list cmux browser --surface "$SURFACE" download list --limit 5 --json
The JSON records expose the stable download_id, filename, actual saved path when known, status (downloading, saved, or failed), byte count when known, and whether a known path still exists. Listing is newest first and repeatable; it remains scoped to the requested surface. Use download wait to keep the existing event-wait workflow.
The open response contains the new surface ref; in a script, extract it from the JSON response instead of printing the full response. If get url is empty or about:blank, navigate first instead of waiting on load state. Re-snapshot after navigation, modal open/close, or any major DOM change because refs go stale.
bashcmux browser --surface "$SURFACE" wait --selector "#ready" --timeout-ms 10000 cmux browser --surface "$SURFACE" wait --text "Success" --timeout-ms 10000 cmux browser --surface "$SURFACE" wait --url-contains "/dashboard" --timeout-ms 10000 cmux browser --surface "$SURFACE" wait --load-state complete --timeout-ms 15000 cmux browser --surface "$SURFACE" wait --function "document.readyState === 'complete'" --timeout-ms 10000
cmux browser --surface "$SURFACE" viewport <width> <height> sets an exact logical viewport from 1 to 4096 CSS pixels. The page is aspect-fitted inside its existing pane, so pane layout and focus stay unchanged, and screenshots use the requested logical dimensions. viewport reset returns to native pane sizing.
Close or detach the browser inspector first: its inspector-managed split layout cannot be combined with viewport emulation, and opening or redocking an attached inspector resets emulation to native sizing. Large viewport and page-zoom combinations are bounded; the command returns structured maximum_page_zoom details and leaves the viewport unchanged when the combination exceeds WKWebView render limits.
Offline emulation, trace/screencast recording, network route interception/mocking, and low-level raw input injection return not_supported; they depend on Chrome/CDP-only APIs. Use click, fill, press, scroll, wait, and snapshot instead.
js_errorSome complex pages reject the JavaScript behind snapshot --interactive and eval. Recover by checking whether the page actually navigated, then fall back to raw text or HTML:
bashcmux browser --surface "$SURFACE" get url cmux browser --surface "$SURFACE" get text body cmux browser --surface "$SURFACE" get html body
If it still fails, navigate to a simpler intermediate page and retry from there. If the CLI and this skill disagree, refresh help (cmux browser --help) and refresh the installed skill before continuing; do not invent an implicit target.
The repository copies are the source of truth: .claude/skills/cmux-browser and .agents/skills/cmux-browser point at skills/cmux-browser. Do not edit a mirror by hand. The supported Vercel installer (pinned here to the reviewed skills 1.5.23 release) refreshes both global Claude Code and Codex discovery roots and copies the complete skill (including references and templates):
bash# From this checkout while developing the skill: npx --yes skills@1.5.23 add . --global --yes --skill cmux-browser --agent claude-code codex --copy # From the published repository after the change is merged: npx --yes skills@1.5.23 add manaflow-ai/cmux --global --yes --skill cmux-browser --agent claude-code codex --copy
Restart an agent session after a refresh if it cached the previous document. The repository's skills.sh remains available for a Codex-only destination; pass its --dest explicitly when that is the installation path:
bash./skills.sh --dest "$HOME/.codex/skills" --skill cmux-browser
Never commit home-directory skill copies, credentials, cookies, or saved browser state.
| Reference | When to Use | |-----------|-------------| | references/surface-discovery.md | Find and target an existing browser surface without focus changes | | references/commands.md | Full command mapping, aliases, agent-browser equivalents, viewport error codes | | references/snapshot-refs.md | Ref lifecycle and stale-ref troubleshooting | | references/authentication.md | Login/OAuth/2FA patterns and state save/load | | references/session-management.md | Multi-surface isolation and state persistence | | references/video-recording.md | Recording status and practical alternatives | | references/proxy-support.md | Proxy behavior in WKWebView and workarounds |
| Template | Description | |----------|-------------| | templates/form-automation.sh | Snapshot/ref form fill loop (requires an explicit surface) | | templates/authenticated-session.sh | Login once, save/load state (requires an explicit surface) | | templates/capture-workflow.sh | Navigate and capture snapshots/screenshots (requires an explicit surface) |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 26,788 | 44,363 | +66% | 1 | 1 | 0% | 3,914 | 3,033 | -23% | 0 | 0 | — |
case-02 | fail→pass | 22,276 | 16,526 | -26% | 1 | 1 | 0% | 3,125 | 4,659 | +49% | 0 | 0 | — |
case-03 | fail→fail | 11,933 | 9,130 | -23% | 1 | 1 | 0% | 1,538 | 3,064 | +99% | 0 | 0 | — |
case-04 | pass→fail | 16,983 | 5,732 | -66% | 1 | 1 | 0% | 1,545 | 3,450 | +123% | 0 | 0 | — |
case-05 | fail→pass | 15,229 | 3,095 | -80% | 1 | 1 | 0% | 1,597 | 3,123 | +96% | 0 | 0 | — |
case-06 | fail→pass | 13,440 | 9,442 | -30% | 1 | 1 | 0% | 2,271 | 3,329 | +47% | 0 | 0 | — |
case-07 | fail→fail | 17,406 | 10,495 | -40% | 1 | 1 | 0% | 1,889 | 3,621 | +92% | 0 | 0 | — |
case-08 | fail→pass | 13,259 | 9,002 | -32% | 1 | 1 | 0% | 1,253 | 3,277 | +162% | 0 | 0 | — |
case-09 | fail→pass | 9,013 | 12,981 | +44% | 1 | 1 | 0% | 1,127 | 3,837 | +240% | 0 | 0 | — |
case-10 | fail→pass | 8,958 | 11,008 | +23% | 1 | 1 | 0% | 1,378 | 3,647 | +165% | 0 | 0 | — |
case-11 | fail→pass | 11,590 | 8,319 | -28% | 1 | 1 | 0% | 1,743 | 3,180 | +82% | 0 | 0 | — |
case-12 | fail→pass | 15,848 | 5,265 | -67% | 1 | 1 | 0% | 1,529 | 3,304 | +116% | 0 | 0 | — |
case-13 | fail→pass | 13,722 | 7,927 | -42% | 1 | 1 | 0% | 1,214 | 3,063 | +152% | 0 | 0 | — |
case-14 | fail→fail | 20,814 | 7,402 | -64% | 1 | 1 | 0% | 2,336 | 3,955 | +69% | 0 | 0 | — |
case-15 | fail→fail | 26,360 | 12,391 | -53% | 1 | 1 | 0% | 3,309 | 3,071 | -7% | 0 | 0 | — |
case-16 | fail→pass | 16,416 | 11,653 | -29% | 1 | 1 | 0% | 1,601 | 3,713 | +132% | 0 | 0 | — |
case-17 | fail→pass | 6,662 | 10,318 | +55% | 1 | 1 | 0% | 1,172 | 3,469 | +196% | 0 | 0 | — |
case-18 | fail→pass | 18,203 | 9,218 | -49% | 1 | 1 | 0% | 2,136 | 3,415 | +60% | 0 | 0 | — |
case-19 | fail→pass | 6,363 | 2,922 | -54% | 1 | 1 | 0% | 1,034 | 3,088 | +199% | 0 | 0 | — |
case-20 | pass→pass | 22,598 | 15,104 | -33% | 1 | 1 | 0% | 3,036 | 5,376 | +77% | 0 | 0 | — |
case-21 | pass→pass | 19,231 | 11,149 | -42% | 1 | 1 | 0% | 1,555 | 3,753 | +141% | 0 | 0 | — |
case-22 | pass→pass | 12,199 | 6,114 | -50% | 1 | 1 | 0% | 1,171 | 3,830 | +227% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 19 counted toward the lift figure. The other 3 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +55 percentage points is the difference between those two pass rates over the 19 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 9/9/2026 | +64% |
| gemini-3.6-flash | verified | 8/4/2026 | +68% |
Other measured skills in the registry, with their headline benchmark lift.