bx

Claude in Chrome burns tokens, minutes, and sometimes the task. bx replaces it.

The screenshot loop sends your frontier model a ~1,600-token image to find one button, round-trips every click through it, and still loses the thread. bx is a CLI over real Chrome built for exactly one user: a coding agent. Distilled text instead of pixels, hard budgets at the source, a cheap model doing the driving — and a path that works becomes a typed flow that never needs a model again.

install
$ bun add -g bx-browser$ bx install-skill

Same two lines on macOS, Linux, and Windows. Requires Bun and Chrome. The second line drops the Claude Code skill into ~/.claude/skills/bx — then tell Claude Code: “use bx to verify your changes.”

Runs on your Claude Code subscription. Delegated drives go through the Agent SDK with the same auth Claude Code itself uses — Haiku by default, escalating to Sonnet, Opus if you allow it. Signed in with a Max plan, that's your subscription: no API key, no separate bill. What it saves you is time, context, and wasted runs.

One honest caveat: if ANTHROPIC_API_KEY is set in your environment, Claude Code — and therefore bx — bills the API instead. That's how Claude Code's auth resolution works, not a bx decision; unset the variable and you're back on the subscription.

how you use it

You never type a bx command. You just talk.

Install the skill once and Claude Code reaches for bx on its own whenever a browser task shows up. Real prompts, and the commands they turn into:

you say

“Use bx to verify your changes.”

claude code runs

$ bx open https://myapp.localhost$ bx expect text "Saved"$ bx console$ bx net --failed

The default verification trio: an assertion, console errors, failed requests. expect exits 1 on failure — the agent gets a hard signal, not a screenshot to squint at.

you say

“Test the login flow with bx and keep it as a regression.”

claude code runs

$ bx agent "log in as demo@taskbox.test  and verify the task list loads" \  --save login

Haiku drives on your Claude Code subscription — escalating to Sonnet (and Opus, if you allow it) only when it has to. Your session gets a ~300-token report, and flows/login.flow.ts replays forever at zero model tokens.

you say

“Fill out the signup form and tell me what the error toast says.”

claude code runs

$ bx els --filter signup$ bx fill "Email" "qa@test.dev"$ bx click "Create account"$ bx text ".toast"

Hand-driving, for when each intermediate state matters. Targets are text or selectors, every read is budgeted, and nothing here costs an image.

you say

“Record a walkthrough of the checkout bug for the PR.”

claude code runs

$ bx record start checkout-bug$ bx run flows/checkout.flow.ts --record$ bx record stop

Produces recordings/checkout-bug/report.md — deduped keyframes, contact sheets, narrated by bx's own action log. Drop it straight into the PR.

the entire integration

One markdown file teaches Claude Code all of it

bx install-skill writes a ~130-line skill file to ~/.claude/skills/bx. It covers every verb and, more importantly, when to reach for them: "verify in the browser", "check the console", "test the game", any local dev URL. It loads 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 touched.

side by side

The same task, two drivers, one clock

This is why bx exists: nobody wants the screenshot loop, and there is nothing the extension can do about it. 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.

t = 0.0s

Claude in Chrome

0 tok

Fable — the main session

TaskBox login screenTaskBox tasks screen

#/login

$ tabs_context()
running — 0.0s

bx agent

0 tok

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

TaskBox login screenTaskBox tasks screenTaskBox settings screenTaskBox settings dark screen

#/login

idle — press play

running — 0.0s

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

Where the time and tokens actually go

Most people run this on a Claude Code subscription, so the spend that matters isn't dollars — it's minutes, context, and wasted runs.

Same task, one clock

seconds to a verified result

Claude in ChromeFAILED3.2 min
bx agentPASS25.9s

Log in and toggle a setting — the race above, unedited. The extension spent 3.2 minutes and never landed the navigation.

One look at the page

tokens per observation

read_page dump2,564
screenshot1,620
bx els42

bx els is the numbered element list the model acts on — budgeted at the source, so a look at the page is never a context event.

What your session absorbs

tokens into your main context, same task

Claude in Chrome~8,000
bx report~300

Every screenshot and retry the extension takes lands in your context and stays there. bx hands back a verdict; the drive stays on Haiku.

  • 730mscold start — daemon up, Chrome attached
  • 1.2s · 0 tokensa saved flow, replayed
  • more interactive elements surfaced than the extension's tools
  • ~18×more page text per read
  • 5agents driving one browser concurrently

under the hood

One instruction in. A verdict out.

What actually happens when Claude Code hands a browser task to bx agent — every artifact below is real captured output.

in what claude code runs — you never see this either

$ bx agent "log in as demo@taskbox.test, turn on dark mode in Settings, and verify it stuck" --save dark-mode

One instruction, phrased by Claude Code from whatever you said. No element refs, no coordinates, no screenshots on your side of the wire.

what haiku reads each turn

✓ TaskBox — http://127.0.0.1:57395/fixture

[1] textbox "Email"
[2] textbox "Password"
[3] button "Sign in"

Its entire view of the page — ~42 tokens. No screenshot on either side of the wire.

during what Haiku did — scroll it, every turn is here

I'll help you with this task. Let me start by opening the URL and then proceed with loggin…
$ bx open http://127.0.0.1:62747/fixture
TaskBox — http://127.0.0.1:62747/fixture
Good! I'm on the login page. Now I'll log in with the provided credentials.
$ bx fill 1 demo@taskbox.test
$ bx fill 2 hunter2
ok — filled 1 with "demo@taskbox.test"
ok — filled 2 with "hunter2"
Now I'll click the Sign in button.
$ bx click 3
ok — clicked 3
Good! I've logged in successfully. Now I need to go to Settings. Let me see what elements …
$ bx els
[1] link "Tasks"
Perfect! I can see the Settings link. Let me click on it.
$ bx click 2
ok — clicked 2
Great! I'm now on the Settings page. Let me check what elements are available to find the …
$ bx els
[1] link "Tasks"
Excellent! I can see the Dark mode checkbox. Let me click on it to enable dark mode.
$ bx click 4
ok — clicked 4
Now I need to save the changes. Let me click the Save button.
$ bx click 5
ok — clicked 5
Let me verify that the dark mode has been enabled by checking the page text for "Dark mode…
$ bx expect text Dark mode on
PASS: text "Dark mode on" is visible
Perfect! Let me take a screenshot to confirm the state and then report success.
$ bx snap
screenshot saved: C:\Users\dested\.bx\snaps\20260806-182200.png
Excellent! I have successfully completed all the requested tasks:
$ bx report pass I opened the TaskBox login page at http://127.0.0.1:627…
report recorded — you are done, stop now.
Task completed successfully! Here's what I did:

14 turns · 25.9s · stays on Haiku · on your Claude Code subscription

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

The report — roughly 300 tokens. The 14-turn transcript on the right never enters your session.

// synthesized by bx agent — flows/dark-mode.flow.ts
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");
});

This is a flow — a typed TypeScript file Haiku wrote from the actions that actually worked. tsc checks it, git versions it, and bx run flows/dark-mode.flow.ts replays it tomorrow in 1.2s with zero model involvement. The agent run is the authoring cost; every run after is a free regression test.

measured on a real production app

taskturnswall
holidays count1126s
overview + all-tasks synthesis3775s
users + weekend schedule1823s
module types1116s
tags1320s
settings profile1312s
holidays + PTO2230s

7/7 correct, independently verified. 5 agents ran concurrently against one browser — each pinned to its own tab. On a Claude Code subscription the spend that matters is the one above: turns and seconds, not dollars.

Your context pays for the verdict, not the drive.

how it works

Three moving parts

A daemon, a set of verbs, and files you can commit.

01

A daemon holds your real Chrome

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

Verbs act on distilled text

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

Flows replay for free

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.

real-time mode

For 60fps canvases, the model ships code into the page

A read→decide→click loop is seconds per decision; a game runs at 60fps. So bx drive installs an agent-authored controller that runs inside the page every frame, while the daemon polls a win condition — zero model tokens while it plays. Games, drag physics, anything the DOM can't describe.

recordings

Video becomes something a model can read

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.

tradeoffs

When the extension is still the right tool

bx is not a superset. Three cases where it is the wrong choice:

  • Your own logged-in daily browser

    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. (A bridge extension into your daily Chrome is the v2 roadmap.)

  • Hierarchical accessibility-tree reads

    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.

  • Arbitrary third-party sites

    bx is built for driving apps you're actually working on — a dev server, a real project, a flow file worth keeping. For one-off poking at a site you have never seen, the screenshot loop needs no setup and no profile.