Browser Automation with browser-use
All commands use the self-contained wrapper bu.sh, which:
- Auto-bootstraps the local
.venvon first run (downloads deps viauv— takes ~60s once) - Sets
PYTHONUTF8=1automatically (handles emoji/Unicode on Windows) - Keeps a persistent browser daemon for ~50ms latency per call
Shorthand — in all examples, bu means:
bash ~/.claude/skills/browser-use/bu.shPrerequisites
uvmust be installed (uv --version)- First run will install browser-use from GitHub into
~/.claude/skills/browser-use/.venv
Core Workflow
- Navigate:
bu open <url>— starts browser + daemon if needed - Inspect:
bu state— returns clickable elements with indices - Interact: use indices from state (
bu click 5,bu input 3 "text") - Verify:
bu stateorbu screenshotto confirm - Repeat: browser stays open between commands
- Cleanup:
bu closewhen done
Browser Modes
bu open <url> # Default: headless Chromium
bu --headed open <url> # Visible window
bu --profile "Default" open <url> # Real Chrome with Default profile (existing logins/cookies)
bu --profile "Profile 1" open <url> # Real Chrome with named profile
bu --connect open <url> # Auto-discover running Chrome via CDP
bu --cdp-url ws://localhost:9222/... open <url> # Connect via specific CDP URL--connect, --cdp-url, and --profile are mutually exclusive.
Commands
# Navigation
bu open <url> # Navigate to URL
bu back # Go back in history
bu scroll down # Scroll down (--amount N for pixels)
bu scroll up # Scroll up
bu switch <tab> # Switch to tab by index
bu close-tab [tab] # Close tab (current if no index)
# Page State — always run state first to get element indices
bu state # URL, title, clickable elements with indices
bu screenshot [path.png] # Screenshot (base64 if no path, --full for full page)
# Interactions — use indices from state
bu click <index> # Click element by index
bu click <x> <y> # Click at pixel coordinates
bu type "text" # Type into focused element
bu input <index> "text" # Click element, then type
bu keys "Enter" # Send keyboard keys (also "Control+a", etc.)
bu select <index> "option" # Select dropdown option
bu upload <index> <path> # Upload file to file input
bu hover <index> # Hover over element
bu dblclick <index> # Double-click element
bu rightclick <index> # Right-click element
# Data Extraction
bu eval "js code" # Execute JavaScript, return result
bu get title # Page title
bu get html [--selector "h1"] # Page HTML (or scoped to selector)
bu get text <index> # Element text content
bu get value <index> # Input/textarea value
bu get attributes <index> # Element attributes
bu get bbox <index> # Bounding box (x, y, width, height)
# Wait
bu wait selector "css" # Wait for element (--state visible|hidden|attached|detached, --timeout ms)
bu wait text "text" # Wait for text to appear
# Cookies
bu cookies get [--url <url>] # Get cookies (optionally filtered)
bu cookies set <name> <value> # Set cookie (--domain, --secure, --http-only, --same-site, --expires)
bu cookies clear [--url <url>] # Clear cookies
bu cookies export <file> # Export to JSON
bu cookies import <file> # Import from JSON
# Python — persistent session with browser access
bu python "code" # Execute Python (variables persist across calls)
bu python --file script.py # Run file
bu python --vars # Show defined variables
bu python --reset # Clear namespace
# Session
bu close # Close browser and stop daemon
bu sessions # List active sessions
bu close --all # Close all sessions
# Recipes — store and replay DOM access patterns (no screenshot on repeat runs)
bu recipe list # Show all installed recipes + run history
bu recipe run <name> # Execute a recipe directly against the DOM
bu recipe run <name> --connect # Override auth: use CDP connect mode
bu recipe run <name> --headed # Override: show browser window (debug)
bu recipe show <name> # Print recipe JSON
bu recipe save <name> --file <path> # Install a recipe JSON file
bu recipe delete <name> # Remove a recipeThe Python browser object provides: browser.url, browser.title, browser.html, browser.goto(url), browser.back(), browser.click(index), browser.type(text), browser.input(index, text), browser.keys(keys), browser.upload(index, path), browser.screenshot(path), browser.scroll(direction, amount), browser.wait(seconds).
Cloud API
bu cloud connect # Provision cloud browser and connect
bu cloud connect --timeout 120 --proxy-country US # With options
bu cloud login <api-key> # Save API key (or set BROWSER_USE_API_KEY)
bu cloud logout # Remove API key
bu cloud v2 GET /browsers # REST passthrough (v2 or v3)
bu cloud v2 POST /tasks '{"task":"...","url":"..."}'
bu cloud v2 poll <task-id> # Poll task until doneCommon Workflows
Authenticated Browsing (existing Chrome session)
bu --connect open https://gmail.com # Reuse logged-in Chrome via CDP
bu --connect state
bu --connect eval "document.title"Requires Chrome launched with --remote-debugging-port=9222.
Authenticated Browsing (Chrome profile)
bu profile list # Check available profiles
bu --profile "Default" open https://github.com # Already logged inExtracting Data via JavaScript
bu --connect open https://x.com/notifications
bu --connect eval "Array.from(document.querySelectorAll('[data-testid=\"notification\"]')).slice(0,10).map(n => n.innerText).join('\n---\n')"Exposing Local Dev Servers
bu tunnel 3000 # → https://abc.trycloudflare.com
bu open https://abc.trycloudflare.comCommand Chaining
bu open https://example.com && bu state
bu input 5 "user@example.com" && bu input 6 "password" && bu click 7Chain when you don't need intermediate output. Run separately when you need to parse state to discover indices first.
Global Options
| Option | Description |
|---|---|
--headed | Show browser window |
--profile [NAME] | Use real Chrome (bare --profile uses "Default") |
--connect | Auto-discover running Chrome via CDP |
--cdp-url <url> | Connect via CDP URL (http:// or ws://) |
--session NAME | Target a named session (default: "default") |
--json | Output as JSON |
--mcp | Run as MCP server via stdin/stdout |
Tips
- Always run
statefirst to see available elements and their indices - Use
--headedfor debugging to see what the browser is doing - Sessions persist — browser stays open between commands
evalwith JS is the most powerful extraction method for complex pages
Troubleshooting
- First run slow? Normal — downloading ~200MB of deps into
.venv. Subsequent runs are instant. - Browser won't start?
bu closethenbu --headed open <url> - Element not found?
bu scroll downthenbu state - Unicode errors? Already handled —
bu.shsetsPYTHONUTF8=1automatically
Cleanup
bu close # Close browser session
bu tunnel stop --all # Stop tunnels (if any)Recipe System
Recipes store DOM access patterns so repeated tasks skip visual discovery entirely.
First run: discover selectors manually → write a recipe JSON Repeat runs: bu recipe run <name> executes JS directly — no screenshot, no element scanning
Recipe JSON format
{
"name": "my_recipe",
"version": 1,
"description": "What this recipe does",
"url": "https://example.com/page",
"auth": {
"mode": "connect", // "connect" | "profile" | "headless"
"profile": null, // Chrome profile name (mode=profile only)
"headed": false,
"session": "default"
},
"steps": [
{
"id": "nav",
"type": "navigate",
"url": "https://example.com/page",
"wait_for": ".main-content",
"wait_timeout_ms": 10000
},
{
"id": "extract",
"type": "eval",
"js": "document.querySelector('.result')?.innerText",
"output_var": "result",
"fallback_selectors": [".alt-result", "[data-result]"]
}
],
"output": { "format": "text", "var": "result" },
"metadata": { "created": "2026-01-01", "last_run": null, "run_count": 0, "broken_step": null }
}Step types
| Type | Required fields | What it does |
|---|---|---|
navigate | url | Open URL; optionally wait for a CSS selector |
eval | js | Run JavaScript; stores result in output_var |
click | index | Click element by index from bu state |
input | index, text | Click element then type text |
wait | selector or text | Wait for element or text to appear |
scroll | direction | Scroll up or down |
Self-healing fallback
When an eval step fails:
- Each
fallback_selectorsentry is tried asdocument.querySelector('SEL')?.innerText - First match wins — logged to stderr, recipe continues
- If all fallbacks fail: screenshot saved to
recipes/.broken_<name>_<step>.png,broken_stepset in metadata, exit 1
Sample recipe
See recipes/x_notifications.json — fetches X.com notifications using CDP connection.
Platform support
Works on Linux, macOS, and Windows (Git Bash or WSL required on Windows). bash must be available in PATH — it is on all standard configurations.