Imported from galaxyeye/browser4.io (
public/SKILL.md). Install upstream withnpx skills add galaxyeye/browser4.io --skill public. Copyright stays with the author.
Browser Automation with browser4-cli
Browser automation CLI for AI agents — Chrome/Chromium via CDP with accessibility-tree snapshots.
This is the decision layer: the core loop, the command map, and which reference to open. Every topic links to a deep dive — load only what the task needs (§7 Reference Map).
Invocation
After installing (browser4-cli install), invoke commands directly — the docs use browser4-cli as the generic command name:
browser4-cli open --headless <url>
Windows / Git Bash (MSYS) users. From Git Bash (or any MSYS2 shell), arguments starting with
/are rewritten into Windows paths before PowerShell starts, so./b4w.ps1refuses them (the original value is unrecoverable). Invoke./b4w.sh <command>, or prefixMSYS2_ARG_CONV_EXCL='*' ./b4w.ps1 <command>. Where the value is a substring/regex rather than an exact path, drop the leading slash —-olp "product/"still matches/product/…hrefs — andcookie-set --pathalready defaults to/, so the flag can be omitted entirely — for example./b4w.sh crawl "<url>" -d 2 -ol "a.product" -olp "product/". The same rewrite hits--path /,pushstate "/route"and the--path=/spelling — see shell-quoting.md.
1. Core Loop
⚡ First-run latency: From a source tree, the first launch builds the runtime bundle via Maven (~1–3 min, before the spinner appears) and then starts the Browser4 backend (Spring Boot + JVM, ~10s). Subsequent commands are instant — the server stays alive between invocations.
🖥️ Headless mode is the default for AI agents: Always open browsers with
--headlessunless the user explicitly asks to see the browser window ("show me the browser", "open visibly", "I want to watch", or "headed"), a human must act on the page, or the site blocked the headless browser — in that last case retry--headedonce (§2 Display Mode).
🛑 Dev mode never serves a stale backend. From a source tree the backend must be the checked-out code. An already assembled runtime bundle is reused as-is while it matches the checkout. If it was built from a different project version, the command fails with a non-zero exit and names the reason, the bundled/checked-out versions, the bundle's build time and the rebuild command — nothing is started. If the checkout's sources merely look newer than the bundle jars, the command rebuilds from source and then starts (that signal cannot tell a real source change from a touched-but-unchanged tree, so refusing on it would block startup on a false positive). Rebuild with
powershell -ExecutionPolicy Bypass -File browser4-apps/browser4-bundle/build-runtime-bundle.ps1(pwsh -File …on Linux/macOS) or re-run withBROWSER4_CLI_FORCE_REBUILD_BUNDLE=1. To deliberately test the older backend, set the single opt-outBROWSER4_CLI_ALLOW_STALE_BUNDLE=1— the warning stays, andstatus/doctorkeep reporting the skew (local_bundlein--json).
1. OPEN browser4-cli open --headless <url> # headless by default for AI agents
browser4-cli goto <url> # or goto to navigate within existing session
2. SNAPSHOT browser4-cli snapshot -v 0 # capture accessibility tree (viewport 0 = current visible screen)
3. INTERACT browser4-cli click <ref> # use refs from the snapshot
browser4-cli fill <ref> <value>
browser4-cli press Enter
4. RE-SNAPSHOT browser4-cli snapshot -v 0 --auto-diff # verify what changed (diff vs previous)
5. EXTRACT browser4-cli htmlsnapshot get ... # or eval, or X-SQL (see §4)
Copy-Paste Template
browser4-cli open --headless "https://example.com" # headless by default for AI agents
browser4-cli snapshot -v 0 --stdout # read the page; note refs
browser4-cli fill <ref> "<value>" # interact
browser4-cli press Enter
browser4-cli wait --load networkidle # prove the network settled — nothing more
browser4-cli wait "<result-selector>" # poll the element carrying the result (late-rendered pages)
browser4-cli click <ref> --no-snapshot # interact without overwriting the diff baseline
browser4-cli snapshot -v 0 --auto-diff --stdout # verify what changed vs pre-interaction page
browser4-cli htmlsnapshot # snapshot metadata for the page the tab is showing
browser4-cli htmlsnapshot get all text "<css-selector>" # extract from the tab's fresh snapshot
For quick inline viewing without opening a file, add --stdout to any snapshot command.
2. Key Concepts
Element Refs
After commands that modify browser state, browser4-cli saves an accessibility-tree snapshot — a YAML file showing the page structure:
- generic [ref=e7]:
- link "News" [ref=e191]: /url: https://example.com/news
- textbox "Search query" [ref=e35]
- button "Search" [ref=e25]
Each interactive element has a ref (e5, e12) — the element's Chrome DevTools Protocol backend node ID prefixed with e (so e12345 refers to backend node 12345). Use refs to target elements in click, fill, type, get attr, etc.
Note:
/urlfields may be relative (e.g./url: news) — the snapshot output includes the page URL at the top for resolution. Add-u/--urlsto include link hrefs (they are omitted by default), and-b/--briefwhen you only need the page URL and title.htmlsnapshot get [all] attrreturns the RAW attribute value (hrefs are often relative); add--absoluteto resolve href/src against the page URL, e.g.htmlsnapshot get all attr "a[href]" href --absolute— that is how you get absolute URLs after redirect resolution.
Ref Lifecycle
Refs are ephemeral — treat them as single-use handles:
- Always re-snapshot after interactions:
click,fill,type,press,check,uncheck,select,hover,drag,dblclick. - Definitely re-snapshot after page/context changes:
goto,reload, tab switches, or clicks that navigate/update the page. If you are chaining form actions: rely on the automatic post-action snapshot, then use refs from that fresh snapshot for the next step. - Interactions auto-scroll the target into view, so a later
snapshot -v 0can start mid-page (nonzerohiddenTopHeight) —eval "window.scrollTo(0,0)"or capture-v allwhen documenting the top of a page.
In practice, the safest loop is interact → re-snapshot → use new refs. Interaction commands capture an automatic snapshot after execution; pass --no-snapshot to skip it when you plan to capture a fresh snapshot manually (saves a round-trip).
Output Modes
- Default — human-readable output on stdout.
--show-tip/-tip— show a relevant, rotating tip on stderr after each successful command. Tips are suppressed by default; use this flag to enable them.--json— single-line JSON envelope on stdout for commands that support structured output. This is the clean machine-readable mode for commands such astab-list,htmlsnapshot get,htmlsnapshot query, andeval. Exception:snapshotremains YAML-focused and warns on stderr instead of returning JSON snapshot data.- File output (default for AI commands) —
extractandsummarizesave their result to a timestamped file in.browser4-cli/snapshot/and print only a link; add--stdout(or--raw) to print the payload directly. Whenextract --schemais used, the requested schema fields are emitted as plain top-level JSON (in the file and on stdout) — no envelope to parse. Without--schema,extractalways returns the fixed{title, content, links}envelope: the instruction only guides which text ends up insidecontent(asking for "price and rating as JSON" still returns one prose blob) — pass--schema(JSON Schema or{"fields":[…]}) for named fields. --quiet/-q— suppress all normal output; only errors appear on stderr.- Global flags after the command —
-q/--quietand--timeout <secs>/--timeout=<secs>are also accepted after the command name (they are hoisted into the global flags):browser4-cli htmlsnapshot -q,browser4-cli webdb export "url1,url2" ./out --timeout 30. Two exceptions:--jsonafter the command belongs to the command (batch --jsonreads JSON from stdin) and is never hoisted, and--timeoutis hoisted only when the command does not define its own —wait --timeoutstays in milliseconds. A global flag that still lands as a stray positional is rejected with the hint "global flags must appear before the command".
Display Mode (Headless vs Headed)
| Mode | Flag | Window | Use case |
|---|---|---|---|
| Headless | --headless |
No GUI window | Default for AI agents — scraping, automation, CI/CD, servers |
| Headed | --headed |
Visible window | Debugging, demonstration, a human must act, or the one-shot bot-detection retry |
Use --headed for exactly three reasons: (1) the user explicitly asks for a visible browser; (2) a human must act on the page (login, CAPTCHA, QR code, 2FA); (3) anti-bot escalation.
Choosing the whole setup (session × display × browser source — including when to attach to your own browser instead of launching one): see browser-modes.md.
Rule for AI agents: always use --headless by default. Use --headed only when the user explicitly requests a visible browser ("show me the browser", "I want to see", "open visibly", "headed", "watch what happens"). If user participation in the page interaction is required — e.g., logging in or entering a verification code (CAPTCHA) — open a headed browser so the user can see and act on the page.
Anti-bot escalation — retry once: when a page looks blocked (CAPTCHA/verification widget, Cloudflare/DataDome/Akamai/PerimeterX challenge, Google /sorry/, "unusual traffic" / "access denied"), close then open --headed with the same -s <name> so profile and cookies survive, retry the same step once, and tell the user the mode was switched. If the headed retry is blocked too, stop — the block is fingerprint/IP-level: prefer attach --cdp / attach --extension (a real logged-in profile), raise --interact-level, or report the site as unreachable. GUI-less environments (CI, Docker) degrade --headed to headless — say the retry ran headless instead of claiming a visible window. goto/open run a one-call probe after a successful navigation and print this escalation as an advisory stderr warning (naming the signature that fired) when the landed page matches a challenge URL or body marker; the command still succeeds, so act on the warning yourself rather than waiting for a failure (--json reports it as challenge_detected / challenge_signature). The weaker "implausibly empty body" signal (few visible chars and no iframes — widget/demo pages are exempt, as are content paths like /wiki/…) only advises a retry and a snapshot inspection, not a mode switch — do not pay a session teardown for that signal alone.
Set the display mode with open when starting a new session; goto does not accept --headless/--headed — it inherits the session's mode:
browser4-cli open --headless https://example.com # headless (preferred default)
browser4-cli open --headed https://example.com # headed (only when user asks)
browser4-cli goto https://other-page.com # stays headless (or headed) as set by open
Notes: When
gotois the very first command (no prioropen), it auto-opens a new session using the CLI's default display mode, which is headless.--headless/--headedonly take effect when creating a new session — whenopenreconnects to a running session they are ignored (the CLI warns on stderr). To change the mode,closefirst, thenopen --headless. Useopen --freshto discard a stale session's tabs/cookies/location entirely and start clean.
Sessions
Named sessions isolate browser state (cookies, localStorage, tabs) in a dedicated browser profile directory keyed by the session id — reopening always restores the same profile. Use -s <name> to target a named session; goto auto-opens/reconnects. list shows a "Next open" column: Reuse (reconnects to the active window) or Refresh (opens fresh — session stale/missing).
Concurrent runs — always pass
-s <name>: the unnamed DEFAULT session is a singleton shared by every invocation that omits-s(last writer wins), so parallel agents navigate each other's pages. Give each run its own-s job-42.
-sis a GLOBAL flag — its position is part of the syntax. Write it before the command:browser4-cli -s job-42 goto <url>. Written after the command —goto <url> -s job-42— it is not recognised as the session option and is rejected as an unexpected positional argument. The long form--session <name>behaves identically (also before the command). Because-sis reserved globally for--session, scope a snapshot with the long form--selector <css>, never-s <css>.Other ways to pin the session:
BROWSER4_CLI_SESSION=job-42(per-invocation env var, overridden by-s/--session),browser4-cli config set session job-42(persisted default), orbrowser4-cli session-default <name>(promote an existing named session to the default).browser4-cli listshows every session and its current page URL (a URL column from the backend, capped to 48 chars — runlist --verboseorlist --jsonfor the untruncated URL;-means the backend is unreachable or the saved session is stale, and?means the session is live but its page URL could not be read — the CLI prints that legend on stderr, andpage-info -s <name>queries one session directly). A locally-saved session the backend no longer recognizes is listed as Stale / Refresh (the same rowclose-allcounts), not hidden.
The list command also has a Display column with the mode fixed at session creation — Headed / Headless / Supervised / Attached (- when unrecorded).
stopkeeps this checkout's session records;closedeletes them.stop(andkill-all) shuts the backend down and marks everysessions/*.jsonrecord stale — the records stay on disk,listshows them as Status Unknown · Next open Refresh, and the nextopen/gotorevives the same session id with its cookies and profile intact.close/close-allinstead remove the record of the session they close, so a lateropen -s <name>mints a new session id and a fresh profile: runstate-save/state-loadaround aclosewhen browser state must survive.
list --all adds a Workspace column and includes the sessions persisted by every other checkout on the machine — in development mode each checkout keeps its own state namespace under ~/.browser4/workspaces/, so a session started in a sibling checkout is invisible to a plain list. The workspace you are running in is marked *. Those foreign rows come from local state only: this backend cannot verify another workspace's sessions, so they read Status Unknown · URL - · Next open Refresh — never treat them as dead, and close them from their own checkout. --json adds workspace + workspace_current to each row when --all is given.
Two on-disk locations — don't confuse them:
- Session state lives in
~/.browser4by default (per checkout in development mode — see Development Mode below), falling back to./.browser4-cli-statewhen unwritable; override withBROWSER4_CLI_STATE_DIR/BROWSER4_RUNTIME_DIR. - Snapshots (accessibility-tree captures from
snapshot/htmlsnapshot) live in./.browser4-cli/snapshot/, relative to the directory the command runs in — the same directory theextract/summarizefile output uses. Look there when hunting saved snapshot files or cleaning up capture artifacts.
Configuration
CLI defaults (config.json: server, timeout, proxy, session, extension_id) and server-side runtime overrides are managed by the config command family — config list prints every value, config set server <url> pins a backend; see config.md for the full key reference.
browser4-cli config # list values + config file path
browser4-cli config set server http://localhost:18182
browser4-cli config set timeout 45 # positive integer; 0 and unknown keys are rejected
browser4-cli config set extension_id <id> # id of a locally loaded ("Load unpacked") extension
browser4-cli config delete session # reset a key to its default
Use the spaced form (config get server), not config-get server.
Development Mode (one backend per checkout)
Running the CLI from inside a Browser4 checkout (a directory holding ROOT.md + pom.xml) enables development mode: each checkout owns a backend port (first free from 8282 upward), a state namespace (~/.browser4/workspaces/<checkout>-<hash>/) and a backend app data root, so parallel checkouts and git worktrees run side by side — stop then stops only this checkout's backends. Full matrix, shared paths and escape hatches: development-mode.md.
Tab Management
Tab commands (tab-list, tab-new, tab-select, tab-close, window new) scope to a session. Re-snapshot after tab-select — tab switches change the active page context. See tab-management.md for the workflow (quick start, recipes, error recovery) and tabs.md for the command behaviour (GUID forms, --json envelope, tab insert position, the last-tab rule, extension-session quirks).
tab-new and window new are the same operation in the managed/headless backend: both create an additional tab/target in the same browser, auto-select it, and print Created tab with GUID: … / ✓ Switched to tab <n> (window new is the legacy spelling). A page-opened popup (window.open, target="_blank") is an ordinary new target that the CLI neither announces nor switches to, so run tab-list after triggering it, select the entry with tab-select <index|guid>, then confirm with page-info. Whether every popup type lands in tab-list is untested — verify on your build.
Frame Switching (iframes)
Element commands (click, fill, type, …) resolve CSS selectors against the main document by default. For iframe content, switch first: frames lists the frame tree; frame "<target>" switches (element ref, CSS selector, frame name/id, or URL fragment — nested frames by switching repeatedly); frame main returns. Scope resets on navigation. Same-origin iframes are fully supported; cross-origin ones fail with an actionable error. eval always runs in the main document. See frames.md for details.
Output shape:
frames,frame <target>andframe mainprint the backend's raw JSON, not an indented tree —framesreturns objects withid/url/depth/active/label/isMainFrame, andframe maincan print an empty payload. Copyid/urlfromframeswhen a selector or name is ambiguous, and remember element-command help never mentions frame scoping: while a frame is selected,click/fill/is visibleresolve inside that frame.
Emulation (set)
set geo|offline|headers|media|device issues CDP emulation commands against the current tab of the
session and prints what it applied (✓ Geolocation emulated: latitude=31.23, …) — no read-back command exists.
| Mode | Command (required flags in bold) | Survives goto / reload |
How to undo |
|---|---|---|---|
| Geolocation | set geo --lat <d> --lon <d> [--accuracy <m>] |
yes | cdp Emulation.clearGeolocationOverride |
| Offline | set offline on|off |
yes (off = normal network) |
set offline off |
| Extra headers | set headers --json '{"X-Api-Key":"abc"}' |
yes | set headers --json '{}' |
| Media | set media --color-scheme light|dark|no-preference |
yes | --color-scheme no-preference |
| Device metrics | set device --width <px> --height <px> [--dpr <n>] [--mobile] |
yes — re-applied after goto; resize keeps the scale factor |
cdp Emulation.clearDeviceMetricsOverride |
- Device metrics survive navigation and resize.
gotore-applies the active emulation after the backend normalises its viewport, andresizecarriesdeviceScaleFactor/mobileforward, soset device --width 390 --height 844 --dpr 2 --mobilestands until you clear it or close the tab. Verify from the page:eval "innerWidth + 'x' + innerHeight + ' dpr ' + devicePixelRatio". - Geolocation needs the browser's geolocation service enabled.
set geogrants the permission for the page origin (without itgetCurrentPosition()never settles), but a browser launched with--disable-geolocation(the default, so unattended browsers never block on a prompt) has the service off, and a launch flag cannot be undone at run time — relaunch withbrowser.launch.disable.geolocation=falseif the position still never resolves. - Inputs are validated:
--lat/--lon,--jsonand--width/--heightare required for their mode, a mode's own options are rejected on another mode (set geo --dpr 2), an unknown mode names the closest valid one, and malformed--jsonheaders are refused without clearing the active headers. Only--accuracy(1) and--dpr(1) default.set device --mobileonly sets the metrics override's mobile flag (maxTouchPoints10):navigator.userAgentstays the desktop UA, so a UA-sniffing site sees a desktop browser on a phone-sized viewport. Overrides live on the tab's CDP session, so a new tab/session starts un-emulated; values persist until changed or the session closes.
3. Command Map
| Command family | Purpose | When to use | Full reference |
|---|---|---|---|
goto, open, close, close-all, reload |
Navigation & session management | Every session starts here; close-all cleans up every session |
— |
snapshot |
Capture accessibility tree (AXTree) with element refs; /url lines for links are opt-in via -u/--urls; --selector fails loudly when the CSS selector matches nothing |
Page structure & interaction — find elements to click, fill, etc. Use snapshot when you need refs (e5, e36) to interact with. |
snapshot.md |
snapshot grep |
Search the live full-page AX tree with regex (not the last saved file; add --diff to search the persisted auto-diff report of the last snapshot --auto-diff instead) |
Find elements by text or pattern. Patterns are Rust regex — | is alternation, and a literal $ is safest written [$] (-i, -A/-B/-C, -F, -v, -c, -l supported). Zero matches = exit 1 |
— |
click, dblclick, drag, hover, mousemove, fill, type, press, select, check, generate-locator |
Page interaction | Form filling, button clicks, mouse actions, navigation. Clicks/hovers move the pointer onto the element first; mousemove 5 5 clears a lingering :hover. type --method auto|chars|exec (needs a target ref) bulk-inserts long (>150 chars) or multi-line text in one execCommand('insertText') instead of typing per character |
— |
upload <ref> <file> [file...] |
Upload local files to a page file input | Send attachments/photos/documents to an <input type="file">; the target must be a file input, paths must be readable on the machine running the browser |
upload.md |
focus, key, keyboard |
Focus an element / press a key (key & keyboard alias press) |
Explicit focus before typing, agent-browser-style keypresses | — |
is visible|enabled|checked <sel> |
Element state assertions | Verify visibility, enabled-ness, or checked state before acting. Prints true/false (and null from is enabled on a missing selector) and exits 0 on current builds — read the value, or use --json (see §5) |
— |
dialog-accept, dialog-dismiss, dialog-status |
Native JS dialog handling | After clicking buttons that trigger alert/confirm/prompt; dialog-status inspects the pending dialog. The triggering click parks server-side until the dialog is handled, so run the dialog command in a separate invocation — dialog-accept "text" fills a prompt, or click --auto-dismiss-dialogs <ref> auto-accepts in one step |
— |
htmlsnapshot get, get all |
Extract text / textcontent / html / attr via CSS selectors from a fresh snapshot of the active page (the tab is captured first, then read); --expires <dur> serves the stored snapshot instead while it is younger than the window |
Page content & text extraction — get article text, headings, attributes, including content that only exists in the tab (form results, SPA updates, eval mutations); --expires 1d reads the previous snapshot version without touching the tab. text is the normalized element text; textcontent is the raw textContent (original whitespace/newlines, hidden text included). attr returns the RAW attribute value (href/src are often relative) — add --absolute to resolve it against the page URL. Neither text field recovers text the page itself truncated in its HTML source — use attr (often title) or the detail page |
htmlsnapshot.md |
get <mode> <selector> [name] |
Live-DOM single-element read (text, html, box, styles, property, attr) — capture-free, works on the current live page with refs or CSS selectors (--raw keeps text verbatim) |
Post-interaction verification & quick reads — "did the submit work?" without a capture round-trip. Value contract: a matched element returns its value — or "" when the attribute/property is absent; null means the selector matched nothing; an unresolvable eN ref errors explicitly |
— |
htmlsnapshot (capture) |
Serialize the page the active tab is showing into the page store and return metadata — the same capture every htmlsnapshot command runs before it works | Get the snapshot's metadata (title, size, timestamps, interactive elements, link groups) | htmlsnapshot.md |
htmlsnapshot readability |
One-step article extraction via a Readability-style heuristic (no LLM, no selectors) | Get the main article (title, byline, text) from the active page in one call; htmlsnapshot readability <url> reads that URL's own stored copy (never the tab's document) |
htmlsnapshot.md |
htmlsnapshot query |
X-SQL queries for structured extraction over a fresh snapshot of the active page (--expires <dur> queries the stored snapshot instead) |
Multi-field, filtered, sorted data; the query captures the tab first, so live state (login, SPA, eval mutations) is visible |
x-sql.md |
htmlsnapshot summary [--algorithm <id>] |
Compressed WPSI YAML summary of a fresh snapshot (--expires <dur> for the stored one); the summarization algorithm is pluggable (built-in wpsi, plugins can add ids) |
Understand a page's structure/landmarks/key nodes cheaply; pick a non-default algorithm for specialized summaries | htmlsnapshot.md |
htmlsnapshot algorithms |
List installed summary algorithms (id, default marker, built-in vs plugin); no page or session needed |
Discover valid --algorithm ids |
htmlsnapshot.md |
eval |
Execute JavaScript in the page (--await for Promises/fetch, --wait-selector for late-rendered content, --file/--stdin/--base64 to dodge shell quoting, --world main|isolated to choose the JavaScript world). Runs in the page's own world by default, so page-defined globals resolve; JS undefined prints as "", like an empty string |
Live DOM access, complex transforms | eval.md |
eval --ref |
Execute JS scoped to a specific element | Element property extraction (text, attrs, styles) | ⚠️ Expression MUST be an arrow function: element => element.textContent |
scrollintoview, pushstate, highlight |
Element scroll / history / visual highlight (eval-based shortcuts) | Scroll an element into view, push a history entry, outline an element | — |
vitals, web-vitals |
Core Web Vitals measurement (LCP, CLS, INP, FCP, TTFB) via injected web-vitals lib | Performance checks on a live page (needs network for CDN) | — |
set geo|offline|headers|media|device |
CDP emulation: geolocation, offline mode, extra headers, color scheme, device metrics | Emulate locations, networks, devices, and media features | — |
errors |
Console errors only (alias of console --min-level error) |
Surface page JS errors fast | — |
extract, summarize, agent run |
AI-powered extraction | Natural language extraction (needs LLM key). extract without --schema returns a fixed {title, content, links} envelope — the instruction shapes the text inside content, and --schema is what produces named structured fields |
agent.md |
crawl |
Recursive crawling + bulk extraction | Multi-page traversal, seed-file processing | crawl.md |
swarm |
Parallel scraping across browser contexts | High-throughput extraction | swarm.md |
loop |
Repeated task execution with persistence | Monitoring, scheduled checks | loop.md |
state-save, state-load, cookie-*, *-storage-* |
Browser storage management | Auth state reuse, cookie manipulation | storage-state.md |
attach |
Connect to existing Chrome/Edge via CDP | Debug live browser, reuse auth — after attaching, check the printed actual browser; a ⚠ warning flags a channel mismatch | attach.md |
webdb export, webdb normalize |
Export cached pages, normalize URLs to database keys | Post-crawl content extraction, URL key lookup | webdb.md |
skills, skills get, skills path, skills unpack |
Bundled AI agent skill files | Refresh agent instructions, unpack skill files | skills.md |
skill-list, skill-info, skill-install, skill-uninstall, skill-reload |
Backend skill management | Install/manage server-side skills | skills.md |
screenshot, scroll, wait, resize |
Visual capture & viewport control | Screenshots, viewport sizing, scroll control. A wait whose condition never became true exits non-zero; wait --download polls a download directory but treats any pre-existing file as a completed download (see §5), and resize clears an active set device scale factor |
— |
tab-list, tab-new, tab-select, tab-close, window new |
Tab & window management | Multi-tab workflows, session-scoped tab operations | tab-management.md |
frames, frame <target>, frame main |
Iframe frame switching | Interact with content inside <iframe>s: frame "#pay-frame" then fill/click/is visible resolve inside that frame; frames lists the frame tree |
frames.md |
diff snapshot |
Unified diff between two saved accessibility snapshots | Verify what changed between interactions (snapshot --auto-diff equivalent on saved files) |
— |
download, wait --download |
Download management | download --dir <path> configures the browser download folder; wait --download blocks until a download completes |
— |
network requests, network request <id>, network har start, network har stop, network route, network routes, network unroute |
Network request inspection, HAR recording & request routing | Inspect what the page loaded (XHR/fetch/status/headers), debug API calls, record a .har file (Chrome DevTools importable), or mock/abort matching requests (Fetch interception). network requests --filter api --status 2xx; network har start --content text then network har stop ./capture.har; network route "**/api/users" --body '{"users":[]}'; network routes lists the active routes (read-only) |
network.md |
profiler start, profiler stop |
V8 CPU profiling via CDP | Profile page interactions and save .cpuprofile (Chrome DevTools / speedscope compatible) |
— |
profiles list |
List browser profile directories | See what profiles exist under ~/.browser4/browser/chrome before open --profile |
— |
profile-import |
Import bookmarks/history/passwords/cookies/extensions from system Chrome/Edge/Safari (requires the browser4-profile-import plugin) | profile-import --list-sources to discover browsers; profile-import --source chrome --data bookmarks,cookies copies a whole profile snapshot to ~/.browser4/imports/; `--into prototype |
defaultseeds a managed profile dir; thenopen --profile ` mounts it |
scrape --formats <list> |
One page capture → many outputs in one document (markdown, html, rawHtml, links, images, attributes, deterministicJson, readability, screenshot, pdf) |
Several outputs from one page: every field comes from a single capture, so they cannot disagree about what the page said, and three outputs cost one page load instead of three. metadata.formatsRequested / formatsDelivered say what was asked for and what came back. screenshot and pdf are the two formats that need the live tab and the only ones that write a file: the path comes back in screenshot / pdf (on the backend's machine, in its temp tree), and "base64": true also returns the bytes as screenshotBase64 / pdfBase64. To get the file onto your machine, pass --output <dir> (or -o): the CLI fetches each one and prints the document with the local paths. From another host, fetch by name from GET /api/scrape/media/{name}. Add --strict when a missing format should stop the job instead of being named in warning (503 unavailable here / 502-504 ran and failed / 400 not honourable). Quote a comma list — an unquoted markdown,links is three arguments in PowerShell and only the first reaches --formats |
scrape-formats.md |
scrape formats |
List every accepted format id, whether this build can deliver it, and why not | Check before asking, instead of decoding a warning afterwards |
scrape-formats.md |
config |
Persistent CLI defaults (server, timeout, proxy, session, extension_id) | Set default server URL, timeout, proxy, session name, or the id of a locally loaded extension | config.md |
status, doctor, doctor log, doctor metrics, doctor status |
Server health & diagnostics | doctor status prints the aggregated status report (health, build, runtime, LLM, sessions, browsers, swarm, plugins, skills, metrics, logs) in layers: summary by default, --verbose for full detail, --section <name> for one report, --json for machine-readable output. status prints the web status panel URL (http://<server>:18182/status) — a live dashboard of the same reports; http://<server>:18182/pages.html shows every open page |
— |
batch |
Run several commands in one invocation | Scripted multi-step flows, fewer round-trips | quickstart.md |
tool call <mcp-name> [--json '{...}'] |
Generic MCP tool passthrough | Invoke any MCP tool that has no dedicated CLI command — most notably plugin-provided agent tools. Tool names are snake_case: captcha.detect → tool call captcha_detect; args as JSON: tool call captcha_solve --json '{"type": "RECAPTCHA_V2", "siteKey": "..."}'. Enumerate tool names with tool list (or GET /mcp/tools) — plugin list lists installed plugin JARs, not tools — and plugin <domain> <method> --help documents one plugin tool; see plugins.md for which plugins a dev bundle ships and how to install the rest |
— |
console, cdp, pdf, page-info, go-back, go-forward, keydown, keyup, mousedown, mouseup, mousewheel, snapshot list, snapshot clean, crawl status|result|cancel|resume|clear|list, swarm submit|status|result|list|close, chat, session-default, delete-data, kill-all, stop, uninstall, plugin list|info|install|remove, plugin <domain> <method>, plugin-* |
Remaining command families (not covered here) | Discover with browser4-cli help / browser4-cli help <command>; plugin management, bundled-vs-installable plugin JARs and the install → restart workflow: plugins.md |
— |
experience save, experience query, experience list, experience deep learn |
Progressive experience memory | Reuse selectors, extraction patterns and blocker awareness across sessions — see the sibling skill | browser4-experience |
Refreshing This Skill
The skills command retrieves bundled skill content that always matches the installed CLI version. Use it to get current instructions rather than relying on cached copies:
browser4-cli skills # List all bundled skills
browser4-cli skills get browser4-cli # Print this SKILL.md
browser4-cli skills get browser4-cli --full # Include all reference files
browser4-cli skills path # Print skills directory path
browser4-cli skills unpack # Unpack bundled skill files to disk
Skill files are unpacked automatically during browser4-cli install (and refreshed by upgrade);
unchanged files are skipped, so re-running is cheap. install / upgrade also copy the bundled
skills into ~/.agents/skills so agents (e.g. Codex) load them automatically. Overrides:
BROWSER4_SKILLS_DIR, BROWSER4_AGENTS_SKILLS_DIR.
4. Decision Trees
What kind of work is this? Pick the branch first — it names the command family; the expanded trees, the comparisons behind them and the X-SQL quickstart template live in decision-trees.md.
What do you need to do?
|- act on a page (click, fill, press, upload) ..... snapshot -> act on refs -> re-snapshot
|- get snapshot metadata for the live page ....... htmlsnapshot
|- read content off the live page ................. htmlsnapshot get / get all / query (each captures the tab first, then reads)
|- compute something in page JS ................... eval (--ref takes an arrow function)
|- understand a page, or find selectors ........... htmlsnapshot inspect | summary
|- fetch many known or linked pages ............... crawl (--seed-file, --depth N)
|- the same task across many URLs, in parallel .... swarm
|- repeat on a schedule ........................... loop
`- structure pages you already have ............... webminer all (raise --max-files; default 40)
- 4a. Extraction method: interact →
snapshot+ refs; read content →htmlsnapshot get/query; snapshot metadata →htmlsnapshot(capture); live DOM →eval --json; natural language →extract; many pages →crawl/swarm. Everyhtmlsnapshotcommand captures the active page first and then works on that snapshot, soget/get all/inspect/summary/grep/export/query/readabilityalready see what the tab shows right now (form results, SPA updates,evalmutations, login state) — no capture needed first.--expires <dur>(default0s) is the one exception you can ask for: a positive value serves the stored snapshot of the active page while it is younger than the window and leaves the tab untouched, i.e. it reads the previous snapshot version. The capture is always keyed by the active tab's own URL, so a command aimed at another URL (readability <url>,query --url <url>) reads that URL's stored copy instead and never files the tab's document under it (--expiresdoes not govern that path — it is already store-only). An empty read means the selector did not match the page as it is now (or no page is loaded) — per-command matrix in decision-trees.md. - 4b. Bulk/scale: one list page →
query; known URLs →crawl --seed-file; follow links →crawl <url> --depth N; more crawl overlap →crawl --parallel 8(each unit collects on its own tab); parallel →swarm; scheduled →loop. An interrupted crawl (backend restart, crash,--timeoutcut it off, or you cancelled it) keeps its id →crawl list --status interrupted, thencrawl resume <task-id>: already-fetched URLs are not requested again andcrawl resultreturns the union of both runs (add--retry-failedto re-fetch terminal failures; automatic resume at startup is off by default). - 4c. Query granularity:
get= first match;get all= all matches (unaligned arrays — don't combine);query= correlated multi-field rows. - 4d. Structuring pages (WebMiner):
< 1,000 pages→webminer all(free, local, zero tokens);> 1,000 pages→ WebMiner Commercial (Spark). Acquire pages first withcrawl/swarm, then feed the HTML directory in.webminer alltruncates the corpus at--max-files, default 40 — a 900-page corpus silently encodes/clusters its first 40 files and still ends withPIPELINE COMPLETE, so raise the cap explicitly:webminer all <dir> --max-files 900. The interactive HTML report and the encoded dataset go under the OS temp dir (%TEMP%/webminer-pereg/…,$TMPDIR/webminer-pereg/…), not under--output(which receives the clustering results); the end-of-run summary prints both paths, andwebminer views <result-dir>rebuilds the views next to the results. See decision-trees.md. - 4e. X-SQL quickstart:
SELECT DOM_FIRST_TEXT(DOM,'h2') AS title ... FROM DOM_LOAD_AND_SELECT(@url, '.product-card')— single quotes for CSS,@urlunquoted; run via--sql @file.sql. A JOIN of twoDOM_LOAD_AND_SELECTsources executes (each source loads the page again, so joins are costly), while CTEs (WITH) and FROM-clause subqueries are not supported (a CTE fails as "Only select statements are supported"). Numeric-returning functions (DOM_FIRST_FLOAT,STR_FIRST_FLOAT) serialize as JSON strings unless youCAST(... AS DOUBLE).
5. Critical Warnings
Refs are single-use. Re-snapshot after any interaction, and always after
goto,reload, and tab switches. Never reuse a ref across navigations.
Selectors break when sites change their HTML. Discover them with
htmlsnapshot inspect/summarybefore extracting. Treat scenario examples as patterns, not copy-paste recipes.
Shell quoting on Windows. Complex JS/SQL with nested quotes breaks inline: prefer
--sql @file.sql,--sql-stdin,--sql-base64,eval --file/--stdin/--base64; forhtmlsnapshot inspectuse@file,--stdin, or--selector-base64. On PowerShell always quote@filepaths (--sql "@query.sql") — an unquoted@is the splatting operator. Never inline--sql "…"with double-quoted CSS selectors. See shell-quoting.md.
Don't cat snapshot files — they can exceed 256KB, and
--stdoutcan dump 63KB+ trees. Bound the capture with-v 0,--depth,--selector,--no-boxes, or usesnapshot grep/htmlsnapshot.snapshot --stdout,htmlsnapshot get htmlandgreppaginate at 2000 lines by default (--all/--page-size 0disables);get all …andtext/textcontentreads print in full.
eval --refrequires an arrow function:element => element.textContent. Writingelement.textContent(orthis.textContent) returnsnull— the #1 mistake with element-scoped eval.
evalruns in the page's own JavaScript world; JSundefinedprints as"". Page-defined globals resolve (eval "window.myApp.version"), and globals written byevalare visible to page scripts.--world isolatedevaluates in the driver's own world (__browser4_runtime__) instead — where the injected runtime lives and page scripts cannot see what you write, but page globals read asnull. DOM reads work in both worlds — drive the page's own elements withfill/clickfor page-visible state. Return values:nullprints asnull, but JSundefinedarrives as an empty string, soundefinedand''both print""(in--json, the string"") — return a sentinel when it matters:eval "typeof x === 'undefined' ? 'M' : x".
wait --load networkidleis not "the page is ready". It proves only that the network went quiet: a page whose results are computed by page JS after load (dashboards, bot-detection verdicts), or fetched by a long-running XHR, can still be empty when it reports success — and the CLI now says so. For result pages, poll the result element first (wait "<result-selector>", oreval --wait-selector <css>); treatwait --load networkidleas a network-settling extra step.
Sandboxed environments: the backend writes its logs inside the runtime bundle, so a workspace-only sandbox makes
open/gotohang until the startup timeout withAccess denied(see the startup log path under🧾 Details). Fix before the first launch: pointBROWSER4_RUNTIME_DIRandBROWSER4_CLI_STATE_DIRat writable directories.
getvalue contract: a matched element returns its value — or""when the attribute/property is absent;nullmeans the selector matched nothing; an unresolvableeNref fails with an explicit error.
Assertions and
wait— read the verdict, don't trust the exit code blindly.is visible|enabled|checked <sel>print the value (true/false, andnullfromis enabledon a missing or invalid selector); a false result still exits 0 unless you pass--fail-on-false, which--helpdeclares as "exit 1 when the assertion is false or null".wait <selector>,wait --text,wait --urlandwait --fnexit non-zero when the condition never became true (a satisfied wait prints the elapsed time and the stderr note names the timed-out target); older builds printed success even for a timed-out wait. If a build does not honour--fail-on-false, read the value instead:browser4-cli --json is visible "#result" | jq -r '.output.result', or cross-check live witheval "!!document.querySelector('#result')".
wait --downloadsucceeds as soon as the watched directory holds any non-partial file while no.crdownload/.tmp/.partfile is present — a pre-existing file returns "Download complete" instantly (it lists the whole directory, not what the download produced), so start clean withrm -rf ./downloads && mkdir -p ./downloadsor delete the old files first.Known deviations:
wait --downloadshould count only files that appear after the call starts, and accept a target filename.
Snapshot modes —
-v 0vs-ivs default:
Mode What it shows Best for snapshot(default)Full AX tree with all element refs General exploration, first look at a page snapshot -v 0Current visible screen (one screen-height viewport chunk; -v all= whole page)Long pages — read one chunk at a time snapshot -iInteractive-only rendering — buttons, links, inputs and pointer ( cursor:pointer) elements, inner text merged into their names so each surviving ref line is a self-contained target.-i -v <n>narrows it to one viewport chunk (-i -v 0= this screen's interactive elements)Form-heavy pages, a quick read of the actionable elements htmlsnapshotStatic HTML (CSS selectors) Content extraction when you need CSS selectors, not AX refs
-iis a strict filter and drops refs you may need: draggable items (li[draggable=true],cursor:grab) and hover-only targets (tooltip spans,cursor:help) keep their text as merged lines but lose their refs, and with-vthey vanish from the output entirely. Act on those with refs from the default snapshot or-v 0instead:snapshot -i -v 0 --stdoutlists this screen's interactive elements only, whilesnapshot -v 0 --stdoutgives the full tree for that screen — draggable and hover-revealed targets included.Extending the heuristic to draggable/hover-revealed elements is expected; until it lands, don't use
-ias the only orientation view before acting.snapshot --helpdocuments the same strict filter, but the size tip printed for very large snapshots (and the optional--show-tiptip) may still carry the older wording "not a strict filter".
6. Quick Patterns & Deep Dives
Copy-paste pairs for the common flows — form fill, snapshot grep, mouse/drag, dialog handling,
verify-after-interaction, single-field and bulk extraction, PowerCSS, agent tasks:
- quick-patterns.md — the recipe catalog.
- quickstart.md — the compressed resident quick reference.
- decision-trees.md — extraction decision trees, expanded.
- htmlsnapshot-scenarios.md — end-to-end walkthroughs; focused variants: advanced, amazon, audit, extraction.
Proven copy-paste recipes — full walkthroughs in quick-patterns.md:
- Multi-Session Workflow —
-s <name>isolates state;list/close/close-allmanage sessions - Interactive Form Fill — open → snapshot → fill refs → submit →
wait --load networkidle→ verify - Find Elements by Text —
snapshot grep [-i] [-A 3 -B 1] "pattern" - Mouse Interactions —
hover,dblclick,drag; verify withsnapshot grep - Dialog Handling —
dialog-accept/dialog-dismissin a separate invocation (orclick --auto-dismiss-dialogs <ref>) - Verifying Results —
snapshot -v 0 --auto-diff --stdoutafter every interaction;generate-locatorfor resilient selectors - Static Data Extraction —
get text/get attr "<css>"straight off the live page: everyhtmlsnapshotread captures the active tab itself, so nothing has to be captured first - Bulk Extraction (X-SQL) — correlated fields via
--sql @query.sqlwithDOM_LOAD_AND_SELECT(@url, '.product-card') - PowerCSS —
:expr()visual-feature selectors; full reference in power-dom.md - Agent Task Lifecycle —
agent run(async) →status→result; or--wait [--wait-timeout] - Agent Memory — run-start
## Memoryrecall,memory_note,memory_search/read/forget, auto-deposit - Typing text (
type) —type "text" <ref>; add--method auto|chars|exec(requires a target ref):auto(default) types short text per-character and switches to a one-shotexecCommand('insertText')bulk insert for long (>150 chars) or multi-line text on textarea/contenteditable;charsforces per-character typing;execforces the bulk insert.--verifykeeps its strict read-back semantics for tool callers. - File Upload —
upload <ref> <file> [file...]uploads one or more local files to a page file input (<input type="file">only); the paths must be readable by the browser process (remote backend: resolved on the backend host). Multi-file, absolute paths,--no-snapshotsupported; see upload.md.
Resilient selectors (PowerCSS): div:expr(width > 400 && height > 400) filters by computed geometry/position/content density instead of class names, so it survives markup changes — usable in any CSS selector and in htmlsnapshot get / X-SQL selectors. Operators: power-dom.md; refs → selectors: css-selector-bridge.md.
AI-powered extraction: agent run "<task>" submits an async task — poll agent status <id> and read agent result <id>. Both answer the shared task envelope ({taskId, status, statusCode, message, error, …}, vocabulary queued|running|done|failed|cancelled), so status: "done" is the completion check (terminal: done/failed/cancelled), and the payload carries the agent's keyFindings, filesChanged and the workspace its deliverables live in. agent list shows the same knowledge, refreshed from the backend on every call; extract / summarize are the synchronous variants. See agent.md.
7. Reference Map
Organized by task — follow the link that matches what you're trying to do:
Start here (distilled core): quickstart.md — distilled resident quick reference (core loop, copy-paste template, key commands, snapshot vs htmlsnapshot, critical warnings); embedded in the CLI engine's system prompt — full details live in this SKILL.md.
Interact with pages (accessibility tree & element refs): snapshot.md — snapshot, snapshot grep, -v viewport paging, --auto-diff, -i interactive-only mode (drops draggable/hover-only refs), element refs
tabs.md — tab command reference: GUID forms and prefixes, --json envelope, tab insert position, last-tab rule, extension-session quirks
upload.md — upload local files to a page <input type="file"> (multi-file, --no-snapshot, browser-host path rules)
Extract data from pages:
htmlsnapshot.md — get, get all, query, grep, summary (incl. --algorithm), algorithms, inspect, export
eval.md — eval, eval --ref: live-DOM JavaScript evaluation (inline/--file/--stdin/--base64, --await, --wait-selector, --json, arrow-function rule)
x-sql.md — X-SQL function reference (DOM, STR, ARRAY namespaces); sub-references: x-sql-dom-functions.md, x-sql-dom-load-select.md, x-sql-dom-select-functions.md, x-sql-string-functions.md, x-sql-array-functions.md
htmlsnapshot-scenarios.md — end-to-end recipes; focused variants: advanced, amazon, audit, extraction
decision-trees.md — choosing extraction method, bulk/scale approach, query granularity, WebMiner, X-SQL quickstart
scrape-formats.md — scrape / scrape formats: one capture, many formats, one document; which formats this build has, the quoting trap, and the degradation contract
quick-patterns.md — proven interact/verify/extract copy-paste recipes
Run at scale (multiple pages/URLs): crawl.md — recursive crawling, seed-file bulk fetch, X-SQL extraction swarm.md — parallel scraping across multiple browser contexts loop.md — repeated task execution with persistence/resume
Manage browser state: storage-state.md — cookies, localStorage, sessionStorage, state save/load browser-state-import.md — copy system browser state into Browser4-managed sessions webdb.md — export cached pages, normalize URLs for database lookups attach.md — connect to existing Chrome/Edge via CDP tab-management.md — multi-tab workflows: quick start, recipes (GUID targeting, cross-session, windows), flags, error recovery
Choose how the browser runs:
browser-modes.md — session (default / named / swarm) × display (headless / headed / SUPERVISED) × browser source (managed / attach --cdp / attach --extension), plus the headless→headed escalation when a site blocks the bot, profile mode, interact level, contexts, and their failure modes
Manage skills & configuration:
skills.md — bundled skill files, backend skill management
config.md — config command family: CLI defaults and server-side runtime overrides; what a dev bundle ships, plugin install/opt-in enablement: plugins.md
development-mode.md — one backend per checkout: development ports from 8282, per-checkout state and app data, workspace-scoped stop
AI-powered extraction: agent.md — extract, summarize, agent run|status|result, LLM provider config
Resilient selectors:
power-dom.md — PowerCSS :expr() visual-feature selectors
css-selector-bridge.md — bridging snapshot refs to CSS selectors
Configure fetching: load-options-guide.md — cache control, quality requirements, interaction, portal crawling load-options-decision.md — choosing LoadOptions (decision tree)
Troubleshoot: shell-quoting.md — avoid shell-quoting breakage for complex JS/X-SQL on Windows / Git Bash
8. Installation
npm install -g browser4-cli && browser4-cli install # Node.js available
irm https://browser4.oss-cn-beijing.aliyuncs.com/scripts/install-browser4-cli.ps1 | iex # Windows
curl -fsSL https://browser4.oss-cn-beijing.aliyuncs.com/scripts/install-browser4-cli.sh | bash # Linux/macOS
The bootstrap scripts also install the Browser4 backend (runtime bundle) automatically:
browser4-cli install on a fresh machine, or browser4-cli upgrade when a backend already exists.
Those two subcommands accept only --tag and --force; to install the CLI only, pass
--skip-backend (-SkipBackend in PowerShell) to the install script — the CLI forwards that flag
to the script internally.
