[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M70D9D9K64JRDTNEW7K9QCSB":3,"skill-same:01M70D9D9K64JRDTNEW7K9QCSB":89},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":16,"sourceHtmlUrl":17,"categories":18,"keywords":35,"publishedAt":56,"linkOnly":57,"markdownContent":58,"markdownBody":59,"frontMatter":60,"refName":62,"commitSha":63,"skillDirectory":64,"forksCount":65,"repositoryHtmlUrl":66,"repositoryHomepageUrl":6,"ownerHtmlUrl":67,"ownerBlogUrl":68,"ownerDescription":69,"updatedAt":70,"install":71,"resources":84},"01M70D9D9K64JRDTNEW7K9QCSB","opencli-browser",null,"Use when an agent needs to drive a real Chrome window via opencli — inspect a page, fill forms, click through logged-in flows, or extract data ad-hoc. Covers the selector-first target contract, compound form fields, stale-ref handling, network capture, and the agent-native envelopes the CLI returns. Not for writing adapters — see opencli-adapter-author for that.","EthanYoQ\u002FSkill-hub","EthanYoQ","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F257484394?v=4","01M70C9QKWR1YS0S7V26DNB18H","Ethan","01M70C9QMER047R5ANZZA6KC2P",11,"Apache-2.0","https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Farchives\u002Fuser-installed-skills\u002Fopencli-browser\u002F3aa28f5282d5ab1b\u002Fopencli-browser\u002FLICENSE","https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Farchives\u002Fuser-installed-skills\u002Fopencli-browser\u002F3aa28f5282d5ab1b\u002Fopencli-browser\u002FSKILL.md",[19,24,27,32],{"categoryNo":20,"categoryName":21,"taxonomyType":22,"taxonomyTypeDesc":23},"01JBSEEDCAT000000000000018","写作与文案","SCENARIO","使用场景",{"categoryNo":25,"categoryName":26,"taxonomyType":22,"taxonomyTypeDesc":23},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":28,"categoryName":29,"taxonomyType":30,"taxonomyTypeDesc":31},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",{"categoryNo":33,"categoryName":34,"taxonomyType":30,"taxonomyTypeDesc":31},"01JBSEEDCAT000000000000037","律师与法务",[36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55],"browser","opencli","opencli browser","Bash","when","flows","click","drive","Adapters","ad","author","use","for","form","forms","edit","write","read","writing","inspect","2026-11-10 10:12:30",false,"---\nname: opencli-browser\ndescription: Use when an agent needs to drive a real Chrome window via opencli — inspect a page, fill forms, click through logged-in flows, or extract data ad-hoc. Covers the selector-first target contract, compound form fields, stale-ref handling, network capture, and the agent-native envelopes the CLI returns. Not for writing adapters — see opencli-adapter-author for that.\nallowed-tools: Bash(opencli:*), Read, Edit, Write\n---\n\n# opencli-browser\n\nThe first reader of this CLI is an agent, not a human. Every subcommand returns a structured envelope that tells you exactly what matched, how confident the match is, and what to do if it didn't. Lean on those envelopes — do not guess.\n\nThis skill is for **driving a live browser** to accomplish an agent task. If you are building a reusable adapter under `~\u002F.opencli\u002Fclis\u002F\u003Csite>\u002F` use `opencli-adapter-author` instead.\n\n---\n\n## Prerequisites\n\n```bash\nopencli doctor\n```\n\nUntil `doctor` is green, nothing else will work. Typical failures: Chrome not running, extension not installed, debug port blocked by 1Password \u002F other extensions. The doctor output tells you which.\n\n---\n\n## Session lifecycle\n\n- `opencli browser *` commands require a `\u003Csession>` positional immediately after `browser`. Use the same session name for a multi-step flow; use a different name to isolate parallel browser work.\n- Use a stable session name for any multi-command or human-paced browser workflow. Example: `opencli browser fb-yaya-warmup open https:\u002F\u002Fexample.com`, then reuse `opencli browser fb-yaya-warmup state`, `extract`, `click`, etc.\n- Owned browser sessions keep a tab lease alive between calls. Release it with `opencli browser \u003Csession> close` or let the idle timeout expire.\n- `opencli browser \u003Csession> bind` binds the Chrome tab you already have open to that session. Use this for logged-in pages, SSO flows, or pages you manually positioned before handing control to the agent.\n- `--window foreground|background` (or `OPENCLI_WINDOW=foreground|background`) chooses whether OpenCLI creates\u002Ffocuses a foreground browser window or uses a background browser window for owned sessions.\n\n### Bind Tab\n\n```bash\nopencli browser gmail bind\nopencli browser gmail state\nopencli browser gmail click \"Search\"\nopencli browser gmail network\nopencli browser gmail unbind\n```\n\nBinding never owns the user window and never closes the user tab. It fails closed if the tab is closed or becomes non-debuggable. Re-run `opencli browser \u003Csession> bind` when you switch to a different real tab.\n\nNavigation is allowed on bound sessions because the session now represents explicit agent ownership of that tab. Tab mutation (`tab new`, `tab select`, `tab close`) is still blocked for bound sessions. Use an owned session when you want OpenCLI to manage tab lifecycle.\n\nBound sessions have no OpenCLI idle-close timer; the binding lasts until `unbind`, tab close, window close, or daemon restart.\n\n---\n\n## Mental model\n\n1. **Selector-first target contract.** Every interaction command (`click`, `type`, `select`, `get text\u002Fvalue\u002Fattributes`) takes one `\u003Ctarget>`, which is *either* a numeric ref from `state`\u002F`find` *or* a CSS selector. Use `--nth \u003Cn>` to disambiguate multiple CSS matches.\n2. **Every envelope reports `matches_n` and `match_level`.** `match_level` is `exact`, `stable`, or `reidentified` — the CLI already rescued moderate DOM drift for you, but the level tells you how confident to be.\n3. **Compact output first, full payload on demand.** `state` is a budget-aware snapshot; `get html --as json` supports `--depth\u002F--children-max\u002F--text-max`; `network` returns shape previews and you re-fetch a single body with `--detail \u003Ckey>`. If you emit a giant payload you are burning context you did not need to burn.\n4. **Structured errors are machine-readable.** On failure the CLI emits `{error: {code, message, hint?, candidates?}}`. Branch on `code`, not on message strings.\n\n---\n\n## Critical rules\n\n1. **Always inspect before you act.** Run `state` or `find` first. Never hard-code a ref or selector from memory across sessions — indices are per-snapshot.\n2. **Prefer site adapters before raw browser driving.** If `opencli \u003Csite> \u003Ccommand>` already covers the task, use that adapter command first (`opencli facebook notifications`, `opencli reddit read`, `opencli chatgpt model \u003Clevel>`, etc.). Use `opencli browser ...` only for gaps, debugging, or one-off UI flows the adapter does not expose.\n3. **Prefer numeric ref over CSS once you have it.** Numeric refs survive mild DOM shifts because the CLI fingerprints each tagged element. A CSS selector written by hand will break the first time the site re-renders.\n4. **Read `match_level` after every write.** `exact` = all good. `stable` = the element is the same but some soft attrs drifted — your action still applied. `reidentified` = the original ref was gone and the CLI found a unique replacement; double-check you hit the right element.\n5. **Use the `compound` field for form controls.** Do not regex-guess a date format, do not `state` twice to get the full `\u003Cselect>` options list. The compound envelope has the format string, full option list up to 50, `options_total` for overflow, and `accept`\u002F`multiple` for `\u003Cinput type=file>`.\n6. **Verify writes that matter.** After `type \u003Ctarget> \u003Ctext>`, run `get value \u003Ctarget>`. After `select`, run `get value`. Autocomplete widgets, React controlled inputs, and masked fields all silently eat characters. The CLI cannot detect this for you.\n7. **`state` → action → `state` after a page change.** Navigations, form submits, and SPA route changes invalidate refs. Take a fresh snapshot. Do not reuse refs from before the transition.\n8. **Chain with `&&` when reusing freshly parsed refs.** A chained sequence runs in one shell so the ref you just read from output can be passed directly to the next command. Separate shell invocations keep the named browser session, but any shell-local variables or copied refs from the previous command can go stale after page changes.\n9. **`eval` is read-only.** Wrap the JS in an IIFE and return JSON. If you need to *change* the page, use the structured `click` \u002F `type` \u002F `select` \u002F `keys` commands instead — they produce structured output and fingerprints, `eval` does not.\n10. **Prefer `network` to screen-scraping.** If a page you care about fetches its data from a JSON API, the API is almost always more reliable than scraping the rendered DOM. Capture once, inspect the shape, then `--detail \u003Ckey>` the body you need.\n\n---\n\n## Sitemaps\n\nIf `browser open` or `browser analyze` returns `sitemap.available: true`, switch to `opencli-browser-sitemap` before continuing a multi-step site flow. The sitemap is prior context for pages, actions, workflows, APIs, and pitfalls; it is not truth. If the browser state disagrees with the sitemap, trust the browser and mark the sitemap stale via `opencli-sitemap-author`.\n\n---\n\n## Target contract (`\u003Ctarget>` for click \u002F type \u002F select \u002F get text|value|attributes)\n\n```\n\u003Ctarget> ::= \u003Cnumeric-ref> | \u003Ccss-selector>\n```\n\n- **Numeric ref** — the `[N]` index from `state` or `find`. Cheap, resilient to soft DOM drift.\n- **CSS selector** — anything `querySelectorAll` accepts. Must be unambiguous on write ops, or pair with `--nth \u003Cn>`.\n\n### Envelope on success\n\n```json\n{ \"clicked\": true, \"target\": \"3\", \"matches_n\": 1, \"match_level\": \"exact\" }\n```\n\n```json\n{ \"value\": \"kalevin@example.com\", \"matches_n\": 1, \"match_level\": \"stable\" }\n```\n\n### match_level\n\n| level | meaning | you should |\n|-------|---------|------------|\n| `exact` | Fingerprint agreed on tag + strong IDs with at most one soft drift | Proceed. |\n| `stable` | Tag + strong IDs still agree, soft signals (aria-label, role, text) drifted | Proceed, but if *what* you typed\u002Fclicked matters, re-check with `get value` or `state`. |\n| `reidentified` | Original ref was gone; a unique live element matched the fingerprint and was re-tagged with the old ref | Double-check you hit the right element before chaining more writes. |\n\n### Structured error codes\n\nBranch on these, not on the human message:\n\n| code | meaning |\n|------|---------|\n| `not_found` | Numeric ref is no longer in the DOM. Re-`state`. |\n| `stale_ref` | Ref exists but the element at that ref changed identity. Re-`state`. |\n| `invalid_selector` | CSS was rejected by `querySelectorAll`. Fix the selector. |\n| `selector_not_found` | CSS matches 0 elements. Try `find` with a looser selector. |\n| `selector_ambiguous` | CSS matches >1 and no `--nth`. Add `--nth` or narrow the selector. |\n| `selector_nth_out_of_range` | `--nth` beyond match count. |\n| `option_not_found` | `select` couldn't find an option matching that label\u002Fvalue. Error envelope includes `available: string[]` of the real option labels. |\n| `not_a_select` | `select` was called on a non-`\u003Cselect>` element. |\n\nError envelope always includes `error.code` and `error.message`. Target errors (`selector_not_found`, `selector_ambiguous`, etc.) often add `error.candidates: string[]` with suggested selectors. `option_not_found` adds `error.available: string[]` instead.\n\n---\n\n## Command reference\n\n### Inspect\n\n| command | purpose |\n|---------|---------|\n| `browser state` | Snapshot: text tree with `[N]` refs, scroll hints, hidden-interactive hints, `compounds (N):` sidecar for date\u002Fselect\u002Ffile refs. |\n| `browser state --source ax` | Opt-in accessibility-tree snapshot. Use when custom controls, portals, or iframe contents are hard to identify in normal `state`. AX refs can recover stale React re-renders by role\u002Fname\u002Fnth and can route same-origin iframe refs. Cross-origin iframe refs are best-effort because Chrome may not expose attachable OOPIF targets to extensions. |\n| `browser state --compare-sources` | Metrics-only DOM vs AX comparison for deciding whether AX should become default. It prints counts and sizes, not page text, so it is safer to share for validation. |\n| `browser find --css \u003Csel> [--limit N] [--text-max N]` | Run a CSS query and return one entry per match with `{nth, ref, tag, role, text, attrs, visible, compound?}`. Allocates refs for matches the prior snapshot didn't tag. Cheap alternative to `state` when you already know the selector. |\n| `browser find --role button --name Save` | Semantic locator query. Also supports `--label`, `--text`, and `--testid`. Use before raw CSS when a control has accessible labels. |\n| `browser frames` | List cross-origin iframe targets. Pass the index to `--frame` on `eval`. |\n| `browser screenshot [path]` | Viewport PNG. No path → base64 to stdout. Prefer `state` when you just need structure. |\n| `browser screenshot --annotate [path]` | Visual ref map. Refreshes DOM refs and overlays visible `[N]` labels so the screenshot maps back to `browser click \u003Cref>` targets. Use for icon-only controls, visual layouts, charts, or when text state is ambiguous. |\n\n### Get (read-only)\n\n| command | returns |\n|---------|---------|\n| `browser get title` | plain text |\n| `browser get url` | plain text |\n| `browser get text \u003Ctarget> [--nth N]` | `{value, matches_n, match_level}` |\n| `browser get value \u003Ctarget> [--nth N]` | `{value, matches_n, match_level}` |\n| `browser get attributes \u003Ctarget> [--nth N]` | `{value: {attr: val, ...}, matches_n, match_level}` |\n| `browser get text --role option --name Travel` | Semantic locator read without a prior `state` call. Same flags as `browser find`. |\n| `browser get html [--selector \u003Ccss>] [--as html\\|json] [--depth N] [--children-max N] [--text-max N] [--max N]` | Raw HTML, or structured tree. JSON tree nodes have `{tag, attrs, text, children[], compound?}`. Truncation reported via `truncated: {depth?, children_dropped?, text_truncated?}`. |\n\n### Interact\n\n| command | notes |\n|---------|-------|\n| `browser click \u003Ctarget> [--nth N]` | Returns `{clicked, target, matches_n, match_level}`. |\n| `browser click --role button --name Submit` | Semantic click. Write actions require a unique match; ambiguous locators return candidates instead of clicking the first match. |\n| `browser hover [target] [--role R --name N] [--nth N]` | Moves the mouse over an element. Use for hover menus\u002Ftooltips before taking `state` or clicking submenu items. Returns `{hovered, target, matches_n, match_level}`. |\n| `browser focus [target] [--role R --name N] [--nth N]` | Focuses an element without typing. Useful before `keys` or when a page reacts to focus\u002Fblur. Returns `{focused, target, matches_n, match_level}`. |\n| `browser dblclick [target] [--role R --name N] [--nth N]` | Double-clicks an element via native mouse events when available. Returns `{dblclicked, target, matches_n, match_level}`. |\n| `browser check [target] [--role R --name N] [--nth N]` | Ensures checkbox\u002Fradio\u002Faria-checked control is checked. Returns `{checked, changed, target, matches_n, match_level, kind}`. Prefer this over blind `click` when target state matters. |\n| `browser uncheck [target] [--role R --name N] [--nth N]` | Ensures checkbox\u002Faria-checked control is unchecked. Radio buttons cannot be unchecked directly; select another radio in the group instead. |\n| `browser upload [target] \u003Cfile...> [--role R --name N] [--nth N]` | Attaches local file path(s) to an `input[type=file]` via CDP. With semantic flags, omit `target` and pass files as positionals. Returns `{uploaded, files, file_names, target, matches_n, match_level, multiple?, accept?}`. |\n| `browser drag [source] [target] [--from-role R --from-name N] [--to-role R --to-name N] [--from-nth N] [--to-nth N]` | Mouse-based drag from one resolved element center to another. Works for mouse-listener drag libraries; native HTML5 `dataTransfer` drops may need a site-specific fallback. Returns `{dragged, source, target, source_matches_n, target_matches_n, ...}`. |\n| `browser type [target] \u003Ctext> [--role R --name N] [--nth N]` | Clicks first, then types. With semantic flags, omit `target` and pass text as the only positional. Returns `{typed, text, target, matches_n, match_level, autocomplete}`. `autocomplete: true` means a combobox\u002Fdatalist popup appeared after typing — you almost always need `keys Enter` or a follow-up `click` on the suggestion to commit the value. |\n| `browser fill [target] \u003Ctext> [--role R --name N] [--nth N]` | Exact replacement for input, textarea, and contenteditable targets. With semantic flags, omit `target` and pass text as the only positional. Returns `{filled, verified, text, actual, matches_n, match_level}`. Use this when you need raw text set and verified, not keyboard\u002Fautocomplete behavior. Pipeline form supports `{ fill: { ref, text, submit: true } }`. |\n| `browser select [target] \u003Coption> [--role R --name N] [--nth N]` | Matches native `\u003Cselect>` option by label first, then value. With semantic flags, omit `target` and pass option as the only positional. Use `compound` from `find`\u002F`state` to see exactly what labels are available. |\n| `browser keys \u003Ckey>` | `Enter`, `Escape`, `Tab`, `Control+a`, etc. Runs against the focused element. |\n| `browser scroll \u003Cdirection> [--amount px]` | `up` \u002F `down`. Default amount `500`. |\n\n### Wait\n\n```bash\nbrowser wait selector \"\u003Ccss>\" [--timeout ms]    # wait until the selector matches\nbrowser wait text \"\u003Csubstring>\" [--timeout ms]  # wait until the text appears\nbrowser wait download [pattern] [--timeout ms]  # wait for a Chrome download whose filename\u002FURL\u002Fmime contains pattern\nbrowser wait time \u003Cseconds>                     # hard sleep, last resort\n```\n\nDefault timeout `10000` ms. SPA routes, login redirects, and lazy-loaded lists need `wait` before `state`\u002F`get`.\n\n`browser wait download` requires Browser Bridge extension 1.0.8+ because it uses\nChrome's downloads lifecycle API. Pass a narrow filename or URL substring such\nas `receipt.pdf` when possible; an empty pattern waits for the next\u002Frecent\ndownload in the timeout window. The command reports `{downloaded, filename, url,\nstate, elapsedMs}` on success and a JSON error envelope on timeout\u002Ffailure.\n\n### Extract\n\n- **`web read --url \u003Curl>`** — One-shot Markdown reader for arbitrary pages. It expands relevant same-origin iframes by default, so old iframe-shell sites work better than with a top-document-only scrape. Use `--frames all-same-origin` when completeness matters more than Markdown noise. For AJAX shell pages use `opencli web read --url \u003Curl> --wait-for \"\u003Cselector>\" --wait-until networkidle --diagnose`; diagnostics show frame URLs, empty containers, and API-like XHRs. If the value you need is table\u002FAPI data, switch to `browser network` or a dedicated adapter instead of relying on Markdown.\n- **`browser eval \u003Cjs> [--frame N]`** — Run an expression in the page (or in a cross-origin frame via `--frame`). Wrap in an IIFE and return JSON. Read-only: no `document.forms[0].submit()`, no clicks, no navigations. If the result is a string, stdout is the raw string; otherwise it's JSON.\n- **`browser extract [--selector \u003Ccss>] [--chunk-size N] [--start N]`** — Markdown extraction of long-form content with a continuation cursor. Returns `{url, title, selector, total_chars, chunk_size, start, end, next_start_char, content}`. Loop on `next_start_char` until it is `null`. Auto-scopes to `\u003Cmain>`\u002F`\u003Carticle>`\u002F`\u003Cbody>` if you don't pass `--selector`.\n\n### Network\n\n```bash\nbrowser network                        # shape preview + cache key list\nbrowser network --detail \u003Ckey>         # full body for one cached entry\nbrowser network --filter \"field1,field2\"  # keep only entries whose body shape contains ALL fields as path segments\nbrowser network --all                  # include static resources (usually noise)\nbrowser network --raw                  # full bodies inline — large; use sparingly\nbrowser network --ttl \u003Cms>             # cache TTL (default 24h)\n```\n\nList entries look like `{key, method, status, url, ct, size, shape, body_truncated?}`. Detail envelope is `{key, url, method, status, ct, size, shape, body, body_truncated?, body_full_size?, body_truncation_reason}`. Cache lives in `~\u002F.opencli\u002Fcache\u002Fbrowser-network\u002F` so you can re-inspect without re-triggering the request.\n\nDefault output keeps JSON\u002FXML\u002Fplain-text and JS-like API responses, then drops obvious static assets and telemetry by URL. If an expected endpoint is missing, run `browser network --all` once and check whether an unusual content type or URL filter hid it.\n\n### Tabs & session\n\n| command | purpose |\n|---------|---------|\n| `browser tab list` | JSON array of `{index, page, url, title, active}`. The `page` string is the tab identity you pass as `\u003CtargetId>` to `tab select` \u002F `tab close`, or to `--tab \u003CtargetId>` on any subcommand. (`--tab`'s placeholder is historical — the value is always `page`.) |\n| `browser tab new [url]` | Open a new tab. Prints the new `page` string. |\n| `browser tab select [targetId]` | Make a tab the default. All subcommands accept `--tab \u003CtargetId>` to target one without changing the default. |\n| `browser tab close [targetId]` | Close by `page`. |\n| `browser back` | History back on the active tab. |\n| `browser close` | Release the current owned browser session when done. |\n| `browser \u003Csession> bind` | Bind the current Chrome tab to the named browser session. |\n| `browser \u003Csession> unbind` | Detach the named bound session without closing the user tab\u002Fwindow. |\n\n---\n\n## Compound form controls\n\nEvery date\u002Ftime, select, and file input carries a `compound` field. Use it — do not regex attributes.\n\n### Date family\n\n```json\n{\n  \"control\": \"date\",\n  \"format\": \"YYYY-MM-DD\",\n  \"current\": \"2026-04-21\",\n  \"min\": \"2026-01-01\",\n  \"max\": \"2026-12-31\"\n}\n```\n\n`control` is one of `date | time | datetime-local | month | week`. `format` is a concrete template string — type into the field using that exact format, or `select` by label if the site wraps the native input in a custom widget.\n\n### Select\n\n```json\n{\n  \"control\": \"select\",\n  \"multiple\": false,\n  \"current\": \"United States\",\n  \"options\": [\n    { \"label\": \"United States\", \"value\": \"us\", \"selected\": true },\n    { \"label\": \"Canada\", \"value\": \"ca\" }\n  ],\n  \"options_total\": 137\n}\n```\n\n`options[]` is capped at 50 entries. **`current` is always correct** even when the selected option is past the cap — it's computed by scanning every option, not from the truncated list. If `options_total > options.length` and you need an option that isn't in `options[]`, call `browser select \u003Ctarget> \"\u003Clabel>\"` directly — the CLI matches against the live DOM, not the truncated list.\n\n### File\n\n```json\n{\n  \"control\": \"file\",\n  \"multiple\": true,\n  \"current\": [\"report.pdf\", \"cover.png\"],\n  \"accept\": \"application\u002Fpdf,image\u002F*\"\n}\n```\n\nDo not invent file paths. Upload is done via the normal click flow — respect `accept` when telling the user what to upload.\n\n### Where compounds show up\n\n- `browser find --css \u003Csel>` entries: inline on each match.\n- `browser get html --as json` tree nodes: inline on matching nodes.\n- `browser state` snapshot: in a `compounds (N):` sidecar keyed by numeric ref, so you can tell at a glance which `[N]` entries have rich metadata.\n\n---\n\n## Cost guide\n\nThink about payload size per call. Budgets exist for a reason.\n\n| command | rough cost | when to use |\n|---------|-----------|-------------|\n| `state` | medium (bounded by internal budget) | First call on any page, after every nav, when you need refs. |\n| `find --css \u003Csel>` | small | You already know the selector — one query, compact entries. |\n| `get title` \u002F `get url` | tiny | Sanity checks between steps. |\n| `get text\u002Fvalue\u002Fattributes` | tiny per call | Verifying one specific field. |\n| `get html` (raw) | can be huge | Avoid on unbounded pages. Always pair with `--selector` and a budget. |\n| `get html --as json --depth 3 --children-max 20` | medium | When you need to reason about structure, not a specific field. |\n| `screenshot` | large | Only when the page is visual (CAPTCHA, charts). Prefer `state`. |\n| `extract` | medium per chunk | Long-form reading. Loop via `next_start_char`. |\n| `network` (default) | small | First look at APIs. |\n| `network --detail \u003Ckey>` | varies | Pull one body. |\n| `network --raw` | huge | Only after `--filter` narrowed the candidate set. |\n| `eval \"JSON.stringify(...)\"` | controlled | Targeted extraction when none of the above fit. |\n\nRule of thumb: **one `state` per page transition, one `find` per follow-up query, one `get`\u002F`click`\u002F`type` per action.** If your plan involves >10 calls per page you are probably scraping instead of interacting — consider `extract` or `network`.\n\n---\n\n## Chaining rules\n\n**Good — one shell, live session:**\n\n```bash\nopencli browser hn open \"https:\u002F\u002Fnews.ycombinator.com\" \\\n  && opencli browser hn state \\\n  && opencli browser hn click 3\n```\n\n**Bad — each line is a fresh shell, refs from call 1 are already forgotten when call 2 runs.** (Only a problem if you rely on shell-scoped state; browser refs themselves persist in-page, but interleaving unrelated shells invites races.) Prefer `&&` when the steps are meant to be atomic.\n\n**Never** chain a write and then an immediate `state` without a `wait` if the action causes a network round-trip — you will snapshot the pre-response DOM and make bad decisions off stale data.\n\n---\n\n## Recipes\n\n### Fill a login form\n\n```bash\nopencli browser login open \"https:\u002F\u002Fexample.com\u002Flogin\"\nopencli browser login state                          # find [N] for email, password, submit\nopencli browser login type 4 \"me@example.com\"\nopencli browser login type 5 \"hunter2\"\nopencli browser login get value 4                    # verify (autocomplete can eat chars)\nopencli browser login click 6                        # submit\nopencli browser login wait selector \"[data-testid=account-menu]\" --timeout 15000\nopencli browser login state                          # fresh refs on the logged-in page\n```\n\n### Pick from a long dropdown\n\n```bash\nopencli browser form state                          # sidebar shows [12] \u003Cselect name=country>\nopencli browser form find --css \"select[name=country]\"\n# the compound.options_total is 137, but compound.current is \"\" — unselected.\nopencli browser form select 12 \"Uruguay\"\nopencli browser form get value 12                   # { value: \"uy\", match_level: \"exact\" }\n```\n\n### Pick from a custom React dropdown\n\nUse this for Radix, shadcn, Material UI, Mercury-style category fields, and\nother controls that are not native `\u003Cselect>`.\n\n```bash\nopencli browser mercury state                          # find category trigger ref\n# If the trigger\u002Foption is not clear, use AX:\nopencli browser mercury state --source ax              # look for combobox\u002Fbutton\u002Flistbox\u002Foption names\nopencli browser mercury click 7                        # click category trigger\nopencli browser mercury state --source ax              # fresh refs after the portal\u002Flistbox opens\nopencli browser mercury click 12                       # click option\nopencli browser mercury get text 7                     # verify visible selected label\n```\n\nDo not use `browser select` on these widgets. `browser select` is only for\nnative `\u003Cselect>` elements. Custom dropdowns should be driven with\n`state -> click trigger -> state -> click option -> verify`.\n\n### Compare DOM vs AX observation\n\nWhen deciding whether AX refs are better for a page, collect metrics without\nsharing page contents:\n\n```bash\nopencli browser compare state --compare-sources\n```\n\nReport `sources.dom.refs`, `sources.ax.refs`, `frame_sections`,\n`approx_tokens`, `elapsed_ms`, and any per-source `error`. Use this before\narguing that AX should become the default on a site.\n\n### Scrape a list via network instead of DOM\n\n```bash\nopencli browser hn open \"https:\u002F\u002Fnews.ycombinator.com\"\nopencli browser hn network --filter \"title,score\"\n# -> find the \u002Ftopstories entry, note its key\nopencli browser hn network --detail topstories-a1b2\n```\n\n### Read a long article in chunks\n\n```bash\nopencli browser article open \"https:\u002F\u002Fblog.example.com\u002Flong-post\"\nopencli browser article extract --chunk-size 8000\n# -> content + next_start_char: 8000\nopencli browser article extract --start 8000 --chunk-size 8000\n# ...until next_start_char is null\n```\n\n### Cross-origin iframe\n\n```bash\nopencli browser checkout frames\n# -> [{\"index\": 0, \"url\": \"https:\u002F\u002Fcheckout.stripe.com\u002F...\", ...}]\nopencli browser checkout eval \"(() => document.querySelector('input[name=cardnumber]')?.value)()\" --frame 0\n```\n\n`browser state --source ax` may omit cross-origin iframe contents or fail to\nroute actions into them when Chrome does not expose an attachable OOPIF target\nto the extension. In that case use `browser frames` + `browser eval --frame`, a\nnormal DOM `state`, or navigate\u002Fbind directly to the iframe URL when possible.\n\n---\n\n## Pitfalls\n\n- **Do not submit forms via `eval \"document.forms[0].submit()\"`** — modern sites intercept with JS handlers and silently drop the call. Either `click` the submit button via its ref, or (if you know the GET URL) just `open` it directly.\n- **Do not reuse refs across a page transition.** `wait` for the new state, then re-`state`. Old refs will either 404 or (worse) `reidentify` onto a similarly-shaped element on the new page.\n- **`match_level: reidentified` is a warning, not an error.** The action went through, but if you are chaining 5 more writes that all depend on that being the right element, verify with a `get text` or `get value` before continuing.\n- **Budget-aware commands silently cap.** `get html --as json` with default budgets will return `truncated: {...}`. If your downstream logic needs the whole subtree, raise `--depth` \u002F `--children-max` or tighten the selector.\n- **`autocomplete: true` on a `type` response is not an error.** It means a suggestion popup is open and your value isn't committed yet. Typically `keys Enter` to accept the first suggestion, or `click` the one you want.\n- **`network --filter` is AND-semantics on path segments.** `--filter \"title,score\"` keeps entries whose body shape contains *both* `title` and `score` as path segments, at any depth. It is not a regex.\n- **Screenshots are for humans, not for agents.** Use `state` + `find` unless the page is genuinely visual (captcha, chart). Screenshots burn tokens and rarely add signal an agent can act on.\n\n---\n\n## Troubleshooting\n\n| symptom | fix |\n|---------|-----|\n| `opencli doctor` red: \"Browser not connected\" | Start Chrome with `--remote-debugging-port=9222`, or install the extension from the [Chrome Web Store](https:\u002F\u002Fchromewebstore.google.com\u002Fdetail\u002Fopencli\u002Fildkmabpimmkaediidaifkhjpohdnifk). |\n| `attach failed: chrome-extension:\u002F\u002F...` | Disable 1Password \u002F other CDP-hungry extensions temporarily. |\n| `selector_not_found` right after `state` | Page mutated. `wait selector \"...\"` then retry. |\n| `stale_ref` across every command | You are reusing refs from a prior page. Re-`state`. |\n| `click` succeeds but nothing happens | The element is probably a decorative wrapper stealing clicks from the real target. `find --css \"...\"` with a narrower selector and retry on the inner element. |\n| `type` appears to finish but value is wrong | Autocomplete, masked input, or React controlled re-render. Verify with `get value`. Add `keys Enter` or re-type. |\n| Giant `get html` output | Pass `--selector` + `--as json --depth 3 --children-max 20 --text-max 200`. |\n| Network cache seems stale | Bump `--ttl` down, or let it expire. The cache lives at `~\u002F.opencli\u002Fcache\u002Fbrowser-network\u002F`. |\n\n---\n\n## See also\n\n- `opencli-adapter-author` — turning what you just figured out into a reusable `~\u002F.opencli\u002Fclis\u002F\u003Csite>\u002F\u003Ccommand>.js`.\n- `opencli-browser-sitemap` — consuming site sitemap context while driving a browser task.\n- `opencli-sitemap-author` — creating or updating sitemap knowledge when you discover a durable path or stale entry.\n- `opencli-autofix` — when an existing adapter breaks, this skill walks you through `--trace retain-on-failure` evidence and filing a fix.\n","# opencli-browser\n\nThe first reader of this CLI is an agent, not a human. Every subcommand returns a structured envelope that tells you exactly what matched, how confident the match is, and what to do if it didn't. Lean on those envelopes — do not guess.\n\nThis skill is for **driving a live browser** to accomplish an agent task. If you are building a reusable adapter under `~\u002F.opencli\u002Fclis\u002F\u003Csite>\u002F` use `opencli-adapter-author` instead.\n\n---\n\n## Prerequisites\n\n```bash\nopencli doctor\n```\n\nUntil `doctor` is green, nothing else will work. Typical failures: Chrome not running, extension not installed, debug port blocked by 1Password \u002F other extensions. The doctor output tells you which.\n\n---\n\n## Session lifecycle\n\n- `opencli browser *` commands require a `\u003Csession>` positional immediately after `browser`. Use the same session name for a multi-step flow; use a different name to isolate parallel browser work.\n- Use a stable session name for any multi-command or human-paced browser workflow. Example: `opencli browser fb-yaya-warmup open https:\u002F\u002Fexample.com`, then reuse `opencli browser fb-yaya-warmup state`, `extract`, `click`, etc.\n- Owned browser sessions keep a tab lease alive between calls. Release it with `opencli browser \u003Csession> close` or let the idle timeout expire.\n- `opencli browser \u003Csession> bind` binds the Chrome tab you already have open to that session. Use this for logged-in pages, SSO flows, or pages you manually positioned before handing control to the agent.\n- `--window foreground|background` (or `OPENCLI_WINDOW=foreground|background`) chooses whether OpenCLI creates\u002Ffocuses a foreground browser window or uses a background browser window for owned sessions.\n\n### Bind Tab\n\n```bash\nopencli browser gmail bind\nopencli browser gmail state\nopencli browser gmail click \"Search\"\nopencli browser gmail network\nopencli browser gmail unbind\n```\n\nBinding never owns the user window and never closes the user tab. It fails closed if the tab is closed or becomes non-debuggable. Re-run `opencli browser \u003Csession> bind` when you switch to a different real tab.\n\nNavigation is allowed on bound sessions because the session now represents explicit agent ownership of that tab. Tab mutation (`tab new`, `tab select`, `tab close`) is still blocked for bound sessions. Use an owned session when you want OpenCLI to manage tab lifecycle.\n\nBound sessions have no OpenCLI idle-close timer; the binding lasts until `unbind`, tab close, window close, or daemon restart.\n\n---\n\n## Mental model\n\n1. **Selector-first target contract.** Every interaction command (`click`, `type`, `select`, `get text\u002Fvalue\u002Fattributes`) takes one `\u003Ctarget>`, which is *either* a numeric ref from `state`\u002F`find` *or* a CSS selector. Use `--nth \u003Cn>` to disambiguate multiple CSS matches.\n2. **Every envelope reports `matches_n` and `match_level`.** `match_level` is `exact`, `stable`, or `reidentified` — the CLI already rescued moderate DOM drift for you, but the level tells you how confident to be.\n3. **Compact output first, full payload on demand.** `state` is a budget-aware snapshot; `get html --as json` supports `--depth\u002F--children-max\u002F--text-max`; `network` returns shape previews and you re-fetch a single body with `--detail \u003Ckey>`. If you emit a giant payload you are burning context you did not need to burn.\n4. **Structured errors are machine-readable.** On failure the CLI emits `{error: {code, message, hint?, candidates?}}`. Branch on `code`, not on message strings.\n\n---\n\n## Critical rules\n\n1. **Always inspect before you act.** Run `state` or `find` first. Never hard-code a ref or selector from memory across sessions — indices are per-snapshot.\n2. **Prefer site adapters before raw browser driving.** If `opencli \u003Csite> \u003Ccommand>` already covers the task, use that adapter command first (`opencli facebook notifications`, `opencli reddit read`, `opencli chatgpt model \u003Clevel>`, etc.). Use `opencli browser ...` only for gaps, debugging, or one-off UI flows the adapter does not expose.\n3. **Prefer numeric ref over CSS once you have it.** Numeric refs survive mild DOM shifts because the CLI fingerprints each tagged element. A CSS selector written by hand will break the first time the site re-renders.\n4. **Read `match_level` after every write.** `exact` = all good. `stable` = the element is the same but some soft attrs drifted — your action still applied. `reidentified` = the original ref was gone and the CLI found a unique replacement; double-check you hit the right element.\n5. **Use the `compound` field for form controls.** Do not regex-guess a date format, do not `state` twice to get the full `\u003Cselect>` options list. The compound envelope has the format string, full option list up to 50, `options_total` for overflow, and `accept`\u002F`multiple` for `\u003Cinput type=file>`.\n6. **Verify writes that matter.** After `type \u003Ctarget> \u003Ctext>`, run `get value \u003Ctarget>`. After `select`, run `get value`. Autocomplete widgets, React controlled inputs, and masked fields all silently eat characters. The CLI cannot detect this for you.\n7. **`state` → action → `state` after a page change.** Navigations, form submits, and SPA route changes invalidate refs. Take a fresh snapshot. Do not reuse refs from before the transition.\n8. **Chain with `&&` when reusing freshly parsed refs.** A chained sequence runs in one shell so the ref you just read from output can be passed directly to the next command. Separate shell invocations keep the named browser session, but any shell-local variables or copied refs from the previous command can go stale after page changes.\n9. **`eval` is read-only.** Wrap the JS in an IIFE and return JSON. If you need to *change* the page, use the structured `click` \u002F `type` \u002F `select` \u002F `keys` commands instead — they produce structured output and fingerprints, `eval` does not.\n10. **Prefer `network` to screen-scraping.** If a page you care about fetches its data from a JSON API, the API is almost always more reliable than scraping the rendered DOM. Capture once, inspect the shape, then `--detail \u003Ckey>` the body you need.\n\n---\n\n## Sitemaps\n\nIf `browser open` or `browser analyze` returns `sitemap.available: true`, switch to `opencli-browser-sitemap` before continuing a multi-step site flow. The sitemap is prior context for pages, actions, workflows, APIs, and pitfalls; it is not truth. If the browser state disagrees with the sitemap, trust the browser and mark the sitemap stale via `opencli-sitemap-author`.\n\n---\n\n## Target contract (`\u003Ctarget>` for click \u002F type \u002F select \u002F get text|value|attributes)\n\n```\n\u003Ctarget> ::= \u003Cnumeric-ref> | \u003Ccss-selector>\n```\n\n- **Numeric ref** — the `[N]` index from `state` or `find`. Cheap, resilient to soft DOM drift.\n- **CSS selector** — anything `querySelectorAll` accepts. Must be unambiguous on write ops, or pair with `--nth \u003Cn>`.\n\n### Envelope on success\n\n```json\n{ \"clicked\": true, \"target\": \"3\", \"matches_n\": 1, \"match_level\": \"exact\" }\n```\n\n```json\n{ \"value\": \"kalevin@example.com\", \"matches_n\": 1, \"match_level\": \"stable\" }\n```\n\n### match_level\n\n| level | meaning | you should |\n|-------|---------|------------|\n| `exact` | Fingerprint agreed on tag + strong IDs with at most one soft drift | Proceed. |\n| `stable` | Tag + strong IDs still agree, soft signals (aria-label, role, text) drifted | Proceed, but if *what* you typed\u002Fclicked matters, re-check with `get value` or `state`. |\n| `reidentified` | Original ref was gone; a unique live element matched the fingerprint and was re-tagged with the old ref | Double-check you hit the right element before chaining more writes. |\n\n### Structured error codes\n\nBranch on these, not on the human message:\n\n| code | meaning |\n|------|---------|\n| `not_found` | Numeric ref is no longer in the DOM. Re-`state`. |\n| `stale_ref` | Ref exists but the element at that ref changed identity. Re-`state`. |\n| `invalid_selector` | CSS was rejected by `querySelectorAll`. Fix the selector. |\n| `selector_not_found` | CSS matches 0 elements. Try `find` with a looser selector. |\n| `selector_ambiguous` | CSS matches >1 and no `--nth`. Add `--nth` or narrow the selector. |\n| `selector_nth_out_of_range` | `--nth` beyond match count. |\n| `option_not_found` | `select` couldn't find an option matching that label\u002Fvalue. Error envelope includes `available: string[]` of the real option labels. |\n| `not_a_select` | `select` was called on a non-`\u003Cselect>` element. |\n\nError envelope always includes `error.code` and `error.message`. Target errors (`selector_not_found`, `selector_ambiguous`, etc.) often add `error.candidates: string[]` with suggested selectors. `option_not_found` adds `error.available: string[]` instead.\n\n---\n\n## Command reference\n\n### Inspect\n\n| command | purpose |\n|---------|---------|\n| `browser state` | Snapshot: text tree with `[N]` refs, scroll hints, hidden-interactive hints, `compounds (N):` sidecar for date\u002Fselect\u002Ffile refs. |\n| `browser state --source ax` | Opt-in accessibility-tree snapshot. Use when custom controls, portals, or iframe contents are hard to identify in normal `state`. AX refs can recover stale React re-renders by role\u002Fname\u002Fnth and can route same-origin iframe refs. Cross-origin iframe refs are best-effort because Chrome may not expose attachable OOPIF targets to extensions. |\n| `browser state --compare-sources` | Metrics-only DOM vs AX comparison for deciding whether AX should become default. It prints counts and sizes, not page text, so it is safer to share for validation. |\n| `browser find --css \u003Csel> [--limit N] [--text-max N]` | Run a CSS query and return one entry per match with `{nth, ref, tag, role, text, attrs, visible, compound?}`. Allocates refs for matches the prior snapshot didn't tag. Cheap alternative to `state` when you already know the selector. |\n| `browser find --role button --name Save` | Semantic locator query. Also supports `--label`, `--text`, and `--testid`. Use before raw CSS when a control has accessible labels. |\n| `browser frames` | List cross-origin iframe targets. Pass the index to `--frame` on `eval`. |\n| `browser screenshot [path]` | Viewport PNG. No path → base64 to stdout. Prefer `state` when you just need structure. |\n| `browser screenshot --annotate [path]` | Visual ref map. Refreshes DOM refs and overlays visible `[N]` labels so the screenshot maps back to `browser click \u003Cref>` targets. Use for icon-only controls, visual layouts, charts, or when text state is ambiguous. |\n\n### Get (read-only)\n\n| command | returns |\n|---------|---------|\n| `browser get title` | plain text |\n| `browser get url` | plain text |\n| `browser get text \u003Ctarget> [--nth N]` | `{value, matches_n, match_level}` |\n| `browser get value \u003Ctarget> [--nth N]` | `{value, matches_n, match_level}` |\n| `browser get attributes \u003Ctarget> [--nth N]` | `{value: {attr: val, ...}, matches_n, match_level}` |\n| `browser get text --role option --name Travel` | Semantic locator read without a prior `state` call. Same flags as `browser find`. |\n| `browser get html [--selector \u003Ccss>] [--as html\\|json] [--depth N] [--children-max N] [--text-max N] [--max N]` | Raw HTML, or structured tree. JSON tree nodes have `{tag, attrs, text, children[], compound?}`. Truncation reported via `truncated: {depth?, children_dropped?, text_truncated?}`. |\n\n### Interact\n\n| command | notes |\n|---------|-------|\n| `browser click \u003Ctarget> [--nth N]` | Returns `{clicked, target, matches_n, match_level}`. |\n| `browser click --role button --name Submit` | Semantic click. Write actions require a unique match; ambiguous locators return candidates instead of clicking the first match. |\n| `browser hover [target] [--role R --name N] [--nth N]` | Moves the mouse over an element. Use for hover menus\u002Ftooltips before taking `state` or clicking submenu items. Returns `{hovered, target, matches_n, match_level}`. |\n| `browser focus [target] [--role R --name N] [--nth N]` | Focuses an element without typing. Useful before `keys` or when a page reacts to focus\u002Fblur. Returns `{focused, target, matches_n, match_level}`. |\n| `browser dblclick [target] [--role R --name N] [--nth N]` | Double-clicks an element via native mouse events when available. Returns `{dblclicked, target, matches_n, match_level}`. |\n| `browser check [target] [--role R --name N] [--nth N]` | Ensures checkbox\u002Fradio\u002Faria-checked control is checked. Returns `{checked, changed, target, matches_n, match_level, kind}`. Prefer this over blind `click` when target state matters. |\n| `browser uncheck [target] [--role R --name N] [--nth N]` | Ensures checkbox\u002Faria-checked control is unchecked. Radio buttons cannot be unchecked directly; select another radio in the group instead. |\n| `browser upload [target] \u003Cfile...> [--role R --name N] [--nth N]` | Attaches local file path(s) to an `input[type=file]` via CDP. With semantic flags, omit `target` and pass files as positionals. Returns `{uploaded, files, file_names, target, matches_n, match_level, multiple?, accept?}`. |\n| `browser drag [source] [target] [--from-role R --from-name N] [--to-role R --to-name N] [--from-nth N] [--to-nth N]` | Mouse-based drag from one resolved element center to another. Works for mouse-listener drag libraries; native HTML5 `dataTransfer` drops may need a site-specific fallback. Returns `{dragged, source, target, source_matches_n, target_matches_n, ...}`. |\n| `browser type [target] \u003Ctext> [--role R --name N] [--nth N]` | Clicks first, then types. With semantic flags, omit `target` and pass text as the only positional. Returns `{typed, text, target, matches_n, match_level, autocomplete}`. `autocomplete: true` means a combobox\u002Fdatalist popup appeared after typing — you almost always need `keys Enter` or a follow-up `click` on the suggestion to commit the value. |\n| `browser fill [target] \u003Ctext> [--role R --name N] [--nth N]` | Exact replacement for input, textarea, and contenteditable targets. With semantic flags, omit `target` and pass text as the only positional. Returns `{filled, verified, text, actual, matches_n, match_level}`. Use this when you need raw text set and verified, not keyboard\u002Fautocomplete behavior. Pipeline form supports `{ fill: { ref, text, submit: true } }`. |\n| `browser select [target] \u003Coption> [--role R --name N] [--nth N]` | Matches native `\u003Cselect>` option by label first, then value. With semantic flags, omit `target` and pass option as the only positional. Use `compound` from `find`\u002F`state` to see exactly what labels are available. |\n| `browser keys \u003Ckey>` | `Enter`, `Escape`, `Tab`, `Control+a`, etc. Runs against the focused element. |\n| `browser scroll \u003Cdirection> [--amount px]` | `up` \u002F `down`. Default amount `500`. |\n\n### Wait\n\n```bash\nbrowser wait selector \"\u003Ccss>\" [--timeout ms]    # wait until the selector matches\nbrowser wait text \"\u003Csubstring>\" [--timeout ms]  # wait until the text appears\nbrowser wait download [pattern] [--timeout ms]  # wait for a Chrome download whose filename\u002FURL\u002Fmime contains pattern\nbrowser wait time \u003Cseconds>                     # hard sleep, last resort\n```\n\nDefault timeout `10000` ms. SPA routes, login redirects, and lazy-loaded lists need `wait` before `state`\u002F`get`.\n\n`browser wait download` requires Browser Bridge extension 1.0.8+ because it uses\nChrome's downloads lifecycle API. Pass a narrow filename or URL substring such\nas `receipt.pdf` when possible; an empty pattern waits for the next\u002Frecent\ndownload in the timeout window. The command reports `{downloaded, filename, url,\nstate, elapsedMs}` on success and a JSON error envelope on timeout\u002Ffailure.\n\n### Extract\n\n- **`web read --url \u003Curl>`** — One-shot Markdown reader for arbitrary pages. It expands relevant same-origin iframes by default, so old iframe-shell sites work better than with a top-document-only scrape. Use `--frames all-same-origin` when completeness matters more than Markdown noise. For AJAX shell pages use `opencli web read --url \u003Curl> --wait-for \"\u003Cselector>\" --wait-until networkidle --diagnose`; diagnostics show frame URLs, empty containers, and API-like XHRs. If the value you need is table\u002FAPI data, switch to `browser network` or a dedicated adapter instead of relying on Markdown.\n- **`browser eval \u003Cjs> [--frame N]`** — Run an expression in the page (or in a cross-origin frame via `--frame`). Wrap in an IIFE and return JSON. Read-only: no `document.forms[0].submit()`, no clicks, no navigations. If the result is a string, stdout is the raw string; otherwise it's JSON.\n- **`browser extract [--selector \u003Ccss>] [--chunk-size N] [--start N]`** — Markdown extraction of long-form content with a continuation cursor. Returns `{url, title, selector, total_chars, chunk_size, start, end, next_start_char, content}`. Loop on `next_start_char` until it is `null`. Auto-scopes to `\u003Cmain>`\u002F`\u003Carticle>`\u002F`\u003Cbody>` if you don't pass `--selector`.\n\n### Network\n\n```bash\nbrowser network                        # shape preview + cache key list\nbrowser network --detail \u003Ckey>         # full body for one cached entry\nbrowser network --filter \"field1,field2\"  # keep only entries whose body shape contains ALL fields as path segments\nbrowser network --all                  # include static resources (usually noise)\nbrowser network --raw                  # full bodies inline — large; use sparingly\nbrowser network --ttl \u003Cms>             # cache TTL (default 24h)\n```\n\nList entries look like `{key, method, status, url, ct, size, shape, body_truncated?}`. Detail envelope is `{key, url, method, status, ct, size, shape, body, body_truncated?, body_full_size?, body_truncation_reason}`. Cache lives in `~\u002F.opencli\u002Fcache\u002Fbrowser-network\u002F` so you can re-inspect without re-triggering the request.\n\nDefault output keeps JSON\u002FXML\u002Fplain-text and JS-like API responses, then drops obvious static assets and telemetry by URL. If an expected endpoint is missing, run `browser network --all` once and check whether an unusual content type or URL filter hid it.\n\n### Tabs & session\n\n| command | purpose |\n|---------|---------|\n| `browser tab list` | JSON array of `{index, page, url, title, active}`. The `page` string is the tab identity you pass as `\u003CtargetId>` to `tab select` \u002F `tab close`, or to `--tab \u003CtargetId>` on any subcommand. (`--tab`'s placeholder is historical — the value is always `page`.) |\n| `browser tab new [url]` | Open a new tab. Prints the new `page` string. |\n| `browser tab select [targetId]` | Make a tab the default. All subcommands accept `--tab \u003CtargetId>` to target one without changing the default. |\n| `browser tab close [targetId]` | Close by `page`. |\n| `browser back` | History back on the active tab. |\n| `browser close` | Release the current owned browser session when done. |\n| `browser \u003Csession> bind` | Bind the current Chrome tab to the named browser session. |\n| `browser \u003Csession> unbind` | Detach the named bound session without closing the user tab\u002Fwindow. |\n\n---\n\n## Compound form controls\n\nEvery date\u002Ftime, select, and file input carries a `compound` field. Use it — do not regex attributes.\n\n### Date family\n\n```json\n{\n  \"control\": \"date\",\n  \"format\": \"YYYY-MM-DD\",\n  \"current\": \"2026-04-21\",\n  \"min\": \"2026-01-01\",\n  \"max\": \"2026-12-31\"\n}\n```\n\n`control` is one of `date | time | datetime-local | month | week`. `format` is a concrete template string — type into the field using that exact format, or `select` by label if the site wraps the native input in a custom widget.\n\n### Select\n\n```json\n{\n  \"control\": \"select\",\n  \"multiple\": false,\n  \"current\": \"United States\",\n  \"options\": [\n    { \"label\": \"United States\", \"value\": \"us\", \"selected\": true },\n    { \"label\": \"Canada\", \"value\": \"ca\" }\n  ],\n  \"options_total\": 137\n}\n```\n\n`options[]` is capped at 50 entries. **`current` is always correct** even when the selected option is past the cap — it's computed by scanning every option, not from the truncated list. If `options_total > options.length` and you need an option that isn't in `options[]`, call `browser select \u003Ctarget> \"\u003Clabel>\"` directly — the CLI matches against the live DOM, not the truncated list.\n\n### File\n\n```json\n{\n  \"control\": \"file\",\n  \"multiple\": true,\n  \"current\": [\"report.pdf\", \"cover.png\"],\n  \"accept\": \"application\u002Fpdf,image\u002F*\"\n}\n```\n\nDo not invent file paths. Upload is done via the normal click flow — respect `accept` when telling the user what to upload.\n\n### Where compounds show up\n\n- `browser find --css \u003Csel>` entries: inline on each match.\n- `browser get html --as json` tree nodes: inline on matching nodes.\n- `browser state` snapshot: in a `compounds (N):` sidecar keyed by numeric ref, so you can tell at a glance which `[N]` entries have rich metadata.\n\n---\n\n## Cost guide\n\nThink about payload size per call. Budgets exist for a reason.\n\n| command | rough cost | when to use |\n|---------|-----------|-------------|\n| `state` | medium (bounded by internal budget) | First call on any page, after every nav, when you need refs. |\n| `find --css \u003Csel>` | small | You already know the selector — one query, compact entries. |\n| `get title` \u002F `get url` | tiny | Sanity checks between steps. |\n| `get text\u002Fvalue\u002Fattributes` | tiny per call | Verifying one specific field. |\n| `get html` (raw) | can be huge | Avoid on unbounded pages. Always pair with `--selector` and a budget. |\n| `get html --as json --depth 3 --children-max 20` | medium | When you need to reason about structure, not a specific field. |\n| `screenshot` | large | Only when the page is visual (CAPTCHA, charts). Prefer `state`. |\n| `extract` | medium per chunk | Long-form reading. Loop via `next_start_char`. |\n| `network` (default) | small | First look at APIs. |\n| `network --detail \u003Ckey>` | varies | Pull one body. |\n| `network --raw` | huge | Only after `--filter` narrowed the candidate set. |\n| `eval \"JSON.stringify(...)\"` | controlled | Targeted extraction when none of the above fit. |\n\nRule of thumb: **one `state` per page transition, one `find` per follow-up query, one `get`\u002F`click`\u002F`type` per action.** If your plan involves >10 calls per page you are probably scraping instead of interacting — consider `extract` or `network`.\n\n---\n\n## Chaining rules\n\n**Good — one shell, live session:**\n\n```bash\nopencli browser hn open \"https:\u002F\u002Fnews.ycombinator.com\" \\\n  && opencli browser hn state \\\n  && opencli browser hn click 3\n```\n\n**Bad — each line is a fresh shell, refs from call 1 are already forgotten when call 2 runs.** (Only a problem if you rely on shell-scoped state; browser refs themselves persist in-page, but interleaving unrelated shells invites races.) Prefer `&&` when the steps are meant to be atomic.\n\n**Never** chain a write and then an immediate `state` without a `wait` if the action causes a network round-trip — you will snapshot the pre-response DOM and make bad decisions off stale data.\n\n---\n\n## Recipes\n\n### Fill a login form\n\n```bash\nopencli browser login open \"https:\u002F\u002Fexample.com\u002Flogin\"\nopencli browser login state                          # find [N] for email, password, submit\nopencli browser login type 4 \"me@example.com\"\nopencli browser login type 5 \"hunter2\"\nopencli browser login get value 4                    # verify (autocomplete can eat chars)\nopencli browser login click 6                        # submit\nopencli browser login wait selector \"[data-testid=account-menu]\" --timeout 15000\nopencli browser login state                          # fresh refs on the logged-in page\n```\n\n### Pick from a long dropdown\n\n```bash\nopencli browser form state                          # sidebar shows [12] \u003Cselect name=country>\nopencli browser form find --css \"select[name=country]\"\n# the compound.options_total is 137, but compound.current is \"\" — unselected.\nopencli browser form select 12 \"Uruguay\"\nopencli browser form get value 12                   # { value: \"uy\", match_level: \"exact\" }\n```\n\n### Pick from a custom React dropdown\n\nUse this for Radix, shadcn, Material UI, Mercury-style category fields, and\nother controls that are not native `\u003Cselect>`.\n\n```bash\nopencli browser mercury state                          # find category trigger ref\n# If the trigger\u002Foption is not clear, use AX:\nopencli browser mercury state --source ax              # look for combobox\u002Fbutton\u002Flistbox\u002Foption names\nopencli browser mercury click 7                        # click category trigger\nopencli browser mercury state --source ax              # fresh refs after the portal\u002Flistbox opens\nopencli browser mercury click 12                       # click option\nopencli browser mercury get text 7                     # verify visible selected label\n```\n\nDo not use `browser select` on these widgets. `browser select` is only for\nnative `\u003Cselect>` elements. Custom dropdowns should be driven with\n`state -> click trigger -> state -> click option -> verify`.\n\n### Compare DOM vs AX observation\n\nWhen deciding whether AX refs are better for a page, collect metrics without\nsharing page contents:\n\n```bash\nopencli browser compare state --compare-sources\n```\n\nReport `sources.dom.refs`, `sources.ax.refs`, `frame_sections`,\n`approx_tokens`, `elapsed_ms`, and any per-source `error`. Use this before\narguing that AX should become the default on a site.\n\n### Scrape a list via network instead of DOM\n\n```bash\nopencli browser hn open \"https:\u002F\u002Fnews.ycombinator.com\"\nopencli browser hn network --filter \"title,score\"\n# -> find the \u002Ftopstories entry, note its key\nopencli browser hn network --detail topstories-a1b2\n```\n\n### Read a long article in chunks\n\n```bash\nopencli browser article open \"https:\u002F\u002Fblog.example.com\u002Flong-post\"\nopencli browser article extract --chunk-size 8000\n# -> content + next_start_char: 8000\nopencli browser article extract --start 8000 --chunk-size 8000\n# ...until next_start_char is null\n```\n\n### Cross-origin iframe\n\n```bash\nopencli browser checkout frames\n# -> [{\"index\": 0, \"url\": \"https:\u002F\u002Fcheckout.stripe.com\u002F...\", ...}]\nopencli browser checkout eval \"(() => document.querySelector('input[name=cardnumber]')?.value)()\" --frame 0\n```\n\n`browser state --source ax` may omit cross-origin iframe contents or fail to\nroute actions into them when Chrome does not expose an attachable OOPIF target\nto the extension. In that case use `browser frames` + `browser eval --frame`, a\nnormal DOM `state`, or navigate\u002Fbind directly to the iframe URL when possible.\n\n---\n\n## Pitfalls\n\n- **Do not submit forms via `eval \"document.forms[0].submit()\"`** — modern sites intercept with JS handlers and silently drop the call. Either `click` the submit button via its ref, or (if you know the GET URL) just `open` it directly.\n- **Do not reuse refs across a page transition.** `wait` for the new state, then re-`state`. Old refs will either 404 or (worse) `reidentify` onto a similarly-shaped element on the new page.\n- **`match_level: reidentified` is a warning, not an error.** The action went through, but if you are chaining 5 more writes that all depend on that being the right element, verify with a `get text` or `get value` before continuing.\n- **Budget-aware commands silently cap.** `get html --as json` with default budgets will return `truncated: {...}`. If your downstream logic needs the whole subtree, raise `--depth` \u002F `--children-max` or tighten the selector.\n- **`autocomplete: true` on a `type` response is not an error.** It means a suggestion popup is open and your value isn't committed yet. Typically `keys Enter` to accept the first suggestion, or `click` the one you want.\n- **`network --filter` is AND-semantics on path segments.** `--filter \"title,score\"` keeps entries whose body shape contains *both* `title` and `score` as path segments, at any depth. It is not a regex.\n- **Screenshots are for humans, not for agents.** Use `state` + `find` unless the page is genuinely visual (captcha, chart). Screenshots burn tokens and rarely add signal an agent can act on.\n\n---\n\n## Troubleshooting\n\n| symptom | fix |\n|---------|-----|\n| `opencli doctor` red: \"Browser not connected\" | Start Chrome with `--remote-debugging-port=9222`, or install the extension from the [Chrome Web Store](https:\u002F\u002Fchromewebstore.google.com\u002Fdetail\u002Fopencli\u002Fildkmabpimmkaediidaifkhjpohdnifk). |\n| `attach failed: chrome-extension:\u002F\u002F...` | Disable 1Password \u002F other CDP-hungry extensions temporarily. |\n| `selector_not_found` right after `state` | Page mutated. `wait selector \"...\"` then retry. |\n| `stale_ref` across every command | You are reusing refs from a prior page. Re-`state`. |\n| `click` succeeds but nothing happens | The element is probably a decorative wrapper stealing clicks from the real target. `find --css \"...\"` with a narrower selector and retry on the inner element. |\n| `type` appears to finish but value is wrong | Autocomplete, masked input, or React controlled re-render. Verify with `get value`. Add `keys Enter` or re-type. |\n| Giant `get html` output | Pass `--selector` + `--as json --depth 3 --children-max 20 --text-max 200`. |\n| Network cache seems stale | Bump `--ttl` down, or let it expire. The cache lives at `~\u002F.opencli\u002Fcache\u002Fbrowser-network\u002F`. |\n\n---\n\n## See also\n\n- `opencli-adapter-author` — turning what you just figured out into a reusable `~\u002F.opencli\u002Fclis\u002F\u003Csite>\u002F\u003Ccommand>.js`.\n- `opencli-browser-sitemap` — consuming site sitemap context while driving a browser task.\n- `opencli-sitemap-author` — creating or updating sitemap knowledge when you discover a durable path or stale entry.\n- `opencli-autofix` — when an existing adapter breaks, this skill walks you through `--trace retain-on-failure` evidence and filing a fix.\n",{"name":5,"description":7,"allowed-tools":61},"Bash(opencli:*), Read, Edit, Write","mine","1e7390741dffd3d5041fca4ebc056071ee3ba137","archives\u002Fuser-installed-skills\u002Fopencli-browser\u002F3aa28f5282d5ab1b\u002Fopencli-browser",5,"https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub","https:\u002F\u002Fgithub.com\u002FEthanYoQ","www.orz.md","Part Marketer, Part AI Explorer.","2026-11-07 14:52:29",{"source":8,"skillName":5,"commands":72,"prompt":82,"zipAvailable":83},[73,76,79],{"tool":74,"command":75},"npx","npx skills add EthanYoQ\u002FSkill-hub --skill opencli-browser",{"tool":77,"command":78},"bunx","bunx skills add EthanYoQ\u002FSkill-hub --skill opencli-browser",{"tool":80,"command":81},"pnpm","pnpm dlx skills add EthanYoQ\u002FSkill-hub --skill opencli-browser","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Farchives\u002Fuser-installed-skills\u002Fopencli-browser\u002F3aa28f5282d5ab1b\u002Fopencli-browser\u002FSKILL.md",true,[85],{"path":86,"resourceType":86,"resourceTypeDesc":87,"sizeBytes":88,"sourceHtmlUrl":16},"LICENSE","许可证",10760,[90,116,143,166,184,199],{"skillNo":91,"name":92,"nameZh":6,"description":93,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":94,"licenseFileUrl":6,"sourceHtmlUrl":95,"categories":96,"keywords":98,"publishedAt":56},"01M713CBDSYARG4PXFXK2R2NNN","xhs-pharma-social-listening","Run evidence-disciplined Xiaohongshu social listening for foreign pharma employee pain points. Use when the user asks to collect and analyze notes\u002Fcomments about pharma companies, aliases, roles, compliance, medical affairs, or AI topics, then produce a cited and scored sampled-insight report. Includes independent-evidence counting, MediaCrawler batch collection, and CAPTCHA\u002F461 fallback to Agent Reach\u002FOpenCLI.","MIT","https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Fskills\u002F10-business-industry\u002Fxhs-pharma-social-listening\u002FSKILL.md",[97],{"categoryNo":25,"categoryName":26,"taxonomyType":22,"taxonomyTypeDesc":23},[99,100,101,102,103,104,40,105,106,107,108,109,37,110,111,112,113,48,114,115],"social","xhs pharma social listening","social listening","xhs","medical","counting","collection","batch","analyze","notes","run","fallback","captcha","agent reach","evidence","collect","produce",{"skillNo":117,"name":118,"nameZh":6,"description":119,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":94,"licenseFileUrl":6,"sourceHtmlUrl":120,"categories":121,"keywords":124,"publishedAt":56},"01M713C8Q76ZP0WXXBRAFKS6EE","healthcare-phi-compliance","Protected Health Information (PHI) and Personally Identifiable Information (PII) compliance patterns for healthcare applications. Covers data classification, access control, audit trails, encryption, and common leak vectors. Use when code touches PHI or PII in a healthcare system, or when auditing access control, audit trails, or leak vectors.","https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Fskills\u002F10-business-industry\u002Fhealthcare-phi-compliance\u002FSKILL.md",[122,123],{"categoryNo":25,"categoryName":26,"taxonomyType":22,"taxonomyTypeDesc":23},{"categoryNo":33,"categoryName":34,"taxonomyType":30,"taxonomyTypeDesc":31},[125,126,127,128,129,130,131,132,40,133,134,135,136,137,138,139,48,140,141,142],"healthcare","phi","compliance","healthcare phi compliance","pii","compliance patterns","information","access","encryption","code","common","audit","data","patterns","Access Control","system","applications","Auditing Access Control",{"skillNo":144,"name":145,"nameZh":6,"description":146,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":94,"licenseFileUrl":6,"sourceHtmlUrl":147,"categories":148,"keywords":150,"publishedAt":56},"01M713C5R2GE43X45T2K75TB76","healthcare-eval-harness","Patient safety evaluation harness for healthcare application deployments. Automated test suites for CDSS accuracy, PHI exposure, clinical workflow integrity, and integration compliance. Blocks deployments on safety failures. Use when a healthcare deployment must be gated on patient-safety tests for CDSS accuracy, PHI exposure, and workflow integrity.","https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Fskills\u002F10-business-industry\u002Fhealthcare-eval-harness\u002FSKILL.md",[149],{"categoryNo":25,"categoryName":26,"taxonomyType":22,"taxonomyTypeDesc":23},[125,151,152,153,154,155,156,40,157,158,159,160,126,48,161,162,163,164,47,165],"eval","harness","healthcare eval harness","eval harness","clinical","patient","workflow","Evaluation","Safety","integrity","deployment","accuracy","tests","deployments","super",{"skillNo":167,"name":168,"nameZh":6,"description":169,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":94,"licenseFileUrl":6,"sourceHtmlUrl":170,"categories":171,"keywords":173,"publishedAt":56},"01M713C2J17S91VYR7A8M696VR","healthcare-emr-patterns","EMR\u002FEHR development patterns for healthcare applications. Clinical safety, encounter workflows, prescription generation, clinical decision support integration, and accessibility-first UI for medical data entry. Use when building EMR or EHR features such as encounter workflows, prescription generation, or clinical data entry UI.","https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Fskills\u002F10-business-industry\u002Fhealthcare-emr-patterns\u002FSKILL.md",[172],{"categoryNo":25,"categoryName":26,"taxonomyType":22,"taxonomyTypeDesc":23},[125,174,138,175,176,103,155,177,40,178,179,180,159,137,181,182,183,48,141,47],"EMR","healthcare emr patterns","decision","workflows","support","clinical decision support","features","ui","as","development",{"skillNo":185,"name":186,"nameZh":6,"description":187,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":94,"licenseFileUrl":6,"sourceHtmlUrl":188,"categories":189,"keywords":191,"publishedAt":56},"01M713BZERTXV0N8X6SW9DDVWZ","healthcare-cdss-patterns","Clinical Decision Support System (CDSS) development patterns. Drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), alert severity classification, and integration into EMR workflows. Use when building clinical decision support — drug interaction checks, dose validation, clinical scoring, or alert severity.","https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Fskills\u002F10-business-industry\u002Fhealthcare-cdss-patterns\u002FSKILL.md",[190],{"categoryNo":25,"categoryName":26,"taxonomyType":22,"taxonomyTypeDesc":23},[125,138,192,176,155,174,177,40,193,194,178,179,195,183,140,196,47,197,165,198],"healthcare cdss patterns","scoring","interaction","severity","classification","alert","decision support",{"skillNo":200,"name":201,"nameZh":6,"description":202,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":94,"licenseFileUrl":6,"sourceHtmlUrl":203,"categories":204,"keywords":206,"publishedAt":56},"01M713BVK3PQR7V4ZKYGMWMXJD","dxy-task-clarify","用户的请求含糊、缺背景、缺资料说明、缺读者或用途时使用。先用少量带选项的追问把任务问清楚，写成任务卡，用户确认后再执行。尤其适用于医生、科研人员和药企各岗位的工作，例如「帮我分析一下这些数据」「帮我准备明天的拜访」「写个总结」「做个 PPT」「回一下这封邮件」「整理一下会议纪要」「整理一下这些文献」「帮我准备下周的讲课」「写个病例汇报」「帮我写个提示词」。用户说「先问我」「帮我理清需求」「我不知道怎么描述」「我不会问 AI」时也使用。用途、资料、读者都已说清的请求，纯翻译，以及写代码、改代码，不使用。","https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Fskills\u002F10-business-industry\u002Fdxy-task-clarify\u002FSKILL.md",[205],{"categoryNo":25,"categoryName":26,"taxonomyType":22,"taxonomyTypeDesc":23},[207,208,209,210,211,212,213,214],"clarify","dxy task clarify","task","pptx","整理","翻译","数据","总结"]