Reference
Command Reference
Custom comboboxes are scrolled into view instantly and opened with trusted pointer input. pick reports verified: false with a warning when the choice is unconfirmed. Page/container scrolling uses instant movement even with CSS smooth scrolling; an unchanged position returns scrolled: false with a warning.
This is the complete cheat sheet of every chrome-use command, alias, and flag. It's organized by command group so it's easy to scan and verify. To get a feel for the overall rhythm first, read the Core Loop; when a command doesn't work, check Troubleshooting.
@e1, @e2 in interaction commands are element references (refs) from
snapshot output. Run snapshot -i first to get the refs, then act on them.
Any command with a @ref always targets the tab that was active when the snapshot ran.
Valid incremental snapshots return JSON refs only for nodes in the delta. A no-change delta has empty refs; the complete live session map and previously captured refs remain available. A full-tree fallback keeps the normal full-snapshot behavior.
read --filter uses literal, ASCII-case-insensitive text matching, not a regex. It prefers matching page headings with their descendants, then matching sections. Use read --outline to find a heading. snapshot --filter separately accepts a regex. read --help lists the reading options.
Navigation
Open, navigate, go back/forward, and close the browser. Without a URL, open just launches
the browser and stops at about:blank — handy for registering network interceptors, cookies,
or init scripts before the first real navigation.
# launch the browser only, no navigation; stops at about:blank
$ chrome-use open
# launch and navigate (aliases: goto, navigate); prepends https:// when no protocol
$ chrome-use open <url>
$ chrome-use back # go back
$ chrome-use forward # go forward
$ chrome-use reload # reload
# SPA client-side navigation: auto-detects Next.js router.push, falls back to history.pushState
$ chrome-use pushstate <url>
# Already on the target's origin and the page links to it? Click that link
# instead of navigating (react.dev: 19 requests in-page vs 47 for a navigation)
$ chrome-use open <url> --prefer-spa
$ chrome-use close # close the browser (aliases: quit, exit)
$ chrome-use connect 9222 # connect over a CDP port
open or navigate whose
renderer-scoped Page.navigate call times out is retried through Chrome's browser-level
tab API. The command returns a warning and keeps the same session instead of restarting it.
You can line up all the pre-navigation setup in one shot with batch — launch cleanly, register
interceptors / cookies in order, then navigate. This suits SSR-only debugging (block everything except
--resource-type script), authenticating against a protected origin, or capturing a clean
react suspense / vitals state.
$ chrome-use batch \
'["open"]' \
'["network","route","*","--abort","--resource-type","script"]' \
'["cookies","set","--curl","cookies.curl","--domain","localhost"]' \
'["navigate","http://localhost:3000/target"]'
Snapshot (page analysis)
Interactive snapshots add a bounded context annotation for controls inside nearby articles, list items, rows, or groups identified by a heading or one distinct linked product name and one action control. Named product links omit repeated context when their sibling action carries it. It preserves local text such as product prices without changing accessible names or refs. Live status receipts also remain visible in interactive snapshots and action observations, with bounded text and explicit truncation. Context may be absent or marked truncated; use a scoped or full snapshot when a required detail is missing.
snapshot gives you a structured accessibility view of the page — the starting point for all
interaction. snapshot -i is recommended: it lists only interactive elements, so the output is
compact and every ref is present.
$ chrome-use snapshot # full accessibility tree
$ chrome-use snapshot -i # interactive elements only (recommended)
$ chrome-use snapshot -c # compact output
$ chrome-use snapshot -d 3 # limit depth to 3
$ chrome-use snapshot -i --dom # actionable elements from a DOM walk (pierces shadow roots; automatic when the AX tree has no refs)
$ chrome-use snapshot -s "#main" # scope to a CSS selector
$ chrome-use snapshot -i -f "SSH|Save" # keep matching lines + their ancestors (refs intact)
$ chrome-use snapshot -i --diff # only what changed since the last snapshot
$ chrome-use snapshot -i --max-bytes 4000 # cut between nodes, report what was omitted + a cursor
$ chrome-use snapshot -i --max-bytes 4000 --from 70 # read on from where it stopped
Actions beyond a click
$ chrome-use actions @e15 # what this element supports right now (read live)
$ chrome-use do @e15 expand # perform one of exactly those; anything else is refused
expand / collapse, showMenu,
increment / decrement and toggle, derived from the
element's accessibility properties. The set is reported again afterwards, so
a control that did not move cannot read as a success. See
Interacting.
But only expand / collapse can be confirmed
afterwards — the element must then offer the opposite action. For
showMenu, toggle, increment and
decrement the tree looks the same whether the control responded or ignored
the click, so those are marked · rather than ✓ and carry
confirmed: null in JSON — dispatched, outcome unknown. Read the page before
treating one as done; never read null as success.
snapshot -i only surfaces visible, interactive elements — it does not show
hidden inputs or a control's actual submitted value. When a form "looks filled in" but won't pass validation,
don't guess from the snapshot — inspect the DOM directly with eval.
Interactions
Drive the real browser with the @ref values from snapshot: click, type, check, drag, upload.
Drag mode follows the current session connection and target frame; an extension running in another Chrome profile does not change a launched browser’s drag mode.
$ chrome-use click @e1 # click
$ chrome-use click @e1 --new-tab # click and open in a new tab
$ chrome-use dblclick @e1 # double-click
$ chrome-use focus @e1 # focus the element
$ chrome-use fill @e2 "text" # clear then type
$ chrome-use type @e2 "text" # type directly (no clear)
$ chrome-use press Enter # press a key (alias: key); ⚠ warns when the page has no listener for it
$ chrome-use press Control+a # key combo
$ chrome-use keydown Shift # hold a key down
$ chrome-use keyup Shift # release a key
$ chrome-use hover @e1 # hover
$ chrome-use check @e1 # check a checkbox
$ chrome-use uncheck @e1 # uncheck
$ chrome-use select @e1 "value" # pick a dropdown option
$ chrome-use select @e1 "a" "b" # multi-select
$ chrome-use scroll down 500 # scroll the page (default down 300px)
$ chrome-use scroll down --until "#footer" # step until the target (selector/@ref/text=) is in view
$ chrome-use scrollintoview @e1 # scroll element into the viewport (alias: scrollinto)
$ chrome-use drag @e1 @e2 # drag and drop
$ chrome-use upload @e1 file.pdf # upload a file
$ chrome-use download @e2 ./file.pdf # download from a link or control
$ chrome-use download-url https://example.com/file.pdf ./file.pdf
$ chrome-use downloads --limit 10 --json # machine-readable Chrome downloads
fill reads the resulting control or rich-editor model value back and compares it exactly before reporting success. If Monaco hides its model API, one trusted editor paste is verified through the editor's copy handler and the browser clipboard is restored afterward. An unverifiable paste or mismatched readback is an error, not a silent success. The error quotes both values (e.g. read back "" after writing "千代田区"); when every non-ASCII character vanished while ASCII survived it says the page filtered non-Latin input (a Latin-only / masked field), so re-typing will not help. type reads the field back too: if it does not contain the typed text, a ⚠ warning is printed (exit 0, JSON gains readBack). Text fields are written with trusted input events; fill warns when the form's Save / Submit is disabled before and after the fill, and click refuses a disabled control (#358).
press probes, for keys whose only effect depends on page JavaScript (Arrow/Home/End/PageUp/PageDown on a text field, Escape, Enter on a bare input outside a form), whether any keydown/keyup/keypress listener exists from the focused element up to window. With none found it still exits 0 but prints a ⚠ warning that the page cannot react to the key and suggests clicking the option instead (JSON gains keyListeners). Chords, Tab, Backspace and printable keys are not probed.
<li> items often close when the input loses focus. If click
reports success but the page doesn't react, retry that click with AGENT_BROWSER_CLICK_MODE=dom —
a plain <li> has no focusable target, so the input keeps focus and the option still gets selected.
Note that a DOM-dispatched click (the relay's default for left clicks) now moves focus like a real click: the clicked
element, its nearest focusable ancestor, or a label's control receives focus unless the handler already moved it,
so click <input> then press Meta+a lands on that input rather than the previously focused field.
Reading information
Read text, HTML, attributes, values, bounding boxes, and computed styles from elements or the page.
$ chrome-use get text @e1 # element text
$ chrome-use get html @e1 # innerHTML
$ chrome-use get value @e1 # input value
$ chrome-use get attr @e1 href # attribute
$ chrome-use get title # page title
$ chrome-use get url # current URL
$ chrome-use get cdp-url # CDP WebSocket URL
$ chrome-use get count ".item" # number of matching elements
$ chrome-use get box @e1 # bounding box
$ chrome-use get styles @e1 # computed styles (font, color, background, etc.)
Check state
$ chrome-use is visible @e1 # is it visible
$ chrome-use is enabled @e1 # is it enabled
$ chrome-use is checked @e1 # is it checked
Screenshots & PDF
$ chrome-use screenshot # save to a temp directory
$ chrome-use screenshot path.png # save to a specific path
$ chrome-use screenshot main el.png # one element: selector, then path
$ chrome-use screenshot el.png --selector main # or --selector/-s, in any order
$ chrome-use screenshot --full # full-page screenshot
$ chrome-use pdf output.pdf # save as PDF
Headless Chromium hides native scrollbars in screenshots for consistent image output. Pass
--hide-scrollbars false at launch to keep the native scrollbars.
Recording
$ chrome-use record start ./demo.webm # start recording
$ chrome-use click @e1 # perform actions
$ chrome-use record stop # stop and save
$ chrome-use record restart ./take2.webm # stop the current one + start a new recording
Waiting
Wait for an element, text, URL, network idle, or any JS condition before asserting state.
$ chrome-use wait @e1 # wait for an element
$ chrome-use wait 2000 # wait a number of milliseconds
$ chrome-use wait --text "Success" # wait for text (or -t)
$ chrome-use wait --url "**/dashboard" # wait for a URL pattern (or -u)
$ chrome-use wait --load networkidle # wait for network idle (or -l)
$ chrome-use wait --fn "window.ready" # wait for a JS condition (or -f)
Mouse control
$ chrome-use mouse move 100 200 # move the mouse
$ chrome-use mouse down left # press a button
$ chrome-use mouse up left # release a button
$ chrome-use mouse wheel 100 # wheel
Semantic locators
An alternative to refs: locate by role, text, label, placeholder, alt, title, testid, or position, and chain the action to perform directly after it. Or pass a natural-language description to return ranked candidates without acting.
$ chrome-use find role button click --name "Submit"
$ chrome-use find "edit web service settings button" # candidates only
$ chrome-use find text "Sign In" click
$ chrome-use find text "Sign In" click --exact # exact match only
$ chrome-use find label "Email" fill "user@test.com"
$ chrome-use find placeholder "Search" type "query"
$ chrome-use find alt "Logo" click
$ chrome-use find title "Close" click
$ chrome-use find testid "submit-btn" click
$ chrome-use find first ".item" click
$ chrome-use find last ".item" click
$ chrome-use find nth 2 "a" hover
Browser settings
$ chrome-use set viewport 1920 1080 # viewport size
$ chrome-use set viewport 1920 1080 2 # 2x retina (CSS size unchanged, higher-res screenshots)
$ chrome-use set device "iPhone 14" # emulate a device
$ chrome-use set geo 37.7749 -122.4194 # geolocation (alias: geolocation)
$ chrome-use set offline on # toggle offline mode
$ chrome-use set headers '{"X-Key":"v"}' # extra HTTP headers
$ chrome-use set credentials user pass # HTTP basic auth (alias: auth)
$ chrome-use set media dark # emulate color scheme
$ chrome-use set media light reduced-motion # light + reduced motion
Cookies & storage
$ chrome-use cookies # get all cookies
$ chrome-use cookies set name value # set a cookie
$ chrome-use cookies clear --domain example.com # clear one site's cookies; --all --yes clears the whole browser
$ chrome-use storage local # all of localStorage
$ chrome-use storage local key # a specific key
$ chrome-use storage local set k v # set a value
$ chrome-use storage local clear # clear everything
Import cookies from cURL
Auto-detects the format: a JSON array of {name, value}, a DevTools → Network → Copy as cURL dump,
or a bare Cookie header. Cookie values are never echoed back on error.
$ chrome-use cookies set --curl <file> # auto-detect JSON / cURL / Cookie header
$ chrome-use cookies set --curl <file> --domain example.com # scope to a domain
Network interception
Intercept, block, mock responses, rewrite requests, or edit real responses. For in-depth usage, see the network section in the Core Loop.
$ chrome-use network route <url> # intercept requests
$ chrome-use network route <url> --abort # block requests
# mock a response (fulfill)
$ chrome-use network route <url> --body '{}' --status 200 --header K=V --content-type application/json
# rewrite a request (continue)
$ chrome-use network route <url> --method POST --set-body '{}' --set-header K=V --rewrite-url <u>
# edit a real response
$ chrome-use network route <url> --edit-status 503 --edit-header K=V --replace 'from=>to'
$ chrome-use network unroute [url] # remove a route
$ chrome-use network requests # view tracked requests
$ chrome-use network requests --filter api # filter requests
Intercept by resource type
# block scripts only (SSR-lock mode)
$ chrome-use network route '*' --abort --resource-type script
# replace images and fonts with empty responses
$ chrome-use network route '*' --resource-type image,font --body ''
Tabs & windows
Tab ids are stable strings like t1, t2 and are not reused within a session.
After a reconnect (a relay restart, for example) each id stays bound to the same Chrome tab; an id whose tab cannot be found again is refused rather than given to another tab, and its label stays reserved for that tab. If the tab you were driving is gone, commands on the current tab are refused until you pick one with tab <id>, tab new <url> or --tab <id>.
Positional integers are not accepted — tab 2 errors; use t2.
You can also give a tab a readable label (docs, app), interchangeable with the id.
$ chrome-use tab # list tabs (with tabId and label)
$ chrome-use tab new [url] # new tab
$ chrome-use tab new --label docs [url] # new tab with a readable label
$ chrome-use tab new [url] --activate # activate before initialization
$ chrome-use tab duplicate [ref] # native Duplicate tab; current by default
$ chrome-use tab duplicate docs --label copy # duplicate by ref and label the copy
$ chrome-use tab t2 # switch by id
$ chrome-use tab select t2 # explicit switch syntax
$ chrome-use tab select t2 --activate # activate before the liveness probe
$ chrome-use tab adopt "example.com/stuck" # adopt without navigating
$ chrome-use tab adopt "example.com" --activate # adopt and leave in the foreground
$ chrome-use tab inspect t2 # browser state without page JavaScript
$ chrome-use tab docs # switch by label
$ chrome-use tab close # close the current tab
$ chrome-use tab close t2 # close by id
$ chrome-use tab close docs # close by label
$ chrome-use tab list --all # every tab in the profile, the user's too (read-only)
$ chrome-use --tab chrome-tab:812 --force snapshot -i # act on the user's tab in place (user asked)
$ chrome-use tab close chrome-tab:812 --force # close the user's tab (user asked)
$ chrome-use window new # new window
tab list --all answers "which tabs do I have open?": every tab of the connected Chrome profile,
across all windows, including the user's own and other chrome-use sessions'. It only observes (over the relay
chrome.tabs.query and ABExt.state, ab-connect 0.5.25+; on direct CDP
Target.getTargets and Browser.getWindowForTarget): even the first call starts no daemon
and creates no window or tab. Nothing is attached, activated,
focused, moved or reloaded, and no page content is read. A running session keeps its existing browser.
A missing non-relay daemon needs --cdp; inventory refuses to launch a browser,
provision a provider or upgrade a daemon. Action-policy restrictions still apply. Each row has a handle
(chrome-tab:<id> over the relay, the targetId on direct CDP), its window, whether it is the
active tab of that window, pinned/incognito/discarded (relay), title, url, and its owner:
{kind: "self"}, {kind: "session", session, live} or {kind: "user"}, also
in words (this session, session alpha (live), the user). This session's
tabs come first, then other sessions' by name, then the user's, each by window and tab position. At most 200
rows (--limit, up to 1000); titles over 300 and urls over 2048 characters are cut, and the reply
says what it left out. Urls are exactly as Chrome reports them and may carry session tokens.
Acting on a tab the session does not own needs --force, and only when the user asked for that
tab. --tab <handle> --force <command> runs any page command on it in place: it is
attached but not activated, focused or moved (to bring it forward too, tab adopt <handle>
--activate). tab close <handle> --force closes it and reads it back. Each forced
result has forced: true and forcedTab (title, url, owner); another live session's tab
also gets a warning. Without --force the refusal names the owner and the flag. close
never closes a forced tab: it stays open and is released (forcedTabsLeftOpen; over the relay this
needs ab-connect 0.5.34, an older extension keeps it attached until it restarts). chrome-tab:
handles need ab-connect 0.5.30+.
Page diagnostics: diag pages
$ chrome-use diag pages --json # every page, owner, stale relay records, extension version (attaches nothing)
$ chrome-use diag pages --measure --json # + heap / nodes / listeners of pages already attached
$ chrome-use diag pages --watch 60 --force --json # two samples 60 s apart, a leak class per page
diag pages is the stable, read-only, versioned report (JSON "schema": 1) for tools that
look for leaky pages, such as Thermo, instead of driving the relay with raw CDP. It runs on its own diagnostic
connection: no daemon, no session tab, no relay restart. Per page: handle, targetId,
url, title, windowId, active / visible / discarded (relay),
attached and the owner (self / session / user). On top: the
count of stale relay records, the extension version and whether it is behind the published one (with the
update / reload hint doctor gives), the profile and the relay connection state.
Without --measure it never attaches anything. --measure adds JSHeapUsedSize,
Nodes, JSEventListeners and Documents (Performance.getMetrics) for
this session's pages and pages chrome-use already holds attached that no other running session holds, and
workers per site (Runtime.getHeapUsage); every other page is skipped with a reason.
--force (with --measure / --watch) measures the rest: a tab that is not
attached is attached, measured and released at once (ab-connect 0.5.34+), never activated or focused. Chrome
shows its debugging bar on it meanwhile, so use it only when the user asked. --watch <s> takes
two samples s seconds apart and adds per-page deltas and a leak class:
listeners_growing_nodes_flat, heap_growing (≥ 1.5 MiB/min), nodes_climbing or
none. Output is bounded (--limit pages, default 200, max 1000; titles cut at 300 and urls at
2048 characters) and says what it left out. Exit codes: 0 ok, 1 usage, 2 the extension relay (or the
--cdp endpoint) is not connected, 3 --measure without --force when every page is
held by another running chrome-use session. The JSON shape is documented in the core skill's
references/diag-pages.md.
tab close on the session's only tab, when the session created it, ends the session the way
close does (sessionClosed: true), so no separate close is needed. A tab
the session did not create (adopted, or the user's) is never closed; the refusal says to leave it open and
run close.
close reads every tab it closed back before reporting it: over the relay from the extension's
ABExt.tabPresence (ab-connect 0.5.33+: Chrome's target registry for that exact target, then the exact
tab id; any API error counts as unconfirmed), or from Chrome's target list on a direct CDP connection. An older
extension cannot confirm a tab is gone, so its tabs stay unverified. The whole close has one 15 s
budget; a tab not confirmed by then stays unverified and keeps its ownership. Closing one of several
tabs (tab close t2) is read back too: a tab not confirmed gone fails and keeps its close right. It returns
tabsClosed (the tabs confirmed gone) and verifiedAbsent: true. If a tab the session
created is still open, or cannot be confirmed gone (unverified), it fails with
close incomplete and names those tabs; the session, its daemon and its tab ownership are kept, so
retry close.
Native duplication requires real Chrome connected through the chrome-use extension. It restores the previously visible foreground tab, while the duplicate becomes chrome-use's internal active tab. There is no same-URL fallback for launched Chrome, raw CDP, Lightpanda, or cloud providers. Chrome decides whether the background duplicate starts loading.
tab select <ref> is equivalent to tab <ref>.
tab adopt <URL substring|targetId> attaches an existing tab in the current session without
reloading it, through either the extension or direct CDP. For direct CDP use chrome-use --session example --cdp 9222 tab adopt <targetId> and retain the same session and endpoint for subsequent commands; top-level adopt still selects the extension relay. On external or extension-connected Chrome, tab list marks tabs as
created, adopted, or foreign. Only created/adopted tabs can be selected,
and only tabs created by the current session can be closed; adopted tabs remain user-owned. Created ownership
survives daemon restarts for the same named session and connected browser endpoint, so interrupted cleanup can resume safely.
tab inspect <ref> reads Chrome's browser-level tab metadata, so it can still
return URL, load status, discard/freeze state, and debugger attachment when page JavaScript blocks the
renderer main thread. eval still cannot complete until that thread responds.
On the extension relay, tab inspect requires ab-connect 0.5.16 or newer. A failed
tab select liveness probe does not by itself prove that the renderer is stuck. If the warning
reports a live extension older than the bundled version, update or reload it at
chrome://extensions, then retry.
A tab that a plain click opens (target=_blank, window.open) is reported as
openedTab and becomes one of the session's tabs: listed by tab list, closed by
tab close and by close. --follow switches to it, and with --observe
the settle and snapshot then describe the new tab; if the switch fails, followed is
false and the session stays on the clicked tab. In your own Chrome only a tab opened by one of the
session's tabs is taken (in the session's tab group, or ungrouped with a session tab as opener); a tab in another
group, or one you open, is left alone. It is attached by Chrome tab id, which needs ab-connect 0.5.30 or newer.
A tab seen but not taken sets openedTabWarning and openedTabStatus:
unadopted (left alone on purpose) or unknown (Chrome or the extension did not answer
within the check's budget of about 2 s to find plus 3 s to attach). tab adopt <url> picks
it up later.
A tab a page opens itself (target=_blank, window.open) brings Chrome to the front over
whatever app you are in (#468). By default chrome-use leaves it to Chrome, exactly like a user's click, and in
your own Chrome the click says so in openedTabWarning. On macOS chrome-use then puts the app that was in front back in front (on by default): only when Chrome comes to the front within 1.5 s of the click with a tab that was not there before, never when Chrome was already in front or you moved to another app (focusRestored / focusNote on the click). Not done on Windows and Linux. AGENT_BROWSER_RESTORE_FOCUS=0 turns it off. Opt-in:
AGENT_BROWSER_BACKGROUND_LINKS=cross-site (also 1/on) makes chrome-use open
cross-site target=_blank links itself, in a tab the session created (left click,
no modifiers, http(s) href to another registrable domain by the Public Suffix List on both ends, no
rel=opener, download or ping): the page's click handlers all run first,
and if none cancelled the click the link opens in a background tab of the session
(openedTabMode: "background"), with the page as referrer (ab-connect 0.5.31+). Its cost against
Chrome's own click: one extra about:blank history entry, and a link that redirects back to the
page's own site arrives there without its SameSite=Strict cookies (the user can
appear signed out). all also takes same-site links, which then always lose those cookies; unset,
0 or off never (the default). Even when on, these go through Chrome and the click says
so: same-site links (unless all), IP addresses, localhost and hosts outside the Public
Suffix List, links a handler changes to another host during the click, links a ChooseBrowser rule applies to,
unclassifiable pages, window.open, rel=opener, named targets, forms, handlers that
stop the click, and pop-ups opened later. The daemon reads the variable when it starts: set it before the
session's first command, or close and start again.
tab new, tab select, and tab adopt stay in the background by default.
Add --activate (alias --front) to raise the target before
renderer initialization or the liveness probe and leave it in the foreground. This changes the visible
tab and focuses its Chrome window over whatever the user is doing, so use it only when the user asks;
activation alone does not prove that page reads work. If new-tab initialization fails, the error
reports the retained target ID. Use tab select <targetId> --activate with the same session
and connection endpoint, then snapshot -i to verify recovery. Do not repeatedly create
tabs or automatically replay a click whose outcome is unknown.
@eN belongs to the tab that was active when the
snapshot ran. To operate on another tab, switch to it first, then snapshot. Labels are never
auto-generated, aren't rewritten after navigation, and are unique within a session.
Frames (iframe)
iframes are detected automatically during a snapshot, and their contents are inlined beneath the iframe element (one level of nesting). refs inside an iframe can be operated on directly; you can also switch frame context for a scoped snapshot.
$ chrome-use frames # [0] top … [1] accessory_layer …
$ chrome-use frame 1 # switch by the index frames prints (0 = top document)
$ chrome-use frame "#iframe" # switch to an iframe by CSS selector
$ chrome-use frame @e3 # switch to an iframe by element ref
$ chrome-use frame main # return to the main frame
A cross-origin overlay is a separate frame — a bank/branch
picker, a payment field. snapshot -i on the page shows nothing of what is
inside it, and focus / press land on the iframe element itself.
The path is frames → frame <index> → snapshot -i
/ click / fill inside it; eval --frame <f> is
the one-off form.
frame accepts three kinds of input: an element ref (frame @e3),
a CSS selector (frame "#payment-iframe"), and a
frame name / URL (matched against the browser's frame tree).
Dialogs
By default alert and beforeunload are accepted automatically so the agent doesn't
block; confirm and prompt still need explicit handling. A click that opens either one
returns a pending-dialog result immediately, so the same session can run the commands below. Use --no-auto-dialog
to turn that behavior off.
$ chrome-use dialog accept [text] # accept the dialog
$ chrome-use dialog dismiss # dismiss the dialog
$ chrome-use dialog status # check whether a dialog is open
Running JavaScript
eval runs in the page's MAIN world, so state persists and handlers fire.
When your code contains nested quotes or special characters, -b / --stdin is more
reliable — shell escaping is easy to get wrong.
$ chrome-use eval "document.title" # simple expressions only
$ chrome-use eval -b "<base64>" # arbitrary JS (base64-encoded)
$ chrome-use eval --stdin # read the script from stdin
# multi-line scripts via stdin + heredoc:
$ cat <<'EOF' | chrome-use eval --stdin
const links = document.querySelectorAll('a');
Array.from(links).map(a => a.href);
EOF
Debug forms / hidden state with eval
# dump every field's name → value, including hidden inputs and unchecked radios
$ chrome-use eval "JSON.stringify([...document.forms[0].elements].map(e=>({name:e.name,type:e.type,value:e.value,checked:e.checked})).filter(e=>e.name))"
# why won't it submit? ask the browser's own validity API
$ chrome-use eval "[...document.forms[0].elements].filter(e=>!e.validity?.valid).map(e=>e.name+': '+e.validationMessage)"
Single-pass scripting (batch / script)
Multi-step flows don't need one round-trip per command. batch runs a fixed sequence in one round-trip;
script adds values between steps, loops, conditions, and asserts. See Single-pass Scripting.
Ordinary CLI --json replies use a success/data/timing envelope; --no-timing (or AGENT_BROWSER_TIMING=0) leaves timing out for a compact reply, and timing.jsonl is still written. batch --json prints an array of {command,success,result,error} entries; script --json prints the bare program result (ok, return, logs, error, advisories, and other program fields). Batch and script CLI output have no top-level timing.
A batch step takes its own per-command flags (--observe, --no-settle, --settle-ms, --with-screenshot, --if-present, --new-tab, --tab): batch "fill @e1 Ada" "click @e2 --observe" puts observed in that step's result. Session-wide flags (--headed, --profile, ...) go before batch; a step carrying one is refused. An explicit --observe false / --if-present false in a step overrides the batch's flag, and a step's --tab never replaces a tab the command names itself (tab close t1 --tab t2 is refused).
Read program outcome separately from transport success: a nested script with daemon-envelope data.ok:false fails its parent even if success:true. JS failures retain ok:false, return:null, error and logs. Both script forms retain observed hints in advisories (top-level in CLI script --json; data.advisories in the daemon envelope) (at most 20), including nested and failed runs; JSON op-list steps also retain each action's noProgress. Text output prints the aggregate hints once. These hints neither change action success nor retry an action.
# JSON op-list: value bus {{name.path}} + waitUntil/forEach/assert/return, --dry-run to validate
$ chrome-use script prog.json
$ chrome-use script - --dry-run < prog.json
# JS form: a synchronous JS program driven by cu.* helpers (ego's code-base idiom)
$ chrome-use script --timeout 120000 <<'JS'
cu.open('https://news.ycombinator.com');
const rows = cu.eval("[...document.querySelectorAll('.athing')].length");
return { count: rows };
JS
Options: --timeout <ms>, --dry-run (validate the JSON program only), --yes (auto-confirm inside the script),
--arg k=v (seed a variable). Exit codes: 0 ok, 1 runtime failure or failed assert, 2 invalid program.
Agent loop (experimental)
jev run drives a goal end to end: TypeSafe's Jev picks each step's operation and
target from an indexed element table, and a small model writes text only when a field needs
typing. Needs TYPESAFE_API_KEY (or ~/.config/typesafe/key).
$ chrome-use jev run --goal "find a flight from Zurich to London" --url https://www.google.com/travel/flights
# Also ask whether the chosen action ends the goal, and record that claim
# against what the closing decision then decided
$ chrome-use jev run --goal "..." --terminal-shadow
The run reports elapsed_ms, jev_ms, text_ms, observe_ms, act_ms, fresh_ms, and decision/action counts. Measure the actual task; timing proportions from one page or provider do not generalize.
--terminal-shadow adds a completion-prediction question to the model request. It records terminal_predicted, terminal_condition_observed, terminal_confirmed_done, and terminal_refuted. It does not skip the normal completion decision. Agreement with a later model decision is not independent proof of task success; adding a question can also affect model choices and latency.
stale_by_phase and stale_fields describe marker-based freshness rejections using categories rather than raw page text. Click/select guard rejections are not included in those breakdowns. A changed URL or a control leaving the actionable set is not a completion test. These counters do not establish safely removable model calls.
JEV_TRACE=<file> appends one JSON line per decision: the request that was sent, the candidates the model was shown (id, kind, label, current value, checked state), its choice, and Jev's raw answers with probabilities. It is off unless set. It records the goal, the page text and field values, including anything already typed into the form, so treat the file as sensitive. The run report also splits act_ms into act_read_ms, cmd_click_ms, cmd_press_ms and cmd_insert_ms.
State management
$ chrome-use state save auth.json # save cookies, storage, and auth state
$ chrome-use state load auth.json # restore saved state
Init scripts
$ chrome-use open --init-script <path> # register before the first navigation (repeatable)
$ chrome-use addinitscript <js> # register at runtime (returns an identifier)
$ chrome-use removeinitscript <id> # remove a registered init script
React / Web Vitals
The react ... commands require launching with --enable react-devtools;
vitals and pushstate are framework-agnostic.
$ chrome-use open --enable react-devtools <url> # launch with the React hook
$ chrome-use react tree # full component tree
$ chrome-use react inspect <fiberId> # props / hooks / state / source
$ chrome-use react renders start # start recording re-renders
$ chrome-use react renders stop [--json] # stop and print the render analysis
$ chrome-use react suspense [--only-dynamic] [--json] # Suspense boundaries + classifier
$ chrome-use vitals [url] [--json] # LCP/CLS/TTFB/FCP/INP + hydration
$ chrome-use pushstate <url> # SPA client-side navigation (auto-detects Next router)
Accessibility audits
a11y runs the axe-core engine embedded in the chrome-use binary. It does not fetch a CDN script,
works under strict page CSP, and preserves any page-owned window.axe value. The report includes
same-origin and cross-origin iframe findings with their frame selector paths. Safari and iOS WebDriver
sessions are not supported because the audit requires CDP.
$ chrome-use a11y # audit the current page
$ chrome-use a11y https://example.com # navigate, then audit
$ chrome-use a11y --tags wcag2a,wcag2aa # filter by axe rule tags
$ chrome-use a11y --selector "#main" # scope to one subtree
$ chrome-use a11y https://example.com --json # structured output
Text output lists impact, rule id, fix guidance, and failing selectors. JSON output contains counts plus
trimmed violations and incomplete arrays. MCP clients can call
chrome_use_a11y from the all tool profile.
Debugging
console / errors capture is off by default — an active CDP
Runtime domain is a detectable bot signal. Start the session with
AGENT_BROWSER_CAPTURE_CONSOLE=1 when you need it.
$ chrome-use --headed open example.com # show the browser window
$ chrome-use --cdp 9222 snapshot # connect over a CDP port
$ chrome-use connect 9222 # alternative: the connect command
$ chrome-use console # view console messages (needs AGENT_BROWSER_CAPTURE_CONSOLE=1)
$ chrome-use console --limit 20 # only the last 20 console messages
$ chrome-use console --level error,warn # errors and warnings only
$ chrome-use console --filter cart --limit 5 # last 5 lines mentioning cart (filter first, then tail)
$ chrome-use console --clear # clear the console
$ chrome-use errors # view page errors (needs AGENT_BROWSER_CAPTURE_CONSOLE=1)
$ chrome-use errors --clear # clear errors
$ chrome-use diagnose # why is the page blank: checks asset 404/MIME + mount state
$ chrome-use highlight @e1 # highlight an element
$ chrome-use inspect # open DevTools for this session
$ chrome-use trace start # start recording a trace
$ chrome-use trace stop trace.zip # stop and save
$ chrome-use profiler start # start profiling
$ chrome-use profiler stop trace.json # stop and save the profile
Find pages the user saved (find-url)
Search local Chrome/Edge bookmarks by keyword — for internal systems or previously saved
pages that public search can't reach. Reads locally; no browser / daemon required. Results are sorted by
most recently added, and javascript: / data: bookmarks are skipped.
$ chrome-use find-url jira board # all keywords must match (name or url)
$ chrome-use find-url --limit 10 invoices
$ chrome-use find-url --browser edge --profile "Profile 1" wiki
$ chrome-use find-url grafana --json # {results:[{name,url,folder}], count}
Drive your real logged-in Chrome (extension)
Chrome 136 locked down --remote-debugging-port on the default profile, so to drive the user's
existing logged-in window, chrome-use goes through a Chrome extension + native
messaging — no port, no token, no per-use confirmation. A one-time install writes the native messaging host
manifest:
$ chrome-use extension install # write the native messaging host manifest (one-time)
$ chrome-use status # CLI, extension, relay, profile, and session health
$ chrome-use extension connect # auto-attach to the real logged-in tabs
$ chrome-use tab # list the real tabs it now controls
$ chrome-use tab t3 # switch the session to one of them
$ chrome-use snapshot -i # drive it like any normal session
$ chrome-use extension status # is the host installed?
$ chrome-use extension uninstall # remove the host manifest
# macOS: skip the policy profile that puts Chrome in managed mode
$ chrome-use extension install --no-profile
$ chrome-use extension install --all-profiles # open the Store page in each missing profile
$ chrome-use extension install --user-data-dir <path> # register the host for a Chrome started with --user-data-dir (macOS/Linux; running ones are found automatically)
Extension 0.5.48 adds a guarded native-tab-group operation for Thermo. It accepts only a reviewed plan, rechecks each tab, preserves existing/current/pinned/audible/private/agent tabs, and never crosses windows or profiles. It journals before mutation, verifies the result, and supports one-shot undo without replaying uncertainty. Normal chrome-use session ownership rules are unchanged.
--no-profile, or undo it with
profiles remove -identifier com.leeguoo.chrome-use.connect (Chrome then uninstalls the
policy-installed extension — re-add it from the Store). chrome-use extension status always
reports where you stand.
chrome-use status distinguishes an installed manifest from a runnable native-host launcher.
JSON output exposes the launcher result as extension.hostHealthy.
knfcmbamhjmaonkfnjhldjedeobeafmk).
A loaded unpacked dev build can be disabled / dropped when Chrome restarts, silently breaking the relay — use
the Store build for unattended scenarios.
--extension <path> is unrelated — that loads an extension into a
freshly launched browser.
Global options
These flags go before the command and apply to the whole session. Note: headless mode is forbidden
(a bot signal) — it's headed by default and always (stealth); on a display-less server, use
AGENT_BROWSER_ALLOW_HEADLESS=1.
$ chrome-use --session <name> ... # isolated browser session
$ chrome-use --json ... # JSON output, easy to parse
$ chrome-use --headed ... # default and always on (stealth)
$ chrome-use --full ... # full-page screenshot (-f)
$ chrome-use --cdp <port> ... # connect over CDP
$ chrome-use -p <provider> ... # cloud browser provider (--provider)
$ chrome-use --proxy <url> ... # use a proxy server
$ chrome-use --proxy-bypass <hosts> # hosts that bypass the proxy
$ chrome-use --headers <json> ... # HTTP headers scoped to the URL origin
$ chrome-use --executable-path <p> # custom browser executable
$ chrome-use --extension <path> ... # load an extension (repeatable)
$ chrome-use --ignore-https-errors # ignore SSL certificate errors
$ chrome-use --hide-scrollbars false # keep native scrollbars in headless Chromium screenshots
$ chrome-use --help # help (-h)
$ chrome-use --version # version (-V)
$ chrome-use <command> --help # detailed help for a command
Environment variables
AGENT_BROWSER_SESSION="mysession" # explicit session name
AGENT_BROWSER_SESSION_ID="agent-id" # stable runner id for auto-isolation
CODEX_THREAD_ID="thread-id" # recognized automatically for Codex tasks
AGENT_BROWSER_EXECUTABLE_PATH="/path/chrome" # custom browser path
AGENT_BROWSER_EXTENSIONS="/ext1,/ext2" # comma-separated extension paths
AGENT_BROWSER_INIT_SCRIPTS="/a.js,/b.js" # comma-separated init script paths
AGENT_BROWSER_ENABLE="react-devtools" # comma-separated built-in init features
AGENT_BROWSER_HIDE_SCROLLBARS="false" # keep native scrollbars
AGENT_BROWSER_PROVIDER="browserbase" # cloud browser provider
AGENT_BROWSER_STREAM_PORT="9223" # override the WebSocket stream port
AGENT_BROWSER_DASHBOARD_ALLOWED_HOSTS="ws.example.com" # extra hostnames the dashboard accepts (reverse proxy); loopback names only otherwise
AGENT_BROWSER_CLICK_MODE="dom" # click strategy: ""(default) / "coord" / "dom"
AGENT_BROWSER_BACKGROUND_LINKS="cross-site" # opt-in (#468): cross-site target=_blank links open in a background tab. unset/"off"/"0" (default) never; "cross-site"/"1"/"on"; "all" same-site too. Cost: redirect-back links lose SameSite=Strict cookies; read at daemon start
Stealth / anti-detection knobs (fork)
AGENT_BROWSER_CAPTURE_CONSOLE="1" # enable console/errors capture (off by default; the Runtime domain is a bot signal)
AGENT_BROWSER_TIMEZONE="Asia/Tokyo" # --launch only. native timezone override (IANA id or "auto")
AGENT_BROWSER_BLOCK_WEBRTC="1" # --launch only. hide the local IP via WebRTC
AGENT_BROWSER_HIDE_CANVAS="1" # --launch only. session-stable canvas/audio fingerprint noise
AGENT_BROWSER_ADAPTIVE_REF="0" # disable adaptive @ref relocation (on by default)
{"messages":[]} / {"errors":[]} plus a
hint until you start the session with AGENT_BROWSER_CAPTURE_CONSOLE=1 — this keeps the
CDP Runtime domain disabled on the common automation paths. When something doesn't work, cross-check
Troubleshooting.
Semantic find role/text/label/placeholder/alt/title/testid requires exactly one visible match, including locate-only queries. Ambiguity returns up to eight candidates with visible state and selector/context hints; no action is dispatched and input values are omitted. Narrow --name/--exact, or use --within <CSS|@ref> to restrict the query to exactly one container. A scope must belong to the active tab's main document; cross-frame refs and an explicitly selected iframe are refused; use direct frame refs or `frame main`. find first/last/nth explicitly selects an order and keeps its existing behavior. A plain CSS selector or XPath given to an action (click, fill, type, hover, check, select, drag …) must match exactly one element too: one that matches several is refused with code selector_ambiguous, listing the first five matches with their text and position, and nothing is done. Narrow it, use a @ref, or choose by index with find nth <index> <selector> <action>; AGENT_BROWSER_STRICT_SELECTORS=0 (read when the session's daemon starts) restores first-match for old scripts. Roles and label names use Chrome accessibility data, including aria-labelledby; a semantic query re-resolves on each call. A detached target before dispatch fails safely; uncertain actions are never replayed.
Discover a scope from the page first: find query "Beta account" returns candidate selector anchors; choose the article/container corresponding to that heading, then use find role button click --name Save --exact --within "<returned-selector>". Inspect an ambiguous scope with snapshot -s "<selector>" and narrow it rather than accepting the first container. Unknown semantic-find options and duplicate --within are refused; use find label Email fill -- --name to enter literal flag-looking text. --exact false requests substring matching.
The existing chrome_use_find tool in mcp --tools all accepts within with the same unique-scope rules; tool count is unchanged. text for fill/type is passed literally, including --name --observe, rather than parsed as CLI options.
Semantic find retains the existing --observe unsupported warning; use one batch containing the scoped find action and a task-specific get text receipt when both are known. The MCP find tool does not advertise an observe field.