| name | browser |
| description | Control a headless Chromium browser — navigate pages, read content via accessibility snapshots, interact with elements, fill forms, take screenshots |
Browser Control
Headless Chromium browser via CLI for web automation.
Setup
Check if the server is running, start if needed:
browser status
browser start
If the server process isn't running at all:
tsx /foo/bar/lib/browser/server.ts &
CLI Reference
browser is shorthand — actual path is in the Tool Paths section of the assistant config.
Navigation
browser open <url>
browser navigate <url>
browser tabs
browser close [targetId]
Reading Pages
browser snapshot --efficient
browser snapshot --selector "main"
browser evaluate "document.title"
browser screenshot
browser console
Choose the right tool:
snapshot --efficient — default for most pages. Text-based, cheap (~3-5K tokens).
snapshot --selector "main" — large pages where nav/sidebar bloats the snapshot.
evaluate — structured data extraction. Returns JSON. Best for scraping lists, tables, feeds.
screenshot — visual layouts, images, CAPTCHAs. Requires vision. Use as fallback.
Interacting with Elements
browser click e5
browser type e5 Hello world
browser press Enter
browser fill e3=John e4=john@x.com
browser select e7 option1
Snapshots
The snapshot command returns an accessibility tree with element refs:
[url: https://news.ycombinator.com]
- heading "Hacker News" [level=1]
- link "Show HN: Something cool" [ref=e5]
- text "142 points by user 3 hours ago"
- link "89 comments" [ref=e6]
Refs like e5 are used in click/type/fill. They change between snapshots — always re-snapshot after acting.
Workflow
- Snapshot → see page state
- Act (click/type/fill) using refs
- Snapshot again → verify result
Structured Data Extraction
For scraping lists, tables, or feeds, use evaluate with JS instead of parsing snapshots:
browser evaluate "JSON.stringify(Array.from(document.querySelectorAll('.item')).map(el => ({title: el.querySelector('a')?.textContent, url: el.querySelector('a')?.href})))"
This is faster, cheaper, and more reliable than parsing accessibility trees for bulk data.
Reddit (old.reddit.com)
browser open https://old.reddit.com/r/<subreddit>/
browser evaluate "JSON.stringify(Array.from(document.querySelectorAll('#siteTable .thing.link')).map(t => ({title: t.querySelector('a.title')?.textContent, score: t.querySelector('.score.unvoted')?.textContent, comments: t.querySelector('.comments')?.textContent, time: t.querySelector('time')?.getAttribute('title'), flair: t.querySelector('.linkflairlabel')?.textContent, url: t.querySelector('a.title')?.href})))"
Always use old.reddit.com — simpler HTML, no infinite scroll, works with evaluate. Rate limit: 30s between subreddits.
Login-Required Sites
The browser profile persists (cookies survive restarts). To log in:
- Navigate to login page
- Snapshot → find form refs
- Fill credentials (from secrets, never hardcode)
- Submit → snapshot to verify
Troubleshooting
- Connection refused: Server isn't running. Start the browser server (see "Starting the Server" above).
- Timeout on start: Stale Chrome process holding the user-data lock. Kill it:
pkill -f 'chrome.*browser-control' then retry.
- Snapshot too large: Use
--efficient or --selector "main" to scope down.
- Reddit blocked: IP rate-limited. Wait 30s+ between requests. Log in for higher limits.