A CLI over real Chrome that replaces the screenshot loop. The model reads a distilled element list instead of a 1,620-token image, issues one-line commands, and once a path works it becomes a typed flow file that never needs a model again.
$ git clone https://github.com/dested/claude-browser$ cd claude-browser$ bun install$ bun linkRequires Chrome and Bun. bun link puts bx on your PATH.
what it looks like
Real commands, real output. Pick one.
Pick a task above — the output is what the CLI actually prints.
the artifacts
The entire interface between the model and the browser is plain text. Here it is.
✓ TaskBox — http://127.0.0.1:57395/fixture
[1] textbox "Email"
[2] textbox "Password"
[3] button "Sign in"This is the model's entire view of the page — no screenshot, no DOM dump. ~42 tokens.
the payoff
One instruction in. A verdict out. The drive happens on a model that costs 1/10th of yours.
in what you type
$ bx agent "log in as demo@taskbox.test, turn on dark mode in Settings, and verify it stuck" --save dark-modeOne instruction. No element refs, no coordinates, no screenshots on your side of the wire.
during what Haiku did (you never see this)
14 turns · 25.9s · stays on Haiku
out what your context receives
PASS — Logged in, enabled dark mode in Settings, verified.
Evidence: expect text "Dark mode on" ✓ · final url #/settings
tier=haiku turns=14 wall=25.9s tokens=31551/948 cost=$0.062The report — roughly 300 tokens. The 14-turn transcript above never enters your session.
import { flow } from "bx/flow";
export default flow("dark-mode", async (b) => {
await b.open("http://127.0.0.1:8000/fixture");
await b.fill("Email", "demo@taskbox.test");
await b.fill("Password", ""); // redacted at record time
await b.click("Sign in");
await b.click("Settings");
await b.click("Dark mode");
await b.expectText("Dark mode on");
});Run it tomorrow: bx run flows/dark-mode.flow.ts — 1.2s, zero model tokens. The agent run is the authoring cost; every run after is free.
measured on a real production app
| task | turns | wall | cost |
|---|---|---|---|
| holidays count | 11 | 26s | $0.025 |
| overview + all-tasks synthesis | 37 | 75s | $0.061 |
| users + weekend schedule | 18 | 23s | $0.034 |
| module types | 11 | 16s | $0.028 |
| tags | 13 | 20s | $0.029 |
| settings profile | 13 | 12s | $0.028 |
| holidays + PTO | 22 | 30s | $0.035 |
7/7 correct, independently verified. 5 agents ran concurrently against one browser — each pinned to its own tab.
Your context pays for the verdict, not the drive.
the race
Log in and toggle a setting on a fixture app. Left is a real Claude in Chrome session. Right is a real bx agent run. Neither timeline has been edited.
The fixture is TaskBox — a ~380-line static demo app that ships in the repo (login → tasks → settings), so the bench is reproducible: fixtures/app/index.html. The screenshots below are bx snap output.
Fable — the main session


#/login
Haiku — a subagent; the main session reads a ~300-token report




#/login
idle — press play
Both timelines are real sessions against the same fixture app, replayed on one clock. Left: the extension, driven by the main session — coordinate clicks, two 30-second CDP timeouts, and a navigation that never landed. Right: bx agent on Haiku, finishing while the left pane is still waiting.
measured
One observation
42tokens
vs 1,620 one screenshot · 2,564 raw DOM dump
bx els — the numbered element list the model actually acts on.
A delegated task
$0.037
Haiku drives end to end. Across seven real tasks on a production app: $0.025–$0.061, 12–75s, 7/7 correct.
A saved flow, replayed
1.2s· 0 tokens
Typed TypeScript, checked by tsc, in version control. Re-running costs no model at all.
Cold start
730ms
Daemon up and Chrome attached. An els scan is 2.3ms, a click 187ms after that.
how it works
A daemon, a set of verbs, and files you can commit.
01
One background process per profile owns a real Chrome instance over playwright-core — persistent user-data-dirs, so you sign in by hand once and the session survives every run. The CLI talks to it over localhost HTTP. Nothing is a bundled headless browser unless you ask for one.
02
open, els, click, fill, expect, console, net. Every read is budgeted at the source — the element list caps around 800 tokens, page text at 2,000, console and network at 30 entries — so output can never blow up a context window, and the model never has to ration a look at the page.
03
A path that works becomes a typed TypeScript file. tsc checks it, git versions it, and re-running it costs zero model tokens — which makes the same artifact a regression test you can drop into CI or a pre-push hook.
agent mode
bx agent takes a natural-language task, runs it on Haiku through the same verbs, and escalates once to Sonnet if the first attempt fails. What comes back to your main session is a ~300-token report — pass/fail, a summary, evidence lines, turns and usage — not a transcript. Add --save and the actions taken are synthesized into a flow file with replay-stable targets.
recordings
bx record start captures Chrome's own video, then distills it into deduped keyframes, 3×3 contact sheets, and a report.md. There is no audio track, so bx's own action log — clicked Save changes, filled Email — becomes the timed transcript, and the report reads as a narrated walkthrough.
the whole prompt surface
Everything above is taught by a single ~130-line skill file — skill/SKILL.md — and it is loaded only when a browser task actually shows up. Compare that to ~24 MCP tool schemas sitting resident in every context you ever open, whether or not the browser is ever touched.
tradeoffs
bx is not a superset. Three cases where it is the wrong choice:
bx drives its own profiles under ~/.bx/profiles. Chrome 136+ blocks CDP on the default user-data-dir and app-bound encryption blocks importing cookies, so the browser you already have open, with all its sessions, stays out of reach. The extension lives inside it.
els returns a flat, numbered list tuned for acting on things. When the question is about structure — nesting, landmarks, what contains what — read_page's tree is genuinely the better read.
bx is built for driving apps you own, where testids and stable accessible names exist and a flow file is worth keeping. For one-off poking at a site you have never seen, the screenshot loop needs no setup and no profile.