br turns a web page into a compact, semantic snapshot with stable @refs, then lets you (or an agent) act on those refs. No Playwright, no Selenium, no huge DOM dumps. Just a tiny command surface built for minimal context.
$ br open https://example.com
$ br snap --compact
Example Domain | https://example.com/
@1 link More information...That's the whole model:
browser → snapshot → @refs → actions
Live website(yeswehack.com) rendering inside terminal
Screendrop_2026-08-28-16-59-38_46FD44.mp4
Open, snapshot, click, capture:
demo-open-snapshot-click.mp4
Live page snapshot:
demo-live-snapshot.mp4
Form automation:
demo-form-automation.mp4
Batch mode:
demo-batch-mode.mp4
- Small by design. Not a terminal browser or a full automation framework, a thin agent interface. Zig owns the CLI and protocol; Bun's WebView owns the browser.
- Stable
@refs. Every interactive element gets a short, reusable handle. If one goes stale,brfails loudly withSTALE_REFinstead of clicking the wrong thing. - Batch-native. Multi-step flows run as JSONL in one shot, so agents do more with less back-and-forth.
- Visual when you want it.
br viewandbr liverender the real page inline via the Kitty graphics protocol.
If it renders in a browser,
brturns it into commands: same page, whether you're an agent, a human, or hunting bugs.
🤖 As an AI agent: a browser without the DOM firehose
Give your agent a browser without drowning it in DOM. It reads a compact snapshot, acts on @refs, and branches on exit codes. No Playwright, no flaky selectors, no 50k-token HTML dumps.
br batch <<'JSONL'
{"command":"open","url":"https://app.example.com/login"}
{"command":"snapshot","compact":true}
{"command":"fill","target":"@2","text":"user@example.com"}
{"command":"fill","target":"@3","text":"hunter2"}
{"command":"click","target":"@4"}
{"command":"snapshot","compact":true}
JSONLOne JSON line in, one out. Stable handles, deterministic exit codes (STALE_REF, TIMEOUT, …), and --json on everything: everything an agent needs to loop reliably.
🧑‍💻 As a human: a browser you can pipe
Inspect a page, fill a form, grab a screenshot, or read it inline, without leaving the terminal or writing a script.
br open https://news.ycombinator.com
br snap --compact # scan the interactive elements
br find "login" # locate a control by text
br screenshot shot.png # grab evidence
br view # render the viewport inline (Kitty graphics)
br live https://github.com # full interactive terminal browserGreat for quick checks, demos, scraping one page, or driving a site from a shell script.
🛡️ For bug bounty & recon: scriptable, pipeable triage
A scriptable, headful browser is a fast triage tool: map inputs, read client-side state, and run JS in page context, all pipeable into your recon pipeline. On targets you're authorized to test.
br --profile target open https://app.example.com/account
br snap # every interactive element + its attrs
br eval 'document.cookie' # inspect session/CSRF state
br eval '[...document.querySelectorAll("input[type=hidden]")].map(i => [i.name, i.value])'
br attr @5 href # pull hrefs, tokens, data-* attrs
br cookies # dump cookies
br console # surface client-side JS errors
br --backend chrome cdp Network.enable # Chrome DevTools Protocol (Chromium only)
br screenshot finding.png # capture proof for the reportKeep separate authenticated contexts with --profile, script repeatable checks across params/endpoints with batch, and drive it all from the same tools as the rest of your recon.
cdpneeds the Chromium backend. Pick it per command with--backend chrome(the defaultwebkitbackend has no CDP). See Backends.
Grab the tarball for your platform from Releases. Each one bundles br, a matching Bun, and the worker/ scripts, so it runs on a fresh machine with nothing else to install. Untar and run: br finds its Bun and worker beside itself.
tar xzf br-<version>-<target>.tar.gz # e.g. br-0.1.0-aarch64-macos.tar.gz
./br-<version>-<target>/br open https://example.comImportant
macOS: the binary is ad-hoc signed but not notarized, so Gatekeeper blocks the first run ("Apple could not verify 'br' is free of malware…"). Clear the download quarantine once and it runs normally:
xattr -dr com.apple.quarantine ./br-<version>-<target>
# e.g. xattr -dr com.apple.quarantine ./br-0.1.0-aarch64-macosYou'll need Zig 0.16+ and Bun 1.4+ (with Bun.WebView: WebKit by default, Chromium via --backend chrome).
# with Nix (recommended)
nix develop
zig build
export BR_BUN="$(command -v bun)"
./zig-out/bin/br open https://example.com
# or build the packaged version
nix build
./result/bin/br open https://example.comTip
Using a downloaded Bun? Point br at it with export BR_BUN=/path/to/bun.
For packaged installs, set BR_WORKER_DIR to the installed worker/ directory.
Log in to a page in five commands:
br open https://example.com/login
br snap # list the interactive elements + their @refs
br fill @2 "user@example.com"
br fill @3 "$PASSWORD"
br click @4
br snap --compact # see where you landedImportant
@refs only exist after a snap. If a page changes under you, an old ref
goes stale, br fails loudly instead of clicking the wrong thing. Just
snapshot again and use the fresh ref:
STALE_REF @4
For anything multi-step, prefer batch: one JSON object in per line, one out. It never runs shell commands.
br batch <<'JSONL'
{"command":"open","url":"https://example.com"}
{"command":"snapshot","compact":true}
{"command":"click","target":"@1"}
{"command":"snapshot","compact":true}
JSONLUse record when a human should teach br a workflow once, then replay it later from an agent, script, or shell.
br record yeswehack-login https://yeswehack.com/login --pane
br recipes
br show yeswehack-login
br replay yeswehack-login --pause-on-secret --pause-on-fail
br patch yeswehack-login
br export yeswehack-login --jsonl
br recipes delete yeswehack-login
br recipes delete --allRecipes are stored locally as JSONL in ~/.local/share/br/recipes. A recorded live session captures opens, clicks, typed input, key presses, and scrolls exactly as entered, including login values. Add --pane to record or patch to show the live JSONL log inside the terminal while browsing. Clicks include a selector plus coordinate fallback so replay can use the DOM when possible and still preserve the original gesture.
| Command | What it does |
|---|---|
open <url> |
Navigate to a page |
snap [--compact] |
Semantic snapshot with @refs |
click <ref|selector> |
Click an element |
fill <ref> <text> |
Focus, clear, and type into a field |
type <text> / press <key> |
Send keystrokes |
find <text> |
Search the current refs |
get <ref> |
Inspect a single element |
screenshot [path] |
Save a PNG |
view |
Render the viewport inline (Kitty graphics) |
live [url] |
Interactive terminal browser (for humans) |
record <name> [url] |
Record a live workflow as a local recipe |
replay <name> |
Replay a saved recipe |
recipes / show <name> |
List or inspect saved recipes |
recipes delete <name> |
Delete one saved recipe |
recipes delete --all |
Delete all saved recipes |
recipes clear --yes |
Delete all saved recipes |
patch <name> [url] |
Append repaired live actions to a recipe |
export <name> --jsonl |
Print a recipe for agents or scripts |
Tip
In br live, press a to AI-answer the current page: multiple choice
and free text. br collects the clickable options and the writable fields,
hands them plus the page prose to a solver command, then clicks the right
options and writes the essay/short answers it gets back. The solver is
$BR_SOLVER (default claude -p); it receives the page on stdin and returns a
JSON plan {"clicks":["@2"],"fills":[{"ref":"#0","text":"..."}]}. Tune it with
BR_SOLVER="claude -p --model haiku" or BR_SOLVER_TIMEOUT=60.
Full command reference
open <url>
snap | snapshot [--compact]
click <ref|selector> fill <ref|selector> <text>
type <text> press <key> hover <ref|selector>
text [ref|selector] html [ref|selector] get <ref|selector>
attr <ref|selector> <attr> value <ref|selector> find <text>
scroll <amount> scroll-to <ref|selector>
url title back | forward | reload
wait <selector|ms> eval <javascript>
screenshot [path] [--format png|jpeg|webp] [--quality 0-100]
view resize <width> <height> cookies console
close
# recipes
record <name> [url] [--refresh ms] [--pane]
replay <name> [--pause-on-secret] [--pause-on-fail]
recipes
recipes delete <name>
recipes delete --all
recipes clear --yes
show <name>
patch <name> [url] [--refresh ms] [--pane]
export <name> --jsonl
# admin
session list | close <name> | close-all
daemon status | stop
Every meaningful command supports --json (stdout is pure JSON; diagnostics go to stderr).
The default session is default. Name others to keep separate browser contexts:
br --session github open https://github.com
br --session github snap
br session list
br session close githubProfiles persist browser state across runs (stored under ~/.local/share/br/profiles/):
br --profile github open https://github.comSession and profile names are limited to [A-Za-z0-9_.-].
Bun.WebView can drive more than one engine. Pick one per command with --backend:
br --backend chrome open https://example.com # Chromium
br --backend webkit open https://example.com # WebKit (default)| Backend | Notes |
|---|---|
webkit |
Default. Available everywhere Bun.WebView is (macOS WebKit today). |
chrome |
Chromium engine, required for cdp (Chrome DevTools Protocol). |
The backend is fixed when a session's browser is first created, so set it on your first command in that session (e.g. the open).
agent / human → br CLI (Zig) → JSONL over a Unix socket → Bun worker → Bun.WebView
br starts a persistent Bun worker automatically and talks to it over a Unix socket under $XDG_RUNTIME_DIR/br/. See .github/ARCHITECTURE.md and .github/AGENTS.md for the details.
Exit codes
| Code | Meaning | Code | Meaning | |
|---|---|---|---|---|
0 |
success | 13 |
stale ref | |
2 |
invalid arguments | 14 |
timeout | |
10 |
browser unavailable | 15 |
evaluation failed | |
11 |
navigation failed | 16 |
protocol error | |
12 |
element not found | 70 |
internal error |
Experimental and early, but usable for local work.
Warning
The browser backend targets Bun's experimental Bun.WebView API, so behavior
can shift as Bun evolves. br live is a human debugging mode, not agent context.
br is Zig (CLI + protocol) plus a Bun worker (the browser). A justfile
wraps the common tasks. Run just with no args to list them:
just recipes
| Recipe | What it does |
|---|---|
just build |
Release binary → zig-out/bin/br |
just debug |
Debug build |
just run <args> |
Run br, e.g. just run open github.com |
just test |
Run the test suite |
just fmt |
Format Zig + TypeScript |
just fmt-check |
Check formatting (what CI runs) |
just check |
fmt-check + build + test |
just clean |
Remove build artifacts |
Note
All paths need Zig 0.16+ and Bun 1.4+. Pick whichever setup fits you.
With Nix + just: recommended, zero manual installs
nix develop # drops you in a shell with Zig + a repo-local Bun on PATH
just # list recipes
just check # fmt-check, build, test
just run open github.comThe devShell auto-adds a repo-local Bun from .tools/<platform>/ to PATH if
one is present; otherwise point BR_BUN at your Bun.
With just, without Nix: you bring Zig + Bun
Install the tools yourself, then let just drive the rest:
# just: https://github.com/casey/just
# zig 0.16+: https://ziglang.org/download
# bun 1.4+:
curl -fsSL https://bun.sh/install | bash
export BR_BUN="$(command -v bun)"
just check
just run open github.comRaw: no just, download Bun manually
Just Zig and a Bun binary; call the underlying commands directly:
# 1. Bun 1.4+ (any location works, just tell br where it is)
curl -fsSL https://bun.sh/install | bash
export BR_BUN="$HOME/.bun/bin/bun"
# 2. build / test / format
zig build -Doptimize=ReleaseSafe
zig build test
zig fmt src build.zig
"$BR_BUN" x prettier --write "worker/**/*.ts"
# 3. run
./zig-out/bin/br open https://example.comCopyright © 2026 - present pwnwriter