{"id":47873861,"url":"https://github.com/nicobailon/surf-cli","last_synced_at":"2026-04-04T01:02:11.359Z","repository":{"id":331519176,"uuid":"1124070604","full_name":"nicobailon/surf-cli","owner":"nicobailon","description":"The CLI for AI agents to control Chrome. Zero config, agent-agnostic, battle-tested.","archived":false,"fork":false,"pushed_at":"2026-02-21T21:24:40.000Z","size":2142,"stargazers_count":177,"open_issues_count":13,"forks_count":19,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-22T02:27:26.331Z","etag":null,"topics":["ai-agents","browser-automation","chrome","cli","devtools"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nicobailon.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-12-28T09:05:24.000Z","updated_at":"2026-02-22T00:32:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/nicobailon/surf-cli","commit_stats":null,"previous_names":["nicobailon/surf-cli"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/nicobailon/surf-cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicobailon%2Fsurf-cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicobailon%2Fsurf-cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicobailon%2Fsurf-cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicobailon%2Fsurf-cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nicobailon","download_url":"https://codeload.github.com/nicobailon/surf-cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicobailon%2Fsurf-cli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31383636,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-03T23:20:52.058Z","status":"ssl_error","status_checked_at":"2026-04-03T23:20:51.675Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","browser-automation","chrome","cli","devtools"],"created_at":"2026-04-04T01:01:26.900Z","updated_at":"2026-04-04T01:02:11.285Z","avatar_url":"https://github.com/nicobailon.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp\u003e\n  \u003cimg src=\"surf-banner.png\" alt=\"surf\" width=\"1100\"\u003e\n\u003c/p\u003e\n\n# Surf\n\n**The CLI for AI agents to control Chrome. Zero config, agent-agnostic, battle-tested.**\n\n[![npm version](https://img.shields.io/npm/v/surf-cli?style=for-the-badge)](https://www.npmjs.com/package/surf-cli)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](LICENSE)\n[![Platform](https://img.shields.io/badge/Platform-macOS%20%7C%20Linux%20%7C%20Windows-blue?style=for-the-badge)]()\n\n\u003e **v2.6.0** — AI Studio support (`surf aistudio`, `surf aistudio.build`), Windows support, Helium browser support, env var overrides. See [CHANGELOG](CHANGELOG.md).\n\n```bash\nsurf go \"https://example.com\"\nsurf read\nsurf click e5\nsurf snap\n```\n\n## Why Surf\n\nBrowser automation for AI agents is harder than it looks. Most tools require complex setup, tie you to specific AI providers, or break on real-world pages.\n\nSurf takes a different approach:\n\n**Agent-Agnostic** - Pure CLI commands over Unix socket. Works with Claude Code, GPT, Gemini, Cursor, custom agents, shell scripts - anything that can run commands.\n\n**Zero Config** - Install the extension, run commands. No MCP servers to configure, no relay processes, no subscriptions.\n\n**Battle-Tested** - Built by reverse-engineering production browser extensions and methodically working through agent-hostile pages like Discord settings. Falls back gracefully when CDP fails.\n\n**Smart Defaults** - Screenshots auto-resize to 1200px (saves tokens). Actions auto-capture screenshots (saves round-trips). Errors on restricted pages warn instead of fail.\n\n**AI Without API Keys** - Query ChatGPT, Gemini, Perplexity, and Grok using your existing browser logins. No API keys needed.\n\n**Network Capture** - Automatically logs all network requests while active. Filter, search, and replay API calls without manually setting up request interception.\n\n## Comparison\n\n| Feature | Surf | Manus | Claude Extension | DevTools MCP | dev-browser |\n|---------|------|-------|------------------|--------------|-------------|\n| Agent-agnostic | Yes | No (Manus only) | No (Claude only) | Partial | No (Claude skill) |\n| Zero config | Yes | No (subscription) | No (subscription) | No (MCP setup) | No (relay server) |\n| Local-only | Yes | No (cloud) | Partial | Yes | Partial |\n| CLI interface | Yes | No | No | No | No |\n| Free | Yes | No | No | Yes | Yes |\n| AI via browser cookies | Yes | No | No | No | No |\n\n## Installation\n\n### Quick Start\n\n```bash\n# 1. Install globally\nnpm install -g surf-cli\n\n# 2. Load extension in Chrome\n#    - Open chrome://extensions\n#    - Enable \"Developer mode\"\n#    - Click \"Load unpacked\"\n#    - Paste the path from: surf extension-path\n\n# 3. Install native host (copy extension ID from chrome://extensions)\nsurf install \u003cextension-id\u003e\n\n# 4. Restart Chrome and test\nsurf tab.list\n```\n\n### Multi-Browser Support\n\n```bash\nsurf install \u003cextension-id\u003e                    # Chrome (default)\nsurf install \u003cextension-id\u003e --browser brave    # Brave\nsurf install \u003cextension-id\u003e --browser helium   # Helium\nsurf install \u003cextension-id\u003e --browser all      # All supported browsers\n```\n\nSupported: `chrome`, `chromium`, `brave`, `edge`, `arc`, `helium`\n\n**Package Manager Installs (Nix, Homebrew, etc.)**\nIf surf is installed via a package manager that stores binaries in non-standard locations, set these environment variables before running `surf install`:\n```bash\nexport SURF_NODE_PATH=/path/to/node\nexport SURF_HOST_PATH=/path/to/native/host.cjs\nexport SURF_EXTENSION_PATH=/path/to/extension/dist\n```\nSee [Environment Variables](#environment-variables) for details.\n\n### Uninstall\n\n```bash\nsurf uninstall                  # Chrome only\nsurf uninstall --all            # All browsers + wrapper files\n```\n\n### Development Setup\n\n```bash\ngit clone https://github.com/nicobailon/surf-cli.git\ncd surf-cli\nnpm install\nnpm run build\n# Then load dist/ as unpacked extension\n```\n\n## Usage\n\n```bash\nsurf \u003ccommand\u003e [args] [options]\nsurf --help                    # Basic help\nsurf --help-full               # All 50+ commands\nsurf \u003ccommand\u003e --help          # Command details\nsurf --find \u003cquery\u003e            # Search commands\n```\n\n### Navigation\n\n```bash\nsurf go \"https://example.com\"\nsurf back\nsurf forward\nsurf tab.reload --hard\n```\n\n### Reading Pages\n\n```bash\nsurf read                           # Accessibility tree + visible text content\nsurf read --no-text                 # Accessibility tree only (no text)\nsurf read --depth 3                 # Limit tree depth (smaller output)\nsurf read --compact                 # Remove empty structural elements\nsurf read --depth 3 --compact       # Both (60% smaller output)\nsurf page.text                      # Raw text content only\nsurf page.state                     # Modals, loading state, scroll position\n```\n\nElement refs (`e1`, `e2`, `e3`...) are stable identifiers from the accessibility tree - semantic, predictable, and resilient to DOM changes.\n\n### Semantic Locators\n\nFind and interact with elements by role, text, or label - no refs or selectors needed:\n\n```bash\n# By ARIA role\nsurf locate.role button --name \"Submit\"           # Find button\nsurf locate.role button --name \"Submit\" --action click  # Find and click\nsurf locate.role textbox --action fill --value \"hello\"  # Find and fill\nsurf locate.role link --all                       # List all links\n\n# By text content  \nsurf locate.text \"Sign In\" --action click         # Click element with text\nsurf locate.text \"Accept\" --exact                 # Exact match only\n\n# By form label\nsurf locate.label \"Email\" --action fill --value \"test@example.com\"\n```\n\n### Iframe Support\n\nWork with content inside iframes:\n\n```bash\nsurf frame.list                     # List all frames\nsurf frame.switch --index 0         # Switch to first iframe\nsurf frame.switch --name \"payment\"  # Switch by frame name\nsurf frame.switch --selector \"#checkout-frame\"  # Switch by CSS selector\n\n# Now all commands target the iframe\nsurf read                           # Read iframe content\nsurf click e5                       # Click in iframe\nsurf locate.role button --action click\n\nsurf frame.main                     # Return to main page\n```\n\n### Interaction\n\n```bash\nsurf click e5                       # Click by element ref\nsurf click --selector \".btn\"        # Click by CSS selector\nsurf click 100 200                  # Click by coordinates\nsurf type \"hello\" --submit          # Type and press Enter\nsurf type \"email@example.com\" --ref e12  # Type into specific element\nsurf key Escape                     # Press key\nsurf scroll.bottom                  # Scroll to bottom\n```\n\n### Forms\n\nSelect options in dropdown menus:\n\n```bash\nsurf select e5 \"US\"                         # Select by value\nsurf select \"#country\" \"US\"                 # Select by CSS selector\nsurf select e5 \"opt1\" \"opt2\"                # Multi-select\nsurf select e5 --by label \"United States\"   # Select by visible text\nsurf select e5 --by index 0                 # Select first option\n```\n\n### Element Inspection\n\nGet computed styles from elements:\n\n```bash\nsurf element.styles e5              # Get styles by ref\nsurf element.styles \".header\"       # Get styles by CSS selector (can return multiple)\n```\n\nReturns font, color, background, border, padding, and bounding box for design debugging.\n\n### Screenshots\n\nScreenshots auto-save to `/tmp` by default (optimized for AI agents):\n\n```bash\nsurf screenshot                             # Auto-saves to /tmp/surf-snap-*.png\nsurf screenshot --output /tmp/shot.png      # Save to specific path\nsurf screenshot --full --output /tmp/hd.png # Full resolution (skip resize)\nsurf screenshot --annotate                  # With element labels\nsurf screenshot --fullpage                  # Entire page\nsurf screenshot --no-save                   # Return base64 + ID only (no file)\nsurf snap                                   # Alias for screenshot\n```\n\nTo disable auto-save globally, set `autoSaveScreenshots: false` in `surf.json`.\n\nActions like `click`, `type`, and `scroll` automatically capture a screenshot after execution - no extra command needed.\n\n### Tabs\n\n```bash\nsurf tab.list\nsurf tab.new \"https://example.com\"\nsurf tab.switch 123\nsurf tab.close 123\nsurf tab.name \"dashboard\"           # Name current tab\nsurf tab.switch \"dashboard\"         # Switch by name\nsurf tab.group --name \"Work\" --color blue\n```\n\n### Window Isolation\n\nKeep using your browser while the agent works in a separate window:\n\n```bash\n# Create isolated window for agent\nsurf window.new \"https://example.com\"\n# Returns: Window 123456 (tab 789)\n\n# All subsequent commands target that window\nsurf click e5 --window-id 123456\nsurf read --window-id 123456\nsurf tab.new \"https://other.com\" --window-id 123456\n\n# Or manage windows directly\nsurf window.list                    # List all windows\nsurf window.list --tabs             # Include tab details\nsurf window.focus 123456            # Bring window to front\nsurf window.close 123456            # Close window\n```\n\n### Device Emulation\n\nTest responsive designs and mobile layouts:\n\n```bash\nsurf emulate.device --list                    # Show available devices\nsurf emulate.device \"iPhone 14\"               # Emulate iPhone 14\nsurf emulate.device \"Pixel 7\"                 # Emulate Pixel 7\nsurf emulate.device reset                     # Return to desktop\n\n# Custom viewport\nsurf emulate.viewport --width 375 --height 812\nsurf emulate.viewport --width 1920 --height 1080 --scale 2\n\n# Touch emulation\nsurf emulate.touch                            # Enable touch\nsurf emulate.touch --enabled false            # Disable touch\n```\n\nAvailable devices: iPhone 12-14 (Pro/Max), iPhone SE, iPad (Pro/Mini), Pixel 5-7 (Pro), Galaxy S21-S23, Galaxy Tab S7, Nest Hub (Max).\n\n### Performance Tracing\n\nCapture performance metrics and traces:\n\n```bash\nsurf perf.metrics                   # Current performance metrics\nsurf perf.start                     # Start tracing\nsurf perf.stop                      # Stop and get trace data\n```\n\n### AI Queries (No API Keys)\n\nQuery AI models using your browser's logged-in session:\n\n```bash\n# ChatGPT\nsurf chatgpt \"explain this code\"\nsurf chatgpt \"summarize\" --with-page     # Include page context\nsurf chatgpt \"analyze\" --model gpt-4o    # Specify model\nsurf chatgpt \"review\" --file code.ts     # Attach file\n\n# Gemini\nsurf gemini \"explain quantum computing\"\nsurf gemini \"summarize\" --with-page                           # Include page context\nsurf gemini \"analyze\" --file data.csv                         # Attach file\nsurf gemini \"a robot surfing\" --generate-image /tmp/robot.png # Generate image\nsurf gemini \"add sunglasses\" --edit-image photo.jpg --output out.jpg\nsurf gemini \"summarize\" --youtube \"https://youtube.com/...\"   # YouTube analysis\nsurf gemini \"hello\" --model gemini-2.5-flash                  # Model selection\n\n# Perplexity\nsurf perplexity \"what is quantum computing\"\nsurf perplexity \"explain this page\" --with-page               # Include page context\nsurf perplexity \"deep dive\" --mode research                   # Research mode (Pro)\nsurf perplexity \"latest news\" --model sonar                   # Model selection (Pro)\n\n# Grok (queries x.com/i/grok using your X.com login)\nsurf grok \"what are the latest AI agent trends on X\"          # Search X posts\nsurf grok \"analyze @username recent activity\"                 # Profile analysis\nsurf grok \"summarize this page\" --with-page                   # Include page context\nsurf grok \"find viral AI posts\" --deep-search                 # DeepSearch mode\nsurf grok \"quick question\" --model fast                       # Models: auto, fast, expert, thinking\nsurf grok --validate                                          # Check UI and available models\nsurf grok --validate --save-models                            # Save discovered models to settings\n\n# AI Studio (queries aistudio.google.com using your Google login)\nsurf aistudio \"explain quantum computing\"\nsurf aistudio \"redteam this\" --with-page                      # Include page context\nsurf aistudio \"quick answer\" --model gemini-3-flash-preview   # Model selection\n\n# AI Studio App Builder (generates full web apps from a prompt)\nsurf aistudio.build \"build a portfolio site\"\nsurf aistudio.build \"todo app\" --model gemini-3.1-pro-preview # Model override\nsurf aistudio.build \"crm dashboard\" --output ./out            # Extract zip to directory\nsurf aistudio.build \"game\" --keep-open --timeout 600          # Keep tab open, 10min timeout\n```\n\nEach AI tool uses your existing browser login - no API keys needed. Just be logged into the respective service in Chrome (chatgpt.com, gemini.google.com, perplexity.ai, x.com, or aistudio.google.com).\n\n**Grok troubleshooting:** If queries fail, run `surf grok --validate` to check if the UI structure changed. Use `--save-models` to update the model cache in `surf.json`. Default model is \"thinking\" (Grok 4.1 Thinking).\n\n### Waiting\n\n```bash\nsurf wait 2                         # Wait 2 seconds\nsurf wait.element \".loaded\"         # Wait for element\nsurf wait.network                   # Wait for network idle\nsurf wait.url \"/dashboard\"          # Wait for URL pattern\n```\n\n### Other\n\n```bash\nsurf js \"return document.title\"     # Execute JavaScript\nsurf search \"login\"                 # Find text in page\nsurf cookie.list                    # List cookies\nsurf zoom 1.5                       # Set zoom to 150%\nsurf console                        # Read console messages\nsurf network                        # Read network requests\n```\n\n### Network Capture\n\nSurf automatically captures all network requests while active. No explicit start needed.\n\n```bash\n# Overview (token-efficient for LLMs)\nsurf network                          # Recent requests, compact table\nsurf network --urls                   # Just URLs (minimal output)\nsurf network --format curl            # As curl commands\n\n# Filtering\nsurf network --origin api.github.com  # Filter by origin/domain\nsurf network --method POST            # Only POST requests\nsurf network --type json              # Only JSON responses\nsurf network --status 4xx,5xx         # Only errors\nsurf network --since 5m               # Last 5 minutes\nsurf network --exclude-static         # Skip images/fonts/css/js\n\n# Drill down\nsurf network.get r_001                # Full request/response details\nsurf network.body r_001               # Response body (for piping to jq)\nsurf network.curl r_001               # Generate curl command\nsurf network.origins                  # List captured domains\n\n# Management\nsurf network.clear                    # Clear captured data\nsurf network.stats                    # Capture statistics\n```\n\nStorage location: `/tmp/surf/` (override with `--network-path` or `SURF_NETWORK_PATH` env).\nAuto-cleanup: 24 hours TTL, 200MB max.\n\n### Workflows\n\nExecute multi-step browser automation as a single command:\n\n```bash\n# Inline workflow (pipe-separated)\nsurf do 'go \"https://example.com\" | click e5 | screenshot'\n\n# Multi-step login flow\nsurf do 'go \"https://example.com/login\" | type \"user@example.com\" --selector \"#email\" | type \"pass\" --selector \"#password\" | click --selector \"button[type=submit]\"'\n\n# From JSON file\nsurf do --file workflow.json\n\n# Run named workflow with arguments\nsurf do my-workflow --url \"https://example.com\" --max_items 10\n\n# Validate without executing\nsurf do 'go \"url\" | click e5 | screenshot' --dry-run\n```\n\n**Why workflows?** Instead of 6-8 separate CLI calls with LLM orchestration between each step, a workflow executes deterministically with smart auto-waits. Faster, cheaper, and more reliable.\n\n**Options:**\n- `--file`, `-f` - Load workflow from JSON file\n- `--dry-run` - Parse and validate without executing\n- `--on-error stop|continue` - Error handling (default: stop)\n- `--step-delay \u003cms\u003e` - Delay between steps (default: 100, use 0 to disable)\n- `--no-auto-wait` - Disable automatic waits between steps\n- `--json` - Output structured JSON result\n- `--\u003carg\u003e \u003cvalue\u003e` - Pass arguments to workflow (e.g., `--url \"...\"`)\n\n**Auto-waits:** Commands that trigger page changes automatically wait for completion:\n- Navigation (`go`, `back`, `forward`) → waits for page load\n- Clicks, key presses, form fills → waits for DOM stability\n- Tab switches → waits for tab to load\n\n#### Workflow Files\n\nWorkflows can be saved as JSON files and run by name. Place them in `~/.surf/workflows/` (user) or `./.surf/workflows/` (project).\n\n**Basic format:**\n```json\n{\n  \"name\": \"login-flow\",\n  \"description\": \"Log into example.com\",\n  \"args\": {\n    \"email\": { \"required\": true, \"desc\": \"Login email\" },\n    \"password\": { \"required\": true, \"desc\": \"Login password\" }\n  },\n  \"steps\": [\n    { \"tool\": \"navigate\", \"args\": { \"url\": \"https://example.com/login\" } },\n    { \"tool\": \"type\", \"args\": { \"text\": \"%{email}\", \"selector\": \"input[name=email]\" } },\n    { \"tool\": \"type\", \"args\": { \"text\": \"%{password}\", \"selector\": \"input[name=password]\" } },\n    { \"tool\": \"click\", \"args\": { \"selector\": \"button[type=submit]\" } }\n  ]\n}\n```\n\n**Step outputs** - Capture results for use in later steps:\n```json\n{\n  \"steps\": [\n    { \"tool\": \"js\", \"args\": { \"code\": \"return document.title\" }, \"as\": \"title\" },\n    { \"tool\": \"js\", \"args\": { \"code\": \"return 'Page: ' + '%{title}'\" } }\n  ]\n}\n```\n\n**Loops** - `repeat` for fixed iterations, `each` for arrays:\n```json\n{\n  \"steps\": [\n    { \"tool\": \"js\", \"args\": { \"code\": \"return ['a', 'b', 'c']\" }, \"as\": \"items\" },\n    {\n      \"each\": \"%{items}\",\n      \"as\": \"item\",\n      \"steps\": [\n        { \"tool\": \"js\", \"args\": { \"code\": \"return 'Processing: %{item}'\" } }\n      ]\n    }\n  ]\n}\n```\n\n```json\n{\n  \"steps\": [\n    {\n      \"repeat\": 5,\n      \"steps\": [\n        { \"tool\": \"scroll\", \"args\": { \"direction\": \"down\" } },\n        { \"tool\": \"wait\", \"args\": { \"duration\": 500 } }\n      ]\n    }\n  ]\n}\n```\n\n**Loop with exit condition** - Stop early when condition is met:\n```json\n{\n  \"repeat\": 20,\n  \"until\": { \"tool\": \"js\", \"args\": { \"code\": \"return !document.querySelector('.next-page')\" } },\n  \"steps\": [\n    { \"tool\": \"click\", \"args\": { \"selector\": \".next-page\" } },\n    { \"tool\": \"wait.load\" }\n  ]\n}\n```\n\n#### Workflow Management\n\n```bash\n# List available workflows\nsurf workflow.list\n\n# Show workflow details and arguments\nsurf workflow.info my-workflow\n\n# Validate workflow JSON\nsurf workflow.validate ./my-workflow.json\n```\n\n**Supported commands:** All surf commands work in workflows. Use aliases (`go`, `snap`, `read`) or full names (`navigate`, `screenshot`, `page.read`).\n\n## Global Options\n\n```bash\n--tab-id \u003cid\u003e      # Target specific tab\n--window-id \u003cid\u003e   # Target specific window (isolate agent from your browsing)\n--json             # Output raw JSON\n--soft-fail        # Warn instead of error (exit 0) on restricted pages\n--no-screenshot    # Skip auto-screenshot after actions\n--full             # Full resolution screenshots (skip resize)\n--network-path \u003cpath\u003e  # Custom path for network logs (default: /tmp/surf, or SURF_NETWORK_PATH env)\n```\n\n## Environment Variables\n\n```bash\nSURF_NETWORK_PATH         # Path for network capture logs (default: /tmp/surf)\nSURF_NODE_PATH            # Path to node binary (for native host wrapper)\nSURF_HOST_PATH            # Path to native/host.cjs (for native host wrapper)\nSURF_EXTENSION_PATH       # Path to extension dist/ directory\n```\n\n**Use cases:**\n- `SURF_NODE_PATH` / `SURF_HOST_PATH`: Package manager installs (e.g., Nix) that store binaries in non-standard locations\n- `SURF_EXTENSION_PATH`: Package managers that create stable symlinks instead of changing paths on reinstall\n\n**Example (Nix):**\n```bash\nexport SURF_NODE_PATH=~/.local/share/surf-cli/node\nexport SURF_HOST_PATH=~/.local/share/surf-cli/native/host.cjs\nexport SURF_EXTENSION_PATH=~/.local/share/surf-cli/extension\n```\n\n## Socket API\n\nFor programmatic integration, send JSON to `/tmp/surf.sock`:\n\n```bash\necho '{\"type\":\"tool_request\",\"method\":\"execute_tool\",\"params\":{\"tool\":\"tab.list\",\"args\":{}},\"id\":\"1\"}' | nc -U /tmp/surf.sock\n```\n\n### Protocol Reference\n\n**Request:**\n```json\n{\n  \"type\": \"tool_request\",\n  \"method\": \"execute_tool\",\n  \"params\": {\n    \"tool\": \"click\",\n    \"args\": { \"ref\": \"e5\" }\n  },\n  \"id\": \"unique-request-id\",\n  \"tabId\": 123,\n  \"windowId\": 456\n}\n```\n\n**Success Response:**\n```json\n{\n  \"type\": \"tool_response\",\n  \"id\": \"unique-request-id\",\n  \"result\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"Result message\" }]\n  }\n}\n```\n\n**Error Response:**\n```json\n{\n  \"type\": \"tool_response\",\n  \"id\": \"unique-request-id\",\n  \"error\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"Error message\" }]\n  }\n}\n```\n\n## Command Groups\n\n| Group | Commands |\n|-------|----------|\n| `workflow` | `do`, `workflow.list`, `workflow.info`, `workflow.validate` |\n| `window.*` | `new`, `list`, `focus`, `close`, `resize` |\n| `tab.*` | `list`, `new`, `switch`, `close`, `name`, `unname`, `named`, `group`, `ungroup`, `groups`, `reload` |\n| `scroll.*` | `top`, `bottom`, `to`, `info` |\n| `page.*` | `read`, `text`, `state` |\n| `locate.*` | `role`, `text`, `label` |\n| `element.*` | `styles` |\n| `frame.*` | `list`, `switch`, `main`, `js` |\n| `wait.*` | `element`, `network`, `url`, `dom`, `load` |\n| `cookie.*` | `list`, `get`, `set`, `clear` |\n| `bookmark.*` | `add`, `remove`, `list` |\n| `history.*` | `list`, `search` |\n| `dialog.*` | `accept`, `dismiss`, `info` |\n| `emulate.*` | `network`, `cpu`, `geo`, `device`, `viewport`, `touch` |\n| `perf.*` | `start`, `stop`, `metrics` |\n| `network.*` | `get`, `body`, `curl`, `origins`, `clear`, `stats`, `export`, `path` |\n\n## Aliases\n\n| Alias | Command |\n|-------|---------|\n| `snap` | `screenshot` |\n| `read` | `page.read` |\n| `find` | `search` |\n| `go` | `navigate` |\n\n## How It Works\n\n```\nCLI (surf) → Unix Socket → Native Host → Chrome Extension → CDP/Scripting API\n```\n\nSurf uses Chrome DevTools Protocol for most operations, with automatic fallback to `chrome.scripting` API when CDP is unavailable (restricted pages, certain contexts). Screenshots fall back to `captureVisibleTab` when CDP capture fails.\n\n## Limitations\n\n- Cannot automate `chrome://` pages or the Chrome Web Store (Chrome restriction)\n- First CDP operation on a new tab takes ~100-500ms (debugger attachment)\n- Some operations on restricted pages return warnings instead of results\n\n## Linux Support (Experimental)\n\nSurf should work on Linux with Chromium. Not yet tested in production.\n\n```bash\n# Install dependencies\nsudo apt install chromium-browser nodejs npm imagemagick\n\n# For headless server: add Xvfb + VNC\nsudo apt install xvfb tigervnc-standalone-server\n\n# Install Surf and native host\nnpm install -g surf-cli\nsurf install \u003cextension-id\u003e --browser chromium\n```\n\n**Notes:**\n- Use Chromium (no official Chrome for Linux ARM64)\n- Screenshot resize uses ImageMagick instead of macOS `sips`\n- Headless servers need Xvfb + VNC for initial login setup\n\n## AI Agent Integration\n\nSurf includes a skill file for AI coding agents like [Pi](https://github.com/badlogic/pi-mono):\n\n```bash\n# Symlink for auto-updates\nln -s \"$(pwd)/skills/surf\" ~/.pi/agent/skills/surf\n\n# Or copy\ncp -r skills/surf ~/.pi/agent/skills/\n```\n\nSee [`skills/README.md`](skills/README.md) for details.\n\n## Development\n\n```bash\nnpm run dev       # Watch mode\nnpm run build     # Production build\n```\n\nAfter changes:\n- **Extension** (`src/`): Reload at `chrome://extensions`\n- **Host** (`native/`): Restart `node native/host.cjs`\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnicobailon%2Fsurf-cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnicobailon%2Fsurf-cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnicobailon%2Fsurf-cli/lists"}