Command reference
Every command, its flags, and an example. `--help` on any command is always authoritative.
Global targeting: most commands accept -t/--tab <id>, --workspace <name>, or --group <name> (exactly one). With none, the active tab is used — except ref actions, which target the ref's own tab.
See the browser
| Command | Does |
|---|---|
tabs |
List all windows and tabs with ids, titles, URLs |
switch <tabId> |
Activate a tab (steals focus — that's its job) |
close <tabIds...> |
Close tabs |
Navigate
chrome-relay navigate "https://chrome-relay.kushalsm.com" --new # background tab (default for --new)
chrome-relay navigate "https://chrome-relay.kushalsm.com" --new --active # foreground
chrome-relay navigate "https://chrome-relay.kushalsm.com" --tab 42 # retarget an existing tab
Read the page
chrome-relay snapshot --tab 42 -i # the way — see /docs/snapshots/
chrome-relay snapshot --tab 42 -i -s "#main" -d 3 -u --json
| Flag | Does |
|---|---|
-i, --interactive |
only ref-bearing elements |
-d, --depth <n> |
truncate tree depth |
-s, --scope <css> |
subtree of the first match; refs outside it are never issued |
-u, --urls |
include link hrefs |
--diff |
print only what changed since this tab's previous snapshot (~100 tokens instead of a re-read; refs in the diff are current and clickable) |
--json |
structured { title, url, tabId, nodes, refs } |
read / ax are deprecated aliases for snapshot.
Wait
chrome-relay wait @e12 # ref resolves and has a box
chrome-relay wait ".results" --tab 42 # selector exists and visible
chrome-relay wait --text "Welcome" --tab 42
chrome-relay wait --url "**/dashboard" --tab 42
chrome-relay wait --load networkidle --tab 42 # also: load | domcontentloaded
chrome-relay wait --fn "window.__READY" --tab 42
chrome-relay wait 1500 # plain sleep
One condition per call; default timeout 10 s, capped at 25 s. On timeout the structured error includes the page's current state (url, readyState, whether the selector exists) — no follow-up probe needed.
Get — one value, no snapshot
chrome-relay get text @e12
chrome-relay get value 'input[name="email"]' --tab 42
chrome-relay get attr @e7 href
chrome-relay get count ".result" --tab 42
chrome-relay get title --tab 42
chrome-relay get url --tab 42
Plain value on stdout, nothing else — built for $(...) substitution in scripts.
Batch — N calls, one round-trip
chrome-relay batch '[
{"name":"chrome_navigate","args":{"url":"https://chrome-relay.kushalsm.com","newTab":true}},
{"name":"chrome_wait","args":{"load":"load"}},
{"name":"chrome_snapshot","args":{"interactiveOnly":true}}
]'
cat commands.json | chrome-relay batch --stdin
Sequential execution in the extension, bail-on-error by default (--no-bail to continue). Uses wire tool names. Amortizes process startup and the bridge hop across N actions; nested batches are rejected.
Act
chrome-relay click @e12 # ref (preferred)
chrome-relay click 'button.save' --tab 42 # CSS selector
chrome-relay click --x 540 --y 320 --tab 42 # coordinates
chrome-relay fill @e14 "value" # input/textarea/select — atomic write
chrome-relay type "text" -s @e7 # contenteditable/Lexical — trusted insertText
chrome-relay keys "Cmd+K" --tab 42 # single key or chord
chrome-relay hover @e3 # pointer move only; fires :hover
chrome-relay click-ax --node 4837 --tab 42 # deprecated — raw backendNodeId
Evaluate JavaScript
chrome-relay js --tab 42 "return document.title"
chrome-relay js --tab 42 "const r = await fetch('/api/me'); return await r.json()"
MAIN world, async IIFE, top-level await works, return sends the value back. Page globals and framework internals are reachable.
Capture
chrome-relay screenshot --tab 42 -o out.png # works on background tabs
chrome-relay screenshot --tab 42 --full -o page.png # beyond the viewport
chrome-relay screenshot --tab 42 --selector "header" --padding 8 -o hdr.png
chrome-relay screenshot --tab 42 --bbox 0,0,1280,80 -o region.png
chrome-relay screenshot --tab 42 --max-edge 800 -o small.png
Region screenshots are ~10× cheaper in tokens than full-tab when you only need one component.
chrome-relay screencast start --tab 42 --quality 80 --max-width 900
# ...drive the interaction...
chrome-relay screencast stop --tab 42 --out /tmp/rec --gif
Paint-driven recording (catches CSS transitions and hover states). Requires the tab to be active — Chrome doesn't paint background tabs. With --gif/--mp4 and ffmpeg on PATH, frames get stitched; consecutive identical frames are deduped.
Observe
chrome-relay console read --tab 42 --level error,warn
chrome-relay network read --tab 42 --status failed
chrome-relay network body --tab 42 --request-id <id>
chrome-relay network har --tab 42 -o session.har
Per-tab ring buffers, last 200 entries each. Details: Observability.
Emulate
chrome-relay viewport preset iphone-14 --tab 42
chrome-relay viewport set --width 390 --height 844 --dpr 3 --mobile --touch --tab 42
chrome-relay viewport clear --tab 42
chrome-relay viewport list
Multi-agent
chrome-relay workspace create hypothesis-1 # a named Chrome window
chrome-relay --workspace hypothesis-1 navigate "https://chrome-relay.kushalsm.com" --new
chrome-relay group create research --color blue --tab 42 # native tab groups
Details: Workspaces.
Maintain
| Command | Does |
|---|---|
install |
register the native host for every detected browser |
doctor |
validate the whole chain end to end |
update |
update the CLI, print what changed (JSON) |
release-notes --since <ver> |
the changelog, agent-readable |
skills get core |
print the agent playbook, version-matched to the binary |
self-reload |
restart the extension service worker (dev loop) |
call <tool> [json] |
raw pass-through to any internal tool |
Output contract
Success prints JSON (or snapshot text) on stdout. Failure prints a human line plus a machine block on stderr and exits 1:
{ "relayError": { "code": "stale_ref", "message": "...", "tool": "chrome_click_element", "details": {} } }
Branch on code. The full list: Errors.