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...
---
name: opencli-browser
description: 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.
allowed-tools: Bash(opencli:*), Read, Edit, Write
---
# opencli-browser
The 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.
This skill is for **driving a live browser** to accomplish an agent task. If you are building a reusable adapter under `~/.opencli/clis/<site>/` use `opencli-adapter-author` instead.
---
## Prerequisites
```bash
opencli doctor
```
Until `doctor` is green, nothing else will work. Typical failures: Chrome not running, extension not installed, debug port blocked by 1Password / other extensions. The doctor output tells you which.
---
## Window lifecycle
- `opencli browser *` commands already keep the automation session alive between calls. The window stays open until you run `opencli browser close` or the idle timeout expires.
- `--focus` (or `OPENCLI_WINDOW_FOCUSED=1`) opens the automation window in the foreground. Use it when you want to watch the page live.
- `--live` (or `OPENCLI_LIVE=1`) is mainly for browser-backed adapter commands such as `opencli xiaohongshu note ...`. It keeps the adapter's automation window open after the command returns so you can inspect the final page state.
---
## Mental model
1. **Selector-first target contract.** Every interaction command (`click`, `type`, `select`, `get text/value/attributes`) takes one `<target>`, which is *either* a numeric ref from `state`/`find` *or* a CSS selector. Use `--nth <n>` to disambiguate multiple CSS matches.
2. **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.
3. **Compact output first, full payload on demand.** `state` is a budget-aware snapshot; `get html --as json` supports `--depth/--children-max/--text-max`; `network` returns shape previews and you re-fetch a single body with `--detail <key>`. If you emit a giant payload you are burning context you did not need to burn.
4. **Structured errors are machine-readable.** On failure the CLI emits `{error: {code, message, hint?, candidates?}}`. Branch on `code`, not on message strings.
---
## Critical rules
1. **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.
2. **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.
3. **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.
4. **Use the `compound` field for form controls.** Do not regex-guess a date format, do not `state` twice to get the full `<select>` options list. The compound envelope has the format string, full option list up to 50, `options_total` for overflow, and `accept`/`multiple` for `<input type=file>`.
5. **Verify writes that matter.** After `type <target> <text>`, run `get value <target>`. After `select`, run `get value`. Autocomplete widgets, React controlled inputs, and masked fields all silently eat characters. The CLI cannot detect this for you.
6. **`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.
7. **Chain with `&&`.** A chained sequence runs in one shell so refs acquired by the first command stay live for the second. Separate shell invocations lose the session context you just set up.
8. **`eval` is read-only.** Wrap the JS in an IIFE and return JSON. If you need to *change* the page, use the structured `click` / `type` / `select` / `keys` commands instead — they produce structured output and fingerprints, `eval` does not.
9. **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 <key>` the body you need.
---
## Target contract (`<target>` for click / type / select / get text|value|attributes)
```
<target> ::= <numeric-ref> | <css-selector>
```
- **Numeric ref** — the `[N]` index from `state` or `find`. Cheap, resilient to soft DOM drift.
- **CSS selector** — anything `querySelectorAll` accepts. Must be unambiguous on write ops, or pair with `--nth <n>`.
### Envelope on success
```json
{ "clicked": true, "target": "3", "matches_n": 1, "match_level": "exact" }
```
```json
{ "value": "kalevin@example.com", "matches_n": 1, "match_level": "stable" }
```
### match_level
| level | meaning | you should |
|-------|---------|------------|
| `exact` | Fingerprint agreed on tag + strong IDs with at most one soft drift | Proceed. |
| `stable` | Tag + strong IDs still agree, soft signals (aria-label, role, text) drifted | Proceed, but if *what* you typed/clicked matters, re-check with `get value` or `state`. |
| `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. |
### Structured error codes
Branch on these, not on the human message:
| code | meaning |
|------|---------|
| `not_found` | Numeric ref is no longer in the DOM. Re-`state`. |
| `stale_ref` | Ref exists but the element at that ref changed identity. Re-`state`. |
| `invalid_selector` | CSS was rejected by `querySelectorAll`. Fix the selector. |
| `selector_not_found` | CSS matches 0 elements. Try `find` with a looser selector. |
| `selector_ambiguous` | CSS matches >1 and no `--nth`. Add `--nth` or narrow the selector. |
| `selector_nth_out_of_range` | `--nth` beyond match count. |
| `option_not_found` | `select` couldn't find an option matching that label/value. Error envelope includes `available: string[]` of the real option labels. |
| `not_a_select` | `select` was called on a non-`<select>` element. |
Error 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.
---
## Command reference
### Inspect
| command | purpose |
|---------|---------|
| `browser state` | Snapshot: text tree with `[N]` refs, scroll hints, hidden-interactive hints, `compounds (N):` sidecar for date/select/file refs. |
| `browser find --css <sel> [--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. |
| `browser frames` | List cross-origin iframe targets. Pass the index to `--frame` on `eval`. |
| `browser screenshot [path]` | Viewport PNG. No path → base64 to stdout. Prefer `state` when you just need structure. |
### Get (read-only)
| command | returns |
|---------|---------|
| `browser get title` | plain text |
| `browser get url` | plain text |
| `browser get text <target> [--nth N]` | `{value, matches_n, match_level}` |
| `browser get value <target> [--nth N]` | `{value, matches_n, match_level}` |
| `browser get attributes <target> [--nth N]` | `{value: {attr: val, ...}, matches_n, match_level}` |
| `browser get html [--selector <css>] [--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?}`. |
### Interact
| command | notes |
|---------|-------|
| `browser click <target> [--nth N]` | Returns `{clicked, target, matches_n, match_level}`. |
| `browser type <target> <text> [--nth N]` | Clicks first, then types. Returns `{typed, text, target, matches_n, match_level, autocomplete}`. `autocomplete: true` means a combobox/datalist popup appeared after typing — you almost always need `keys Enter` or a follow-up `click` on the suggestion to commit the value. |
| `browser select <target> <option> [--nth N]` | Matches option by label first, then value. Use `compound` from `find`/`state` to see exactly what labels are available. |
| `browser keys <key>` | `Enter`, `Escape`, `Tab`, `Control+a`, etc. Runs against the focused element. |
| `browser scroll <direction> [--amount px]` | `up` / `down`. Default amount `500`. |
### Wait
```bash
browser wait selector "<css>" [--timeout ms] # wait until the selector matches
browser wait text "<substring>" [--timeout ms] # wait until the text appears
browser wait time <seconds> # hard sleep, last resort
```
Default timeout `10000` ms. SPA routes, login redirects, and lazy-loaded lists need `wait` before `state`/`get`.
### Extract
- **`browser eval <js> [--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.
- **`browser extract [--selector <css>] [--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 `<main>`/`<article>`/`<body>` if you don't pass `--selector`.
### Network
```bash
browser network # shape preview + cache key list
browser network --detail <key> # full body for one cached entry
browser network --filter "field1,field2" # keep only entries whose body shape contains ALL fields as path segments
browser network --all # include static resources (usually noise)
browser network --raw # full bodies inline — large; use sparingly
browser network --ttl <ms> # cache TTL (default 24h)
```
List 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 `~/.opencli/cache/browser-network/` so you can re-inspect without re-triggering the request.
### Tabs & session
| command | purpose |
|---------|---------|
| `browser tab list` | JSON array of `{index, page, url, title, active}`. The `page` string is the tab identity you pass as `<targetId>` to `tab select` / `tab close`, or to `--tab <targetId>` on any subcommand. (`--tab`'s placeholder is historical — the value is always `page`.) |
| `browser tab new [url]` | Open a new tab. Prints the new `page` string. |
| `browser tab select [targetId]` | Make a tab the default. All subcommands accept `--tab <targetId>` to target one without changing the default. |
| `browser tab close [targetId]` | Close by `page`. |
| `browser back` | History back on the active tab. |
| `browser close` | Close the automation window when done. |
---
## Compound form controls
Every date/time, select, and file input carries a `compound` field. Use it — do not regex attributes.
### Date family
```json
{
"control": "date",
"format": "YYYY-MM-DD",
"current": "2026-04-21",
"min": "2026-01-01",
"max": "2026-12-31"
}
```
`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.
### Select
```json
{
"control": "select",
"multiple": false,
"current": "United States",
"options": [
{ "label": "United States", "value": "us", "selected": true },
{ "label": "Canada", "value": "ca" }
],
"options_total": 137
}
```
`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 <target> "<label>"` directly — the CLI matches against the live DOM, not the truncated list.
### File
```json
{
"control": "file",
"multiple": true,
"current": ["report.pdf", "cover.png"],
"accept": "application/pdf,image/*"
}
```
Do not invent file paths. Upload is done via the normal click flow — respect `accept` when telling the user what to upload.
### Where compounds show up
- `browser find --css <sel>` entries: inline on each match.
- `browser get html --as json` tree nodes: inline on matching nodes.
- `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.
---
## Cost guide
Think about payload size per call. Budgets exist for a reason.
| command | rough cost | when to use |
|---------|-----------|-------------|
| `state` | medium (bounded by internal budget) | First call on any page, after every nav, when you need refs. |
| `find --css <sel>` | small | You already know the selector — one query, compact entries. |
| `get title` / `get url` | tiny | Sanity checks between steps. |
| `get text/value/attributes` | tiny per call | Verifying one specific field. |
| `get html` (raw) | can be huge | Avoid on unbounded pages. Always pair with `--selector` and a budget. |
| `get html --as json --depth 3 --children-max 20` | medium | When you need to reason about structure, not a specific field. |
| `screenshot` | large | Only when the page is visual (CAPTCHA, charts). Prefer `state`. |
| `extract` | medium per chunk | Long-form reading. Loop via `next_start_char`. |
| `network` (default) | small | First look at APIs. |
| `network --detail <key>` | varies | Pull one body. |
| `network --raw` | huge | Only after `--filter` narrowed the candidate set. |
| `eval "JSON.stringify(...)"` | controlled | Targeted extraction when none of the above fit. |
Rule of thumb: **one `state` per page transition, one `find` per follow-up query, one `get`/`click`/`type` per action.** If your plan involves >10 calls per page you are probably scraping instead of interacting — consider `extract` or `network`.
---
## Chaining rules
**Good — one shell, live session:**
```bash
opencli browser open "https://news.ycombinator.com" \
&& opencli browser state \
&& opencli browser click 3
```
**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.
**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.
---
## Recipes
### Fill a login form
```bash
opencli browser open "https://example.com/login"
opencli browser state # find [N] for email, password, submit
opencli browser type 4 "me@example.com"
opencli browser type 5 "hunter2"
opencli browser get value 4 # verify (autocomplete can eat chars)
opencli browser click 6 # submit
opencli browser wait selector "[data-testid=account-menu]" --timeout 15000
opencli browser state # fresh refs on the logged-in page
```
### Pick from a long dropdown
```bash
opencli browser state # sidebar shows [12] <select name=country>
opencli browser find --css "select[name=country]"
# the compound.options_total is 137, but compound.current is "" — unselected.
opencli browser select 12 "Uruguay"
opencli browser get value 12 # { value: "uy", match_level: "exact" }
```
### Scrape a list via network instead of DOM
```bash
opencli browser open "https://news.ycombinator.com"
opencli browser network --filter "title,score"
# -> find the /topstories entry, note its key
opencli browser network --detail topstories-a1b2
```
### Read a long article in chunks
```bash
opencli browser open "https://blog.example.com/long-post"
opencli browser extract --chunk-size 8000
# -> content + next_start_char: 8000
opencli browser extract --start 8000 --chunk-size 8000
# ...until next_start_char is null
```
### Cross-origin iframe
```bash
opencli browser frames
# -> [{"index": 0, "url": "https://checkout.stripe.com/...", ...}]
opencli browser eval "(() => document.querySelector('input[name=cardnumber]')?.value)()" --frame 0
```
---
## Pitfalls
- **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.
- **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.
- **`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.
- **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` / `--children-max` or tighten the selector.
- **`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.
- **`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.
- **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.
---
## Troubleshooting
| symptom | fix |
|---------|-----|
| `opencli doctor` red: "Browser not connected" | Start Chrome with `--remote-debugging-port=9222`, or rerun the extension install. |
| `attach failed: chrome-extension://...` | Disable 1Password / other CDP-hungry extensions temporarily. |
| `selector_not_found` right after `state` | Page mutated. `wait selector "..."` then retry. |
| `stale_ref` across every command | You are reusing refs from a prior page. Re-`state`. |
| `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. |
| `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. |
| Giant `get html` output | Pass `--selector` + `--as json --depth 3 --children-max 20 --text-max 200`. |
| Network cache seems stale | Bump `--ttl` down, or let it expire. The cache lives at `~/.opencli/cache/browser-network/`. |
---
## See also
- `opencli-adapter-author` — turning what you just figured out into a reusable `~/.opencli/clis/<site>/<command>.js`.
- `opencli-autofix` — when an existing adapter breaks, this skill walks you through `OPENCLI_DIAGNOSTIC` and filing a fix.
don't have the plugin yet? install it then click "run inline in claude" again.
extracted and formalized the implicit intent, inputs, and decision tree from the original clawhub guide; added structured error codes and edge cases (auth expiry, network timeouts, empty result sets, rate limits) as decision branches; clarified external connection setup and ref lifecycle rules; preserved the selector-first contract and compound field logic verbatim.
the first reader of this CLI is an agent, not a human. every subcommand returns a structured envelope telling you exactly what matched, how confident the match is, and what to do if it didn't. lean on those envelopes. do not guess.
this skill drives a live browser to accomplish an agent task. if you are building a reusable adapter under ~/.opencli/clis/<site>/ use opencli-adapter-author instead.
use this skill when an agent needs to automate a live Chrome window: inspect page state, fill and submit forms, click through authenticated flows, extract data from rendered pages, or capture network traffic. use it to bootstrap a one-off task, to debug a broken adapter (see opencli-autofix), or to handle sites that require JavaScript execution or session state. do not use this for writing reusable adapters (that is opencli-adapter-author).
required environment & setup:
--remote-debugging-port=9222opencli doctor)opencli doctor returns green status before any other commandoptional environment variables:
OPENCLI_WINDOW_FOCUSED=1 or --focus: open automation window in foreground for live inspectionOPENCLI_LIVE=1 or --live: keep the window open after adapter commands complete (for post-command inspection)external connections:
browser open or after browser close are not captured.context assumptions:
querySelectorAll (respects cross-origin iframes via --frame on eval).state / find are valid for the lifetime of the current page (invalidated by navigation, form submit, or SPA route change).input: none output: green status or list of failures
run opencli doctor. if any check fails (chrome not running, extension not installed, debug port blocked), fix it before proceeding. the doctor output tells you which.
input: target URL output: browser window active, tab opened, network capture initialized
run opencli browser open "<url>". this opens a new tab, initializes the network cache, and keeps the window alive until you run opencli browser close or timeout occurs. use --focus if you want to watch the page live.
input: CSS selector or text substring to wait for output: confirmation that the selector/text appeared or timeout
if the page is a SPA or uses lazy-loading, run opencli browser wait selector "<css>" or opencli browser wait text "<substring>" with --timeout <ms> (default 10000). this blocks until the element appears or timeout. do not state immediately after a navigation without a wait if the page is still loading.
input: none
output: JSON snapshot with [N] refs, text tree, visibility hints, and compounds sidecar
run opencli browser state. the output includes a text-tree view with numeric refs [0], [1], etc. for each interactive element. the compounds (N): sidecar at the bottom lists date/select/file inputs and their metadata. use the refs to target actions in the next steps.
alternatively, if you already know the CSS selector, run opencli browser find --css "<selector>" to query and allocate refs only for matches, bypassing a full state.
input: target (numeric ref or CSS selector), interaction type, optional additional args (text, option label, --nth for disambiguation)
output: success envelope with clicked: true, typed: true, etc., plus matches_n and match_level
click: run opencli browser click <target> where target is either [N] from state or a CSS selector. returns {clicked: true, target, matches_n, match_level}.
type: run opencli browser type <target> "<text>" to click then type into a form field. the envelope includes autocomplete: true if a suggestion popup appeared (usually needs keys Enter to commit). always verify the typed value with get value <target> on fields where character loss matters (dates, codes, autocomplete).
select: run opencli browser select <target> "<option-label>" to pick from a <select> or select-like widget. the CLI matches against option labels first, then values. use the compound.options array from state to see exact labels. if options_total > 50 and you need an unlisted option, call select anyway (it queries the live DOM).
keys: run opencli browser keys "<key>" to send keyboard input. common keys: Enter, Escape, Tab, Control+a, ArrowDown. operates on the currently focused element.
scroll: run opencli browser scroll <direction> with direction up or down and optional --amount <px> (default 500). use before clicking if the target is off-screen.
input: target (the field you just wrote to)
output: envelope with value and match_level
after type or select, immediately run opencli browser get value <target> to confirm the value was accepted. React controlled inputs, autocomplete fields, masked inputs, and form validation can silently drop or transform characters. if the value is wrong, re-type, inspect match_level for drift, or use keys Enter to commit autocomplete.
input: none (triggered by prior click, form submit, or navigation) output: none at this step; wait for the next step
after a click that triggers a form submit, navigation, or SPA route change, take a fresh state snapshot. refs from before the transition are invalidated (either not_found or will reidentify onto a new element). do not reuse numeric refs across a page transition.
input: target, or CSS selector, or network filter output: JSON envelope with extracted value(s), shape, or cached body
single values: run opencli browser get text <target>, get value <target>, or get attributes <target> to read one element.
structured HTML: run opencli browser get html --as json --depth 3 --children-max 20 to get a JSON tree with bounds (default parameters are budget-aware). pass --selector "<css>" to scope to a subtree.
long-form text: run opencli browser extract --chunk-size 8000 to read article content in chunks. loop on next_start_char until it is null.
network data: run opencli browser network to see cached requests, then network --detail <key> to fetch a single response body. use --filter "field1,field2" to keep only entries whose shape contains all listed path segments (AND-semantics).
custom JS: run opencli browser eval "(() => JSON.stringify(result))()" to run read-only JavaScript. wrap in an IIFE and return JSON. do not use for mutations (use click / type / select instead).
input: none output: window closed, session ended
run opencli browser close when done. the window also auto-closes after the idle timeout expires. refs are lost; any future actions require a fresh open and state.
if Chrome is not running or debug port is blocked:
then start Chrome with --remote-debugging-port=9222 or move blocking extensions (1Password, etc.) to a separate profile. re-run opencli doctor to verify.
if the page is a single-page app (SPA) or lazy-loads content:
then use browser wait selector "<css>" or browser wait text "<text>" before the next state, so refs don't land in a partially-loaded DOM.
if a CSS selector matches multiple elements and you want a specific one:
then use --nth <n> (zero-indexed) to disambiguate, or narrow the selector. for writes (click, type, select), an ambiguous selector without --nth returns selector_ambiguous error.
if a numeric ref returns not_found or stale_ref:
then the page has mutated or navigated. re-run state to allocate fresh refs. do not reuse old refs.
if a type or select action returns match_level: reidentified:
then the original ref was gone and the CLI re-tagged a similar element. before chaining further writes, verify the action hit the right target with get text or get value. if you chained 5 writes off a reidentified ref, the rest may have gone to the wrong element.
if you need to read a lot of HTML or extract long-form text:
then use get html --as json --depth <n> --children-max <m> or extract --chunk-size <bytes> instead of a giant get html --as html. the defaults are budget-aware; explicitly raising limits signals intentional cost.
if the page fetches data from a JSON API:
then prefer network --filter "<fields>" over scraping the DOM. the API is almost always more reliable and cheaper than parsing rendered HTML.
if you need to interact with a cross-origin iframe:
then run browser frames to list iframes, then pass --frame <index> to eval commands. note that DOM queries within an iframe are restricted to that frame's document; you cannot click elements across the frame boundary.
if a form field has autocomplete enabled:
then the type command returns autocomplete: true. send keys Enter to accept the first suggestion, or click a specific suggestion to commit. re-verify with get value after.
if a select operation fails with option_not_found:
then the error envelope includes available: string[] with the real option labels. either pass a matching label, or if the site wraps the native input in a custom widget, try type instead.
if you are chaining multiple commands and the session drops:
then use && to run them in one shell so refs stay live. separate shell invocations lose session context and invite race conditions.
success case:
click, type, select, keys, scroll commands return {action: true, target?, matches_n, match_level} or similar.get commands return {value, matches_n, match_level} (or {value: {...}, ...} for attributes).state returns a JSON object with {snapshot: {...}, compounds: {...}} structure.find returns a JSON array of {nth, ref, tag, role, text, attrs, visible, compound?} objects.network returns a JSON array of entries: {key, method, status, url, ct, size, shape, body_truncated?}.extract returns {url, title, selector, total_chars, chunk_size, start, end, next_start_char, content}.error case:
{error: {code, message, hint?, candidates?, available?}}.code is one of: not_found, stale_ref, invalid_selector, selector_not_found, selector_ambiguous, selector_nth_out_of_range, option_not_found, not_a_select, or similar.code, never on the message string.candidates (for selector errors) lists suggested selectors.available (for option_not_found) lists valid option labels.data location:
~/.opencli/cache/browser-network/ so you can re-inspect without re-triggering requests.the skill worked if:
opencli doctor returns green.browser open completes without error and the window opens (visible in taskbar or --focus).browser state returns a non-empty snapshot with at least one [N] ref.click, type, select return success envelopes with match_level: exact (or stable / reidentified if DOM drift occurred, but the action still applied).get value returns the expected value (or does not, if the field silently dropped input - triggering further action).wait confirms the new page loaded, then a fresh state is taken with new refs.browser network after a page load.eval expressions return the expected JSON.close ends the session and the window exits.the skill failed if:
opencli doctor shows red for Chrome connection, extension install, or debug port.browser open times out or the window does not open.selector_not_found, selector_ambiguous, not_found, stale_ref, and no recovery (re-state, retry with a narrower selector) succeeds after 2 attempts.match_level: reidentified occurs on a critical action and verification shows the wrong element was targeted.wait times out and the page never loads.network is empty when requests should have been captured (check that network capture was active during the requests).eval expression throws an error or returns unparseable output.