Skip to content

Repository files navigation

Chrome Skill — Browser Automation for Claude Code

General-purpose Chrome automation from the terminal. Control any tab, fill any form, read any page.

Architecture

Claude Code → chrome.sh (CLI) → HTTP daemon (:7866) → WebSocket → Chrome MV3 Extension → Any webpage
Layer File Role
CLI scripts/chrome.sh Bash wrapper — parses args, curls daemon
Daemon daemon/server.py Starlette HTTP server, WebSocket hub, request correlation
Extension extension/ MV3 service worker — executes JS, captures screenshots, manages tabs
JS Helpers lib/ Injected on-demand — cursor, forms, extract, wait

Setup

1. Start the daemon

# Option A: launchd (auto-start on login)
cp com.agent-chrome.daemon.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.agent-chrome.daemon.plist

# Option B: manual
uv run --python 3.12 --with starlette --with uvicorn --with websockets python3 daemon/server.py

2. Install the extension

  1. Open chrome://extensions/
  2. Enable Developer mode
  3. Click Load unpacked → select the extension/ directory inside your skill install path
  4. Verify connection: scripts/chrome.sh health

3. Verify

scripts/chrome.sh health                    # Should show extension: connected
scripts/chrome.sh tabs                      # Lists open tabs
scripts/chrome.sh exec "document.title"     # Returns active tab's title

Quick Reference

# Navigation
chrome.sh navigate "https://example.com"
chrome.sh back
chrome.sh forward
chrome.sh scroll --to bottom

# Reading
chrome.sh read --mode structured            # JSON: title, headings, tables, links
chrome.sh read --mode md                    # Markdown conversion
chrome.sh read --selector "h1"              # Specific element

# Interaction
chrome.sh click "button.submit"             # Click element
chrome.sh click "button.submit" --human     # Animated cursor + click
chrome.sh type "#email" "user@test.com"     # Type into field
chrome.sh forms                             # Detect all form fields
chrome.sh fill '{"fields":{"#email":"x@y.com","#name":"Test User"}}'

# Capture
chrome.sh screenshot --save /tmp/page.png
chrome.sh pdf --save /tmp/page.pdf

# Tabs
chrome.sh tabs                              # List all
chrome.sh tab:open "https://example.com"    # Open new
chrome.sh tab:focus 12345                   # Switch to tab
chrome.sh tab:close 12345                   # Close tab

# Waiting
chrome.sh wait ".results" --timeout 10      # Wait for element

File Structure

agent-chrome/
├── SKILL.md                    # Claude Code skill interface
├── README.md                   # This file
├── .env.example                # CHROME_PORT=7866
├── daemon/
│   └── server.py               # HTTP + WebSocket daemon (PEP 723 inline deps)
├── scripts/
│   └── chrome.sh               # Bash CLI wrapper
├── extension/
│   ├── manifest.json           # MV3 manifest
│   ├── background.js           # Service worker
│   └── icons/                  # Extension icons
├── lib/
│   ├── cursor.js               # Human-like cursor animation (Bezier paths)
│   ├── forms.js                # Form detection + React-compatible filling
│   ├── extract.js              # Structured data extraction + markdown
│   └── wait.js                 # Element/condition waiting (MutationObserver)
├── com.agent-chrome.daemon.plist  # launchd auto-start (edit paths before use)
└── dashboard/
    └── index.html              # Status page at http://127.0.0.1:7866

Key Design Decisions

  • HTTP daemon + CLI, not MCP — matches the speak skill pattern. Simpler, more debuggable, works with any shell.
  • WebSocket to extension — bidirectional push needed. Keeps service worker alive.
  • world: "MAIN" for JS execution — access page JS, React state, framework APIs directly.
  • Async-aware execute — extension wraps code in async () => {} and tries expression-first, then statement fallback. Supports await in injected lib code (cursor, wait, etc.).
  • On-demand lib injection — cursor/forms/extract/wait only loaded when their endpoints are called. Keeps payloads small.
  • Dual API surface — libs export both namespaced (window.__claude_chrome_cursor.click()) and shorthand (window.__cursor_click()) APIs. Namespaced for direct execute calls; shorthand for daemon injection compatibility.
  • No hardcoded selectors — everything parameterized via CSS selectors and JS expressions. Works on any website.

Daemon API

Dashboard: http://127.0.0.1:7866

Endpoint Method Description
/health GET Status + extension connection state
/events GET SSE stream for dashboard
/execute POST Run arbitrary JS in a tab
/navigate POST Go to URL
/click POST Click element (optional human: true)
/type POST Type text (optional human: true)
/read POST Read page — modes: text, html, md, structured, selector
/read/forms POST Detect all forms + fields
/fill POST Fill form fields (React-compatible)
/submit POST Submit form
/extract POST Extract tables, links, images, headings
/screenshot POST Capture visible tab as PNG
/pdf POST Generate PDF via debugger protocol
/tabs GET List open tabs
/tabs/open POST Open new tab
/tabs/close POST Close tab
/tabs/focus POST Switch to tab
/tabs/reload POST Reload tab
/wait POST Wait for selector/condition/navigation
/cookies GET Read cookies for domain
/storage GET Read localStorage/sessionStorage
/back POST History back
/forward POST History forward
/scroll POST Scroll page

All POST endpoints accept optional tab or tabId param (Chrome tab ID integer). Defaults to active tab.

WebSocket Protocol

Daemon → Extension: {"id": "req_xxx", "action": "execute"|"screenshot"|..., "params": {...}} Extension → Daemon: {"id": "req_xxx", "ok": true|false, "result": ..., "elapsed": 42} Extension push events: {"event": "tab_updated"|"tab_removed"|"tab_created"|"connected", "data": {...}}

Security

  • Localhost only — daemon binds to 127.0.0.1, origin guard rejects non-local POST requests.
  • Save path validation/screenshot and /pdf save param restricted to $HOME or /tmp.
  • No remote code — all JS executed locally via chrome.scripting.executeScript in MAIN world.

Troubleshooting

Symptom Fix
health shows extension disconnected Check chrome://extensions — is it enabled? Reload it.
Daemon not running launchctl load ~/Library/LaunchAgents/com.agent-chrome.daemon.plist
JS execution fails on chrome:// pages Chrome blocks script injection on internal pages. Use regular web pages.
Form fill doesn't trigger React state Should work — uses native property descriptor + event dispatch. File a bug if not.
Screenshot is blank Tab must be visible (not minimized). Use tab:focus first.

About

Chrome browser automation skill for Claude Code — control tabs, fill forms, read pages, screenshot, and more

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages