Skip to main content

brow - agentic browsing

Standalone Playwright CLI for agent browser automation. Launches a real Chromium instance with an agent-friendly API - structured commands for common actions, eval escape hatch for full power.

Documentation →

Install

Homebrew:

brew tap detrin/tap
brew install brow

pip:

pip install brow-cli

Then install Chromium once (either method above):

brow setup            # ~150MB, one-time

Update patchright and its matching Chromium build later with:

brow setup --upgrade

Agent skill:

npx -y skills add detrin/brow

Example: Find Bars Near Times Square with Google Maps

A real use case: use your Google account to search Maps in a city you've never visited, and extract structured results.

Step 1: Log into Google (once)

Open a headed browser with a persistent profile and sign in manually:

brow session new --profile personal --headed
brow navigate -s 1 "https://accounts.google.com"
# Sign in manually in the browser window...
brow session delete 1

Your login is saved in ~/.brow/profiles/personal/ -you won't need to sign in again.

Paste this into Claude Code:

Open a brow session with my personal profile, go to Google Maps, and search for bars near Times Square in New York. Return the names, Google Maps URLs, ratings, and number of reviews in a markdown table.

Claude Code runs:

brow session new --profile personal --headed    # → 1 (already logged in)
brow navigate -s 1 "https://www.google.com/maps/search/bars+near+Times+Square+New+York"
brow screenshot -s 1
brow eval -s 1 "
results = await page.evaluate('''() => {
    const items = document.querySelectorAll('div.Nv2PK');
    return Array.from(items).slice(0, 8).map(el => {
        const name = el.querySelector('.fontHeadlineSmall, .qBF1Pd');
        const rating = el.querySelector('.MW4etd');
        const reviews = el.querySelector('.UY7F9');
        const link = el.querySelector('a[href*=\"/maps/place\"]');
        return {
            name: name?.innerText || '',
            rating: rating?.innerText || '',
            reviews: reviews?.innerText.replace(/[()]/g, '') || '',
            url: link?.href || ''
        };
    });
}''')
import json
result = json.dumps(results, indent=2)
"
brow session delete 1

Result

Google Maps search results for bars near Times Square

Bar Rating Reviews Link
The Riff Raff Club 4.4 60 Maps
Ascent Lounge 4.4 646 Maps
Jimmy's Corner 4.6 2,195 Maps
O'Donoghue's Times Square 4.4 2,633 Maps
The Dickens 4.8 2,128 Maps
The Woo Woo 4.8 1,871 Maps

Because the google profile persists your login, you get personalized results -no cookie banners, no sign-in walls, just data.

Commands

Daemon

brow daemon start [--port 19987]
brow daemon stop
brow daemon status

Sessions

brow session new [--profile <name>] [--headed]
brow session list
brow session delete <id>

Navigation

brow -s <id> navigate <url>
brow -s <id> wait <selector>
brow -s <id> wait --load

Observation

brow -s <id> snapshot [--search <regex>] [--locator <selector>]
brow -s <id> screenshot [--full] [--path <file>]
brow -s <id> html [--locator <selector>] [--search <regex>]
brow -s <id> logs [--search <regex>] [--count <n>]
brow -s <id> url

Interaction

brow -s <id> click <selector>
brow -s <id> fill <selector> <value>
brow -s <id> type <text>
brow -s <id> key <key>            # Enter, Tab, Meta+a
brow -s <id> hover <selector>
brow -s <id> scroll <pixels>
brow -s <id> scroll-to <selector>
brow -s <id> drag <from> <to>
brow -s <id> upload <selector> <filepath>

Pages

brow -s <id> page list
brow -s <id> page new [url]
brow -s <id> page close [index]
brow -s <id> page switch <index>

Profiles & State

brow profile list
brow profile delete <name>
brow state save <name> -s <id>
brow state restore <name> -s <id>
brow state list

Eval & Run

brow eval -s <id> <code>              # inline, one-off
brow run -s <id> workflow.py --arg k=v  # reusable, from a file

Variables available: page, context, browser, state, pages, plus args (from --arg) for run.

Selectors

Playwright selector syntax:

  • CSS: button.submit, #login
  • Text: text=Login
  • Role: role=button[name="Save"]
  • XPath: xpath=//div

Architecture

  ┌─────────────────────────────────────────────────────────────────┐
  │  Agent (Claude Code, script, etc.)                              │
  │                                                                 │
  │  brow session new --headed          ← start browser             │
  │  brow navigate -s 1 "https://..."   ← go to page               │
  │  brow snapshot -s 1                 ← read page (a11y tree)     │
  │  brow click -s 1 "text=Login"       ← interact                  │
  │  brow fill -s 1 "#email" "me@..."   ← fill form                 │
  │  brow screenshot -s 1               ← capture screen            │
  │  brow eval -s 1 "await page..."     ← escape hatch              │
  │  brow session delete 1              ← cleanup                   │
  └──────────────┬──────────────────────────────────────────────────┘
                 │ HTTP (localhost:19987)
                 ▼
  ┌──────────────────────────────────────┐
  │  brow daemon (FastAPI + uvicorn)     │
  │                                      │
  │  ┌──────────┐  ┌──────────────────┐  │
  │  │ Session 1 │  │ ProfileManager   │  │
  │  │ (browser) │  │ ~/.brow/profiles │  │
  │  ├──────────┤  └──────────────────┘  │
  │  │ Session 2 │                       │
  │  │ (browser) │  ┌──────────────────┐  │
  │  └──────────┘  │ StateManager     │  │
  │                 │ ~/.brow/states   │  │
  │                 └──────────────────┘  │
  └──────────────┬───────────────────────┘
                 │ CDP (Chrome DevTools Protocol)
                 ▼
  ┌──────────────────────────────────────┐
  │  Chromium (via Playwright)           │
  │                                      │
  │  ┌────────┐ ┌────────┐ ┌────────┐   │
  │  │ Page 1 │ │ Page 2 │ │ Page 3 │   │
  │  └────────┘ └────────┘ └────────┘   │
  └──────────────────────────────────────┘
  • Daemon auto-starts on first brow command
  • Persistent Chromium profiles for login session survival
  • One browser per session, full isolation
  • Headless by default, --headed to watch

Configuration

Variable Default Description
BROW_HOME ~/.brow Data directory
BROW_PORT 19987 Daemon port
BROW_MAX_SESSIONS 10 Max concurrent sessions

Benchmarks

16 fixture tasks, Claude Sonnet via AWS Bedrock, 1 run each. Compared against playwright-cli, MCP Playwright, agent-browser, and browser-use.

Metric brow agent-browser browser-use playwright-cli MCP Playwright
Success rate (16 fixture) 88% (14/16) 63% (10/16) 63% (10/16) 50% (8/16) 44% (7/16)
Success rate (22 total) 82% (18/22) 64% (14/22) 64% (14/22) 55% (12/22) 36% (8/22)
Avg tokens/task (16 fixture) 68K 73K 75K 113K 118K
Avg tokens/task (22 total) 88K 69K 81K 96K 132K
Avg tool calls 9.6 11.2 5.8 9.6 11.6
Avg wall-clock 41s 36s 73s 44s 50s
Est. cost/task $0.22 $0.23 $0.27 $0.35 $0.37

brow leads on success rate across both suites. On token efficiency, brow leads the 16-task fixture suite (68K avg); agent-browser leads across all 22 tasks (69K avg) — brow's 22-task average is inflated by a single live task where the agent didn't use snapshot filtering. agent-browser wins on wall-clock speed. browser-use runs its own agent loop — included for completeness.

Full results and per-task breakdown →

Resource Usage

~150-300MB per Chromium instance. 10 sessions = ~2-3GB.

License

Elastic License 2.0 (releases <=1.2.0 remain MIT-licensed).

Metadata

Release files for brow-cli 1.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for brow-cli 1.3.0
File Size Uploaded Provenance Actions
brow_cli-1.3.0.tar.gz 38.7 kB With provenance Details for brow_cli-1.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for brow-cli 1.3.0
File Interpreter ABI Platform Actions
brow_cli-1.3.0-py3-none-any.whl Python 3 none any Details for brow_cli-1.3.0-py3-none-any.whl

Total release size: 69.9 kB

Release files / brow_cli-1.3.0.tar.gz

Download URL brow_cli-1.3.0.tar.gz
Size 38.7 kB
Tags Source
SHA-256 checksum
How to use checksums
dff9a948e81f53969d0d72ed4ae6a2f68c6ade6974265203a0110444fbf10169
BLAKE2b-256 checksum
How to use checksums
28bd331feeb82bb5c30b18b9afffa2b32f3ebc303a4b39f7d66a95bee1e92314
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 15, 2026.

Transparency log

Release files / brow_cli-1.3.0-py3-none-any.whl

Download URL brow_cli-1.3.0-py3-none-any.whl
Size 31.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bf2615069e7c7e077770c8686e1ff11be78fd58a47ef6234fa00d0b3a3fa8f77
BLAKE2b-256 checksum
How to use checksums
0eefb2285eed874ccf75b26614a57f473fbfb34bff37128225c808af6d6fb4e7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 15, 2026.

Transparency log

Security for brow-cli 1.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for brow_cli-1.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 15, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page