YAML-DRIVEN BROWSER AUTOMATION   /   BREW INSTALL CRACKFETCH/TAP/BRZ   /   STEALTH-HARDENED   /   JSON OUTPUT   /   BUILT FOR LLM AGENTS   /   ZERO DEPENDENCIES   /   SESSION PERSISTENCE   /   12 STEP TYPES   /   9 EVAL ASSERTIONS   /   SEMANTIC EXIT CODES   /   PUBLIC GO API   /   YAML-DRIVEN BROWSER AUTOMATION   /   BREW INSTALL CRACKFETCH/TAP/BRZ   /   STEALTH-HARDENED   /   JSON OUTPUT   /   BUILT FOR LLM AGENTS   /   ZERO DEPENDENCIES   /   SESSION PERSISTENCE   /   12 STEP TYPES   /   9 EVAL ASSERTIONS   /   SEMANTIC EXIT CODES   /   PUBLIC GO API   /  
> MEMORY CHECK ... 64MB OK
> LOADING CHROME DRIVER ... FOUND
> STEALTH MODULE ... INJECTED
> SYSTEM.INIT // BRZ V0.9.1 // STATUS: ONLINE

Brainstorm: browser automation
for the terminal.

Write YAML. brz clicks the buttons.
One Go binary. Stealth baked in.
Works alone or inside your LLM agent.

$ brew install crackfetch/tap/brz Copy
$ go install github.com/crackfetch/brz@latest Copy
Terminal
# Discover what's on any page
$ brz inspect https://app.example.com/login --compact
{
"ok": true,
"url": "https://app.example.com/login",
"elements": [
{ "selector": "input#email", "tag": "input", "type": "email" },
{ "selector": "input#password", "tag": "input", "type": "password" },
{ "selector": "button.submit", "tag": "button", "text": "Sign In" }
],
"duration_ms": 1247
}

The website has no API. Now what?

You know the drill. The web app you depend on has your data, but no way to get it out programmatically. So you log in, click through four pages, hit Export, wait, download a CSV, then do it again tomorrow. brz does that whole sequence from one terminal command. You write the steps in YAML, brz drives Chrome, and you get JSON back.

Export from apps that won't let you

Your CRM, your analytics dashboard, your admin panel... they all have an Export button buried three clicks deep. brz logs in, finds it, clicks it, downloads the file. Set it up once, run it forever.

brz run exports.yaml login,download-report

Kill the manual workflow

That thing you do every Monday morning where you log into two apps, download a CSV from one, upload it to the other, and then check it worked? That's a 6-line YAML file and a cron job now.

brz run weekly-sync.yaml export,upload

Plug a browser into your LLM

Your agent needs to fill out a form, scrape a table, or download a report. brz inspect tells it what's on the page. The agent writes the YAML. brz run executes it. JSON comes back. That's the whole loop.

brz inspect $URL --compact --json

Pipe data between web apps

App A has pricing data. App B needs it. Neither has an API. Chain two actions in one brz session: export from the first, upload to the second. Cookies persist, so you're not re-authenticating every time.

brz run sync.yaml export-a,import-b

Scrape a number off a page

Sometimes you just need one value: a price, a stock count, a status. brz eval runs JavaScript on any page and returns the result as JSON. Wrap it in a shell script. Check it every hour.

brz eval $URL "document.querySelector('.price').textContent"

Know it worked, not just that it ran

The export completed, but was the CSV empty? Did the page redirect to an error? Eval assertions check the actual outcome: row counts, column names, URL, visible text. If the data's wrong, brz fails loud.

eval: [download_min_rows: 1, url_contains: "/success"]

Three steps to any workflow

Same pattern every time. Find the selectors, write the YAML, run it.

01

Inspect

Point brz inspect at any URL. Get back every interactive element with its CSS selector, tag, type, and name. Filter with --tag and --name. Pipe to jq.

02

Write YAML

Define your workflow as a YAML file. Steps are declarative: fill, click, select, wait, download, upload, eval. No imperative code. Validate with brz validate.

03

Run

Execute with brz run. Chain multiple actions in one browser session. Get JSON output with download paths, timing, and eval results.

The Full Loop
# 1. Discover selectors
$ brz inspect https://app.example.com/login --tag input,button --compact
 
# 2. Write the workflow (login.yaml)
# 3. Validate it
$ brz validate login.yaml
{"ok":true,"actions":2,"total_steps":7}
 
# 4. Run it
$ brz run login.yaml login,export --env EMAIL=dev@co.com
{"ok":true,"action":"export","download":"/tmp/report.csv","duration_ms":3200}

Eight commands. That's the whole CLI.

Each one does one thing. Pipe them together. Let your agent call them in a loop.

brz inspect

brz inspect <url> [flags]

Shows every interactive element on a page with CSS selectors you can paste straight into YAML.

--compact --tag --name --screenshot --eval

brz run

brz run <wf.yaml> <action> [flags]

Runs a workflow action in Chrome. Comma-separate to chain them: login,export.

--env KEY=VAL --dry-run --headed --debug

brz screenshot

brz screenshot <url> [--output file]

Capture a full-page screenshot. Returns path + size as JSON.

--output

brz eval

brz eval <url> <js-expr>

Execute JavaScript on any page. Promises auto-awaited.

--json

brz validate

brz validate <workflow.yaml>

Check YAML syntax. Count actions and steps. Fast feedback.

--json

brz actions

brz actions <workflow.yaml>

List all actions in a workflow with URLs and step counts.

--json

brz prompt

brz prompt

Print the embedded LLM agent prompt. Copy into your agent's system prompt.

brz version

brz version

Print the version. That's it.

0
Success
1
Step Failed
2
Bad Workflow
3
Browser Error

YAML in. Browser out.

The binary has zero site-specific code. Your workflow YAML is the whole program. Write it yourself or let your agent generate it.

12 step types

Each step is a single YAML line. Selectors come from brz inspect. Environment variables interpolate with ${VAR}.

StepWhat it does
clickClick by selector, text, or nth
fillType into input fields
selectNative or Select2 dropdowns
uploadSet file on input[type=file]
downloadCapture file downloads
navigateGo to a URL
wait_visibleWait for element
wait_textWait for text on page
wait_urlWait for URL match
evalRun JS in page context
screenshotCapture page state
sleepPause for a duration
export-reports.yaml
name: export-reports
actions:
login:
url: https://app.example.com/login
steps:
- fill:
selector: 'input[name="email"]'
value: '${EMAIL}'
- fill:
selector: 'input[name="password"]'
value: '${PASSWORD}'
- click:
selector: 'button[type="submit"]'
- wait_url:
match: '/dashboard'
 
export:
url: https://app.example.com/reports
steps:
- click: { selector: '#export-csv-btn' }
- download: { timeout: '60s' }
eval:
- download_min_rows: 1
- download_has_columns:
- ID
- Name
- Price
Run It
$ brz run export-reports.yaml login,export \
--env EMAIL=dev@co.com \
--env PASSWORD=secret
 
{"ok":true,"action":"export","download":"/tmp/report.csv","duration_ms":4100}

Your LLM's browser.

brz outputs JSON when piped, returns meaningful exit codes, and includes similar selectors in error messages so your agent can self-correct.

Agent Loop
# Agent discovers the page
$ brz inspect $URL --compact --json
 
# Agent generates workflow YAML
# Agent validates before running
$ brz validate agent-wf.yaml
 
# Agent dry-runs to check env vars
$ brz run agent-wf.yaml action --dry-run
 
# Agent executes
$ brz run agent-wf.yaml action
 
# On failure: page_elements has similar selectors
# Agent self-corrects without re-inspecting

JSON When Piped

Run it in a terminal, you get readable text. Pipe it somewhere, you get JSON. Automatic.

Error Recovery

When a step fails, the JSON includes page_elements with nearby selectors. The agent fixes the YAML without having to re-inspect.

Embedded Prompt

brz prompt prints a system prompt you paste into your agent. It covers all the commands, flags, and failure modes.

Session Persistence

Cookies stick around between runs. Log in once with --headed, then every brz command after that is authenticated.


One tool, done right.

It's a Go binary. You install it, you run it. There is no config file.

Stealth Mode

Masks the webdriver flag, spoofs Client Hints, uses a real User-Agent. Sites behind Cloudflare and DataDome don't know it's automated.

Auto-Headed Fallback

BRZ_HEADED=auto starts headless but pops open a real window if it hits a CAPTCHA or stale session. Solve it manually, brz picks back up.

Debug Screenshots

Captures before/after JPEGs when a step fails. Kept in a ring buffer in memory, so you pay nothing on success. When something breaks, you see exactly what Chrome saw.

9 Eval Assertions

After the steps run, check the result: did the CSV have rows? Did the URL end up where it should? Was the error text absent? Nine different checks.

Smart Navigation

If the next action targets the same URL, brz skips the navigation. Actions with no URL just keep working on whatever page is loaded.

Native Dropdowns

The select step figures out if it's a real <select> or a Select2 widget and handles both. Retries if the dropdown is still loading from AJAX.

System Chrome First

Finds and uses the Chrome already on your machine. Only downloads Chromium if you don't have Chrome installed at all.

Public Go API

Import the workflow/ package into your own Go code. It's mutex-protected and safe to call from multiple goroutines.


Try it.

Install, point brz inspect at a URL, and see what comes back.

$ brew install crackfetch/tap/brz Copy
$ go install github.com/crackfetch/brz@latest Copy