| name | surf |
| description | Control Chrome browser via CLI for testing, automation, and debugging. Use when the user needs browser automation, screenshots, form filling, page inspection, network/CPU emulation, DevTools streaming, or AI queries via ChatGPT/Gemini/Perplexity/Grok/AI Studio. |
Surf Browser Automation
Control Chrome browser via CLI or Unix socket.
Native Host / Socket Notes
For WSL2 with Windows Chrome, run surf install <extension-id> inside WSL2. Surf detects WSL2 and writes the Windows-side native messaging manifest plus a wrapper that launches the WSL host. Use surf install <extension-id> --target linux only for Linux browsers running inside WSLg.
On macOS, Chrome reads the native messaging manifest at ~/Library/Application Support/Google/Chrome/NativeMessagingHosts/surf.browser.host.json. If native messaging fails, confirm that file exists, its allowed_origins extension ID matches chrome://extensions, then rerun surf install <extension-id>, restart Chrome, reload the extension, and inspect the extension service-worker console.
If a command reports Socket connect failed, run surf doctor first, then check the Attempted socket: line. Default sockets are /tmp/surf.sock on macOS/Linux/WSL2 and //./pipe/surf on Windows. If SURF_SOCKET is set, the browser-launched host and the shell running surf must use the same value.
Remote Surf
Remote clients require a per-client credential; Tailnet reachability alone is not authorization. On the POSIX browser host, authorize the client before installing the listener:
surf remote authorize agent-macbook --output ~/agent-macbook.surf-credential.json
surf install <extension-id> --listen 100.101.102.103:4321
surf remote list
Move the mode-0600 credential to the client through a secure channel. It grants full trusted Surf authority. Use it explicitly or through SURF_REMOTE and SURF_REMOTE_CREDENTIAL:
surf --remote 100.101.102.103:4321 \
--remote-credential ~/.config/surf/agent-macbook.json \
page.read
surf remote revoke agent-macbook
Remote paths are client-local by default. local:./file is explicit client-local syntax; only remote:/absolute/path accesses the browser host directly. Remote transfer supports one upload or ChatGPT/Gemini input and one screenshot, network-export, or Gemini image output. Limits are 256 MiB per file, 512 MiB and 32 files per connection, and 256 KiB decoded chunks. record, aistudio.build, smoke screenshot directories, directories, and multi-file inputs are not supported remotely. Successful action screenshots and failure --auto-capture diagnostics are transferred back to client-local paths.
CLI Quick Reference
surf --help
surf <group>
surf --help-full
surf --find <term>
surf --help-topic <topic>
Core Workflow
surf navigate "https://example.com"
surf page.read
surf click --ref "e1"
surf click --x 100 --y 200
surf type --text "hello"
surf screenshot --output /tmp/shot.png
surf animate-audit --selector ".thing" --duration 2000 --fps 10
AI Assistants (No API Keys)
Query AI models using your browser's logged-in session. Must be logged into the respective service in Chrome.
ChatGPT
surf chatgpt "explain this code"
surf chatgpt "summarize" --with-page
surf chatgpt "review" --model gpt-4o
surf chatgpt "analyze" --file document.pdf
Gemini
surf gemini "explain quantum computing"
surf gemini "summarize" --with-page
surf gemini "analyze" --file data.csv
surf gemini "a robot surfing" --generate-image /tmp/robot.png
surf gemini "add sunglasses" --edit-image photo.jpg --output out.jpg
surf gemini "summarize" --youtube "https://youtube.com/..."
surf gemini "hello" --model gemini-3.5-flash
surf gemini "wide banner" --generate-image /tmp/banner.png --aspect-ratio 16:9
Perplexity
surf perplexity "what is quantum computing"
surf perplexity "explain this page" --with-page
surf perplexity "deep dive" --mode research
surf perplexity "latest news" --model sonar
Grok (via x.com - requires X.com login in Chrome)
surf grok "what are the latest AI trends on X"
surf grok "analyze @username recent activity"
surf grok "summarize this page" --with-page
surf grok "find viral AI posts" --deep-search
surf grok "quick question" --model fast
For exhaustive, multi-angle X research with categorized findings and full post-URL traceability, use the deep-x-research skill (skills/deep-x-research/) instead of a single Grok query.
Grok Validation & Troubleshooting:
surf grok --validate
surf grok --validate --save-models
AI Studio (via aistudio.google.com - requires Google login in Chrome)
surf aistudio "explain quantum computing"
surf aistudio "redteam this" --with-page
surf aistudio "quick answer" --model gemini-3-flash-preview
surf aistudio "analyze" --timeout 600
Why AI Studio over Gemini? AI Studio gives access to less restricted Gemini models. For Gemini 3 Pro the difference can be significant with certain prompts. Downside: aggressive per-day rate limits on Pro and Flash models.
Model selection is best-effort: Pass any AI Studio model id (e.g. gemini-3.1-pro-preview, gemini-3-flash-preview, gemini-flash-lite-latest). If the model isn't found, AI Studio uses whatever model was last selected in the UI.
AI Studio App Builder
surf aistudio.build "build a portfolio site"
surf aistudio.build "todo app" --model gemini-3.1-pro-preview
surf aistudio.build "crm dashboard" --output ./out
surf aistudio.build "game" --keep-open --timeout 600
Automates AI Studio's App Builder at aistudio.google.com/apps. Types your prompt, clicks Build, waits for completion, downloads the generated zip, and optionally extracts it.
--output <dir> extracts the zip to a directory
--model <id> overrides the model in Advanced Settings
--timeout <seconds> build timeout (default: 600s)
--keep-open leaves the AI Studio tab open after completion
Returns zipPath, extractedPath, model, buildDuration, and tookMs.
AI Tool Troubleshooting
When AI queries fail, check these common issues:
- Not logged in: The error "login required" means you need to log into the service in Chrome (chatgpt.com, gemini.google.com, perplexity.ai, x.com, or aistudio.google.com)
- Model selection failed: The UI may have changed. Run
surf grok --validate to check
- Response timeout: Reasoning-heavy models (ChatGPT o1, Grok Expert) can take 45+ seconds. AI Studio builds can take several minutes.
- Element not found: The service's UI changed. Check for surf-cli updates
Debugging workflow for agents:
surf grok --validate
surf grok --validate --save-models
surf grok "query" --model <model-from-validation>
surf grok "query" --timeout 600
Tab Management
surf tab.list
surf tab.new "https://google.com"
surf tab.switch 12345
surf tab.close 12345
surf tab.move 12345 --to-window 67890
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 resize 1280 720
surf resize 1280
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
Multi-agent isolation:
surf window.new "https://example.com"
surf --window-id 123 tab.list
surf --window-id 123 go "https://other.com"
surf read --tab-id 456
surf tab.name agent-a --tab-id 456
surf tab.switch agent-a
Use window.new, --window-id, --tab-id, and named tabs to keep parallel agents on separate targets. Surf serializes non-streaming browser CLI requests per socket with a file-based lock, so agents sharing one native host wait instead of interleaving commands. Use --no-lock only for intentional bypasses. For hard isolation, run separate browser/profile instances with separate native hosts and SURF_SOCKET values; each socket gets its own lock. Surf does not yet have session.new, session IDs, or independent per-agent CDP sessions.
Input Methods
surf type --text "hello"
surf click --x 100 --y 200
surf type "hello" --into "#input"
surf type "hello" --ref e5
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
Page Inspection
surf page.read
surf page.read --no-text
surf animate-audit --selector ".thing" --duration 2000 --fps 10
surf page.read --ref e5
surf page.read --depth 3
surf page.read --compact
surf page.read --max-bytes 2000
surf page.text
surf page.state
Semantic Element Location
Find and act on elements by role, text, or label instead of refs:
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
surf find "login"
Element Inspection
surf element.styles e5
surf element.styles ".card"
Scrolling
surf scroll down 800
surf scroll up 400
surf scroll bottom
surf scroll top
surf scroll.bottom
surf scroll.top
surf scroll.to --ref e5
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
Dialog Handling
surf dialog.info
surf dialog.accept
surf dialog.accept --text "response"
surf dialog.dismiss
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 "Pixel 7"
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
Form Automation
surf page.read
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"
Iframe Handling
surf frame.list
surf frame.switch "FRAME_ID"
surf frame.main
surf frame.js --id "FRAME_ID" --code "return document.title"
surf frame.switch "iframe-1"
surf page.read
surf click e5
surf frame.main
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 -vv --body-mode text --per-body-bytes 65536
surf network.export --har --output ./trace.har
surf network.clear
Response-body capture supports none, text, and all modes plus per-body and per-tab-session byte caps. HAR exports carry body completeness metadata. Persistent network state is private under ~/.surf/state/network/ by default; configure SURF_NETWORK_PATH in the native host environment to change it.
Console
surf console
surf console --stream
surf console --stream --level error
JavaScript Execution
surf js "return document.title"
surf js "document.querySelector('.btn').click()"
Performance
surf perf.metrics
surf perf.start
surf perf.stop
Screenshots
surf screenshot
surf screenshot --output /tmp/shot.png
surf screenshot --selector ".card"
surf screenshot --full-page
surf screenshot --full-page /tmp/full.png
surf screenshot --no-save
Zoom
surf zoom
surf zoom 1.5
surf zoom 1
Cookies & Storage
surf cookie list
surf cookie list --domain .google.com
surf cookie set --name "token" --value "abc123"
surf cookie get "token"
surf cookie clear --all
surf cookie delete "token"
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
Execute multi-step browser automation as a single command with smart auto-waits.
Inline Workflows
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 workflows as JSON files 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 JSON Format
{
"name": "Login Flow",
"description": "Automate login process",
"args": {
"email": { "required": true },
"password": { "required": true },
"url": { "default": "https://example.com/login" }
},
"steps": [
{ "tool": "navigate", "args": { "url": "%{url}" } },
{ "tool": "type", "args": { "text": "%{email}", "selector": "input[name=email]" } },
{ "tool": "type", "args": { "text": "%{password}", "selector": "input[name=password]" } },
{ "tool": "click", "args": { "selector": "button[type=submit]" } },
{ "tool": "screenshot", "args": {}, "as": "result" }
]
}
Loops and Step Outputs
{
"steps": [
{ "tool": "js", "args": { "code": "return [1,2,3]" }, "as": "items" },
{ "repeat": 5, "steps": [
{ "tool": "click", "args": { "ref": "e5" } }
]},
{ "each": "%{items}", "as": "item", "steps": [
{ "tool": "js", "args": { "code": "console.log('%{item}')" } }
]},
{ "repeat": 20, "until": { "tool": "js", "args": { "code": "return done" } }, "steps": [...] }
]
}
Workflow Options
--file, -f <path>
--dry-run
--on-error stop|continue
--step-delay <ms>
--no-auto-wait
--json
Auto-waits: Commands automatically wait for completion:
- Navigation (
go, back, forward) → waits for page load
- Clicks, key presses, form fills → waits for DOM stability
- Tab switches → waits for tab to load
Why use do? Instead of 6-8 separate CLI calls with LLM orchestration between each, a workflow executes deterministically. Faster, cheaper, and more reliable.
Playbooks
Use surf do for a direct command sequence. Use a playbook for a reusable site capability with provenance, browser-session network execution, workflow fallback, and write-safety policy.
surf playbook list
surf pb show page
surf pb ops page
surf use page read --json
Resolution order is project (./.surf/playbooks/), user (~/.surf/playbooks/), then built-in. Provider compatibility commands stay on their validated command paths until provider playbooks have real login-flow validation. A write op requires --write; Surf records semantic intent before dispatch so a timeout or concurrent retry cannot silently double-submit.
Author from redacted recent activity when it contains only read/navigation behavior, or use an explicit record for richer evidence:
surf pb suggest --since 1h
surf pb save example --op read --from-recent 1h
surf pb record start example --op read --network --watch
surf pb record mark "loaded results"
surf pb record stop --draft
surf pb save --from-record <record-id>
surf pb trace export --from-record <record-id> --har ./trace.har
surf pb export example --out ./example-playbook
surf pb import ./example-playbook
Records, trace slices, receipts, and the bounded activity journal are private Surf state. Inputs and authentication headers are redacted by default. Use --include-input-values only when the saved values are necessary and acceptable.
Client projections replay a validated read endpoint and never embed captured browser credentials:
surf pb client derive example --op read --from-record <record-id> --request-id <request-id> --out ./client
surf pb client export example --op read --out ./client
surf pb client verify ./client
Error Diagnostics
surf wait.element ".missing" --auto-capture --timeout 2000
Common Options
--tab-id <id>
--window-id <id>
--json
--auto-capture
--timeout <ms>
Tips
- 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 contenteditable - Modern editors (ChatGPT, Claude, Notion) need
--method js
- Named tabs for workflows -
tab.name app then tab.switch app
- Auto-capture for debugging -
--auto-capture saves diagnostics on failure
- AI tools use browser session - Must be logged into the service (ChatGPT, Gemini, Perplexity, Grok, AI Studio), no API keys needed
- Grok validation - Run
surf grok --validate if queries fail to check UI changes
- Long timeouts for reasoning-heavy models - ChatGPT o1 and Grok Expert can take 60+ seconds. AI Studio builds default to 600s.
- AI Studio for unrestricted Gemini -
surf aistudio gives less filtered responses than surf gemini for the same models
- 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 - Use
window.new + --window-id or --tab-id to keep agent work separate from your browsing
- Request lock - Non-streaming browser CLI requests serialize per socket; use
--no-lock only when you intentionally want to bypass it
- Native host diagnostics - If commands fail with socket/native-host errors, run
surf doctor or surf doctor --browser all before guessing at reinstall steps
- Animation capture - Use
surf record --duration 2000 --fps 10 --output /tmp/anim.gif when the agent needs to see motion; use animate-audit for numeric timelines and perf-audit for jank/layout-shift snapshots
- Hard isolation - Use separate browser/profile instances plus separate
SURF_SOCKET values when agents must not share a host or target
- Semantic locators -
locate.role, locate.text, locate.label for more 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