| name | surf |
| description | Control Chrome browser via CLI for testing, automation, and debugging. Use when the user needs browser automation, form filling, page inspection, network/CPU emulation, or DevTools streaming. |
Surf Browser Automation
Control Chrome browser via CLI or Unix socket.
⚡ Key Principles
- Text-first, screenshots-last — Always use
page.read, page.text, and page.read --compact to understand pages. Only take screenshots when you genuinely need to see visual layout, verify styling, or capture evidence.
- Refs for interaction — Use
page.read --no-text --compact to get element refs, then act on them directly. This is faster and cheaper than screenshots.
- Use
surf do for multi-step tasks — Chain commands in a single call instead of round-tripping through the LLM.
- No AI assistant features — Do NOT use
surf chatgpt, surf gemini, surf perplexity, surf grok, or surf aistudio. Use pi's own model for all reasoning and analysis.
Core Workflow
surf navigate "https://example.com"
surf page.text
surf page.read --compact
surf page.read --no-text
surf click --ref e5
surf type --text "hello"
surf screenshot --output /tmp/shot.png
CLI Quick Reference
surf --help
surf <group>
surf --help-full
surf --find <term>
surf --help-topic <topic>
Page Reading (Preferred Over Screenshots)
surf page.text
surf page.read --compact
surf page.read --no-text
surf page.read
surf page.read --ref e5
surf page.read --depth 3
surf page.state
When to use what:
- Reading an article/page content →
surf page.text
- Need to click/fill/interact →
surf page.read --no-text --compact to get refs, then act
- Debugging layout or verifying visual state →
surf screenshot (only then)
Semantic Element Location
Find and act on elements by role, text, or label — no refs needed:
surf locate.role button --name "Submit" --action click
surf locate.role textbox --name "Email" --action fill --value "test@example.com"
surf locate.role link --all
surf locate.text "Sign In" --action click
surf locate.text "Accept" --exact --action click
surf locate.label "Username" --action fill --value "john"
surf locate.label "Password" --action fill --value "secret"
Actions: click, fill, hover, text (get text content)
Text Search
surf search "login"
surf search "Error" --case-sensitive
surf search "button" --limit 5
Tab Management
surf tab.list
surf tab.new "https://google.com"
surf tab.switch 12345
surf tab.close 12345
surf tab.reload
surf tab.name myapp
surf tab.switch myapp
surf tab.named
surf tab.unname myapp
surf tab.group
surf tab.ungroup
surf tab.groups
Window Management
surf window.list
surf window.list --tabs
surf window.new
surf window.new --url "https://example.com"
surf window.new --incognito
surf window.new --unfocused
surf window.focus 12345
surf window.close 12345
surf window.resize --id 123 --width 1920 --height 1080
surf window.resize --id 123 --state maximized
Window isolation for agents:
surf window.new "https://example.com"
surf --window-id 123 tab.list
surf --window-id 123 go "https://other.com"
Input Methods
surf type --text "hello"
surf click --ref e5
surf click --x 100 --y 200
surf type --text "hello" --selector "#input" --method js
surf key Enter
surf key "cmd+a"
surf key.repeat --key Tab --count 5
surf hover --ref e5
surf drag --from-x 100 --from-y 100 --to-x 200 --to-y 200
Scrolling
surf scroll.bottom
surf scroll.top
surf scroll.to --y 500
surf scroll.to --ref e5
surf scroll.by --y 200
surf scroll.info
Waiting
surf wait 2
surf wait.element ".loaded"
surf wait.network
surf wait.url "/success"
surf wait.dom --stable 100
surf wait.load
Form Automation
surf page.read --no-text --compact
surf form.fill --data '[{"ref":"e1","value":"John"},{"ref":"e2","value":"john@example.com"}]'
surf form.fill --data '[{"ref":"e7","value":true}]'
surf select e5 "Option A"
surf select e5 "Option A" "Option B"
surf select e5 --by label "Display Text"
surf select e5 --by index 2
File Upload
surf upload --ref e5 --files "/path/to/file.txt"
surf upload --ref e5 --files "/path/file1.txt,/path/file2.txt"
JavaScript Execution
surf js "return document.title"
surf js "document.querySelector('.btn').click()"
Useful for interacting with rich text editors, SPAs, or when you need to set values programmatically:
surf js "const el = document.querySelector('textarea'); el.value = 'text'; el.dispatchEvent(new Event('input', {bubbles:true})); return 'done'"
Iframe Handling
surf frame.list
surf frame.switch "FRAME_ID"
surf frame.main
surf frame.js --id "FRAME_ID" --code "return document.title"
Network Inspection
surf network
surf network --stream
surf network.get --id "req-123"
surf network.body --id "req-123"
surf network.curl --id "req-123"
surf network.origins
surf network.stats
surf network.export
surf network.clear
Console
surf console
surf console --stream
surf console --stream --level error
Screenshots (Use Sparingly)
Only use when you need to verify visual layout, capture evidence, or debug rendering.
surf screenshot
surf screenshot --output /tmp/shot.png
surf screenshot --selector ".card"
surf screenshot --full-page
surf screenshot --no-save
Device/Network Emulation
surf emulate.network slow-3g
surf emulate.network reset
surf emulate.cpu 4
surf emulate.cpu 1
surf emulate.device "iPhone 14"
surf emulate.device --list
surf emulate.viewport --width 1280 --height 720
surf emulate.touch --enable
surf emulate.geo --lat 37.7749 --lon -122.4194
surf emulate.geo --clear
Element Inspection
surf element.styles e5
surf element.styles ".card"
Dialog Handling
surf dialog.info
surf dialog.accept
surf dialog.accept --text "response"
surf dialog.dismiss
Cookies & Storage
surf cookie.list
surf cookie.list --domain .google.com
surf cookie.set --name "token" --value "abc123"
surf cookie.get --name "token"
surf cookie.clear
History & Bookmarks
surf history --query "github" --max 20
surf bookmarks --query "docs"
surf bookmark.add --url "https://..." --title "My Bookmark"
surf bookmark.remove
Health Checks & Smoke Tests
surf health --url "http://localhost:3000"
surf smoke --urls "http://localhost:3000" "http://localhost:3000/about"
surf smoke --urls "..." --screenshot /tmp/smoke
Workflows (surf do)
Execute multi-step browser automation as a single command with smart auto-waits.
surf do 'go "https://example.com" | click e5 | screenshot'
surf do 'go "https://example.com/login" | type "user@example.com" --selector "#email" | type "pass" --selector "#password" | click --selector "button[type=submit]"'
surf do 'go "url" | click e5' --dry-run
Named Workflows
Save as JSON in ~/.surf/workflows/ (user) or ./.surf/workflows/ (project):
surf workflow.list
surf workflow.info my-workflow
surf do my-workflow --email "user@example.com" --password "secret"
surf workflow.validate workflow.json
Workflow Options
--file, -f <path>
--dry-run
--on-error stop|continue
--step-delay <ms>
--no-auto-wait
--json
Zoom
surf zoom
surf zoom 1.5
surf zoom 1
Performance
surf perf.metrics
surf perf.start
surf perf.stop
Error Diagnostics
surf wait.element ".missing" --auto-capture --timeout 2000
Common Options
--tab-id <id>
--window-id <id>
--json
--auto-capture
--timeout <ms>
Tips
- Text first —
page.text and page.read --compact before reaching for screenshots
- First CDP operation is slow (~5-8s) — debugger attachment overhead, subsequent calls fast
- Use refs from page.read for reliable element targeting over CSS selectors
- JS method for rich editors — CodeMirror, Monaco, contenteditable need
--method js or direct surf js
- Named tabs for workflows —
tab.name app then tab.switch app
- Auto-capture for debugging —
--auto-capture saves diagnostics on failure
- Use
surf do for multi-step tasks — Reduces token overhead and improves reliability
- Dry-run workflows first —
surf do '...' --dry-run validates without executing
- Window isolation —
window.new + --window-id keeps agent work separate from user browsing
- Semantic locators —
locate.role, locate.text, locate.label for robust element finding
- Frame context — Use
frame.switch before interacting with iframe content
Socket API
For programmatic access:
echo '{"type":"tool_request","method":"execute_tool","params":{"tool":"tab.list","args":{}},"id":"1"}' | nc -U /tmp/surf.sock