Browser
Drive a managed Chrome session via CDP. Navigate, click, fill forms, read the DOM, take screenshots, run JS.
SKILL.mdskills/browser/SKILL.md
GitHubBrowser
Controls a real Chrome/Chromium instance managed by the agent process. Use for tasks that require reading or interacting with a live web page: research, scraping structured data, filling forms, downloading files, grabbing a visual reference. Every tool call maps 1:1 to a Chrome DevTools Protocol command, so actions are immediate — no sandbox.
Use when
- "Open example.com and tell me what the homepage says"
- "Log in to this page and grab my account balance"
- "Screenshot the dashboard for me"
- "Find the cheapest flight on this comparison site"
- "Click the Accept cookies button before you read the page"
- "Fill this form with my saved details and submit"
Do not use when
- You can answer with a simple HTTP GET — use
fetch-urlskill instead. The browser is heavy; spinning it up for plain JSON is wasteful. - Sites that ship a proper API (GitHub, Gmail, Slack). Use the matching
skill (
github,google, etc.) which talks the API directly. - Anti-bot protected sites (Cloudflare JS challenge, hCaptcha). The browser can technically get past some, but you're one bot-detection update from breaking. Ask the user first.
Tools exposed
| Tool | When to call |
|---|---|
browser_navigate {url} |
Go to an absolute URL. Wait for load. Call first on any new page. |
browser_snapshot |
Text representation of the DOM with element refs (@e1, @e2…). Primary way to "see" a page. Refresh refs by calling again after any navigate/click. |
browser_screenshot |
Base64 PNG of the viewport. For vision-capable models, or when layout matters. Don't call blindly — snapshot is cheaper. |
browser_click {target} |
Click an element. target is an @eN from the latest snapshot or a CSS selector. Prefer @eN. |
browser_fill {target, value} |
Replace the contents of an input / textarea. value is literal — special keys don't work here. |
browser_press_key {key} |
Dispatch a keyboard event on the focused element. Names: Enter, Tab, Escape, Arrow{Up,Down,Left,Right}. Anything else is treated as a literal char. |
browser_scroll_to {target} |
Scroll an element into view. Do this before clicking items the snapshot flagged as below the fold. |
browser_wait_for {selector, timeout_ms?} |
Poll until a CSS selector appears. Use between browser_click and the next browser_snapshot when the page triggers an XHR/SPA transition. |
browser_evaluate {script} |
Run arbitrary JS. Escape hatch for anything the other tools can't express. Returned value is in result. |
browser_current_url |
Shortcut for browser_evaluate{ 'location.href' }. |
browser_go_back / browser_go_forward |
Step through the history stack. |
Canonical workflow
1. browser_navigate({url: "https://example.com"})
2. browser_snapshot() ← read the page, collect refs
3. browser_fill({target: "@e12", value: "user@example.com"})
4. browser_fill({target: "@e13", value: "s3cret"})
5. browser_click({target: "@e14"}) ← submit
6. browser_wait_for({selector: ".dashboard"})
7. browser_snapshot() ← confirm logged in, collect new refs
Element refs (@eN) are only valid inside the snapshot that produced
them — as soon as the DOM changes, re-call browser_snapshot before
referencing more refs. CSS selectors stay valid across snapshots, but
the LLM usually gets better accuracy with refs.
Failure modes
{ok: false, error: "navigate timed out"}— page didn't fireloadwithin the timeout. Retry once with a longer implicit timeout via evaluate, or check if the URL is wrong.{ok: false, error: "element not found"}— the@eNis stale or the selector doesn't match. Callbrowser_snapshotagain.{ok: false, error: "circuit breaker ... open"}— the plugin is cooling off after repeated failures. Wait a few seconds or ask the user to retry.
Gotchas
- The browser is shared across agents that have
plugins: [browser]— actions interleave. For side-effect-free reads it's fine; for stateful sessions (logins), give each agent its ownuser_data_dirinconfig/plugins/browser.yaml. - Screenshots are PNG base64, usually 100–500 KB. Don't pipe them
through
browser_evaluate— hit the tool directly. browser_evaluatereturns the value of the last expression. Wrap complex objects inJSON.stringify(...)if you need the full shape.- Headless mode is opt-in via
browser.headless: true. With a visible window the user sees exactly what you're doing — useful for trust, expensive in CPU.