@cvr/browser
v1.0.0
Published
Headless browser automation CLI for AI agents
Readme
Browser CLI
Headless browser automation CLI for AI agents. Built with Effect and Playwright.
Inspired by vercel-labs/browser.
Features
- Daemon architecture - Background process manages browser lifecycle
- Unix socket IPC - Fast, reliable communication between CLI and daemon
- Session isolation - Run multiple browser instances in parallel
- Accessibility snapshots - Get semantic page structure with element refs
- Chrome profile support - Reuse existing cookies and auth state
- Interactive picker - Let users click to select elements in headed mode
Installation
# Clone and install
git clone https://github.com/your-username/browser.git
cd browser
bun install
# Build binary
bun run build
# Add to PATH (or use directly)
export PATH="$PWD/bin:$PATH"Quick Start
browser open https://example.com # Navigate (auto-starts daemon)
browser snapshot # Get accessibility tree
browser click "button[Submit]" # Click element
browser fill "#email" "[email protected]" # Fill input
browser screenshot # Take screenshot
browser close # Close browser and daemonCommands
Navigation
| Command | Description |
|---------|-------------|
| browser open <url> | Navigate to URL (auto-starts daemon) |
| browser open --headed <url> | Navigate with visible browser window |
| browser open --profile true <url> | Use default Chrome profile |
| browser back | Go back in history |
| browser forward | Go forward in history |
| browser reload | Reload current page |
Page Content
| Command | Description |
|---------|-------------|
| browser snapshot | Get page accessibility tree with refs |
| browser snapshot -i | Interactive elements only |
| browser snapshot -c | Compact mode (no empty elements) |
| browser screenshot [path] | Take screenshot |
| browser screenshot -f | Full page screenshot |
| browser content | Get page HTML |
| browser get url | Get current URL |
| browser get title | Get page title |
| browser get text <selector> | Get element text |
Actions
| Command | Description |
|---------|-------------|
| browser click <selector> | Click element (supports @refs) |
| browser fill <selector> <value> | Fill input field |
| browser type <selector> <text> | Type text character by character |
| browser hover <selector> | Hover over element |
| browser check <selector> | Check checkbox |
| browser uncheck <selector> | Uncheck checkbox |
| browser press <key> | Press keyboard key |
| browser select <selector> <value> | Select dropdown option |
| browser scroll down | Scroll page down |
Tabs
| Command | Description |
|---------|-------------|
| browser tab list | List all tabs |
| browser tab new | Open new tab |
| browser tab switch <index> | Switch to tab by index |
| browser tab close [index] | Close tab |
Debug
| Command | Description |
|---------|-------------|
| browser console | Get console messages |
| browser errors | Get page errors |
| browser eval <script> | Evaluate JavaScript |
| browser wait <selector> | Wait for element |
Interactive
| Command | Description |
|---------|-------------|
| browser pick <message> | Interactive element picker |
Session Management
| Command | Description |
|---------|-------------|
| browser close | Close browser and daemon |
| browser -s <name> <cmd> | Use named session |
Using Element Refs
After snapshot, elements have refs like [ref=e1]. Use them directly with @:
browser snapshot
# Output:
# - button "Submit" [ref=e1]
# - link "Home" [ref=e2]
browser click @e1 # Click the Submit button
browser hover @e2 # Hover over Home linkChrome Profile Support
Reuse your existing Chrome cookies and authentication:
# Use default Chrome profile
browser open --profile true --headed https://github.com
# Use custom profile path
browser open --profile "/path/to/profile" https://github.comSessions
Run multiple browser instances in parallel:
# Terminal 1
browser -s session1 open https://site1.com
# Terminal 2
browser -s session2 open https://site2.comArchitecture
┌─────────────────────────────────────────────────────────┐
│ CLI (src/cli.ts) │
│ - Parses args via @effect/cli │
│ - Uses DaemonManager for daemon lifecycle │
│ - Sends commands via Unix socket │
└────────────────────────────┬────────────────────────────┘
│
┌────────────────────┴────────────────────┐
▼ ▼
┌───────────────────┐ ┌───────────────┐
│ DaemonManager │ │ Unix Socket │
│ (spawn/check) │ │ (IPC) │
└───────────────────┘ └───────┬───────┘
│
[Daemon Process]
│
▼
┌─────────────────────────────┐
│ server.ts │
│ - ManagedRuntime │
│ - Command dispatch │
└──────────────┬──────────────┘
│
▼
┌─────────────────────────────┐
│ PlaywrightService │
│ - executeCommand(cmd) │
│ - State via Effect Ref │
│ - 65+ command handlers │
└─────────────────────────────┘Development
# Run from source
bun run src/cli.ts open https://example.com
# Type check
bun run typecheck
# Run tests
bun run test
# Build binary
bun run buildTesting
Tests use @effect/vitest with mock layers:
import { describe, expect, it } from '@effect/vitest'
import { Effect } from 'effect'
import { PlaywrightService } from '../src/services/PlaywrightService.js'
describe('PlaywrightService', () => {
it.effect('navigate sends URL', () => {
const recorder = createRecorder(new Map([
['navigate', { url: 'https://example.com', title: 'Example' }]
]))
return Effect.gen(function* () {
const playwright = yield* PlaywrightService
const response = yield* playwright.executeCommand({
id: 'test-1',
action: 'navigate',
url: 'https://example.com',
})
expect(response.success).toBe(true)
}).pipe(Effect.provide(PlaywrightService.Test(recorder.handler)))
})
})Tech Stack
- Runtime: Bun
- Effect System: Effect for typed errors, services, and layers
- CLI Framework: @effect/cli
- Browser Automation: Playwright
- Testing: Vitest + @effect/vitest
Credits
- vercel-labs/browser - Original inspiration for browser automation CLI design
- Effect - Excellent TypeScript library for building robust applications
- Playwright - Reliable browser automation
License
MIT
