Skill: huaerye23/browser-automation-skills (version 1.0.0)

--- slug: browser-automation-skills display-name: Browser Automation Skills version: 1.0.0 description: Browser automation skills for AI models — navigate, screenshot, interact, scrape, debug, test, and record browser sessions. Controls local Google Chrome via CDP with visual overlay locking. 浏览器自动化技能包，让 AI 模型控制本地 Chrome。 license: MIT-0 --- # Browser Automation Skills / 浏览器自动化技能包 A set of Skills that teach AI models how to control the local Google Chrome browser. Works with any model — via built-in `browser_subagent` or the bundled Playwright CLI script. 一组教会 AI 模型控制本地 Google Chrome 的技能包。 适用于任何模型 — 通过内置 `browser_subagent` 或自带 Playwright CLI 脚本。 ## Included Skills / 包含的技能 | Skill | Description / 说明 | Invocation / 调用 | |-------|-----------|------------| | `navigate` | Open URLs, read page content / 打开网页、读取内容 | Auto + `/navigate` | | `screenshot` | Capture visuals / 截图截屏 | Auto + `/screenshot` | | `interact` | Click, type, fill forms / 点击、输入、表单 | Auto + `/interact` | | `scrape` | Extract structured data / 数据抓取 | Auto + `/scrape` | | `debug` | Inspect network, console / 网页调试 | Auto + `/debug` | | `test` | Automated QA / 自动化测试 | `/test` only | | `record` | Record sessions / 录制操作 | `/record` only | | `browser-context` | API reference / API 参考 | Model only | ## Prerequisites / 前提条件 - **Google Chrome** installed locally / 本地安装 Chrome - For Playwright CLI: `pip install playwright` - Start Chrome: `chrome.exe --remote-debugging-port=9222` ## Quick Start / 快速开始 ```bash python scripts/browser.py status python scripts/browser.py navigate https://example.com python scripts/browser.py screenshot python scripts/browser.py lock # Visual overlay, block user input python scripts/browser.py unlock # Remove overlay, restore input ``` See [docs/api-reference.md](docs/api-reference.md) for full API documentation.

path: README.md
content: # Browser Automation Skills / 浏览器自动化技能包

> **[English](#english)** | **[中文](#中文)**

Browser Automation Skills for AI models — control local Google Chrome from any AI coding assistant.

浏览器自动化技能包 — 让任何 AI 编码助手都能控制本地 Google Chrome 浏览器。

## Highlights / 亮点

- 🌐 **Navigate** — open URLs, read page content / 打开网页、读取内容
- 📸 **Screenshot** — viewport, full-page, responsive / 截图截屏
- 🖱️ **Interact** — click, type, fill forms, scroll / 点击、输入、表单
- 🕷️ **Scrape** — extract text, tables, structured data / 数据抓取
- 🔍 **Debug** — network, console, DOM inspection / 网页调试
- 🧪 **Test** — automated QA with PASS/FAIL / 自动化测试
- 🎬 **Record** — capture sessions as video / 录制操作视频
- 🚫 **No detection banner** — connects to existing Chrome via CDP / 无检测横幅

---

<a id="english"></a>

## English

A set of Skills that teach AI models how to control the local Google Chrome browser. Works with **any model** — uses `browser_subagent` on Antigravity, or the bundled Playwright CLI script on any other platform with terminal access.

### Two Backends, Same Skills

| Backend | Platform | How It Works |
|---------|----------|-------------|
| **Antigravity** | Antigravity IDE | Built-in `browser_subagent` → Chrome DevTools Protocol → Chrome |
| **Playwright CLI** | Any model with terminal access | `scripts/browser.py` → Playwright → Chrome DevTools Protocol → Chrome |

Both connect to your **existing Chrome** via CDP — no new browser instance, no "controlled by automated software" banner.

### Prerequisites

- **Google Chrome** installed locally
- For Antigravity: nothing extra (built-in)
- For other platforms: `pip install playwright` (no `playwright install` needed — we use your existing Chrome)

### Quick Start

```bash
# 1. Start Chrome with remote debugging
chrome.exe --remote-debugging-port=9222

# 2. Install dependency
pip install playwright

# 3. Try it
python scripts/browser.py status
python scripts/browser.py navigate https://example.com
python scripts/browser.py screenshot
```

### Skills

| Skill | What It Does | Invocation |
|-------|-------------|------------|
| `navigate` | Open URLs, read page content | Auto + `/navigate` |
| `screenshot` | Capture visuals (viewport, full-page, mobile) | Auto + `/screenshot` |
| `interact` | Click, type, fill forms, scroll, drag | Auto + `/interact` |
| `scrape` | Extract text, tables, structured data | Auto + `/scrape` |
| `debug` | Inspect network, console, DOM errors | Auto + `/debug` |
| `test` | Automated QA with PASS/FAIL reports | `/test` only |
| `record` | Record sessions as WebP video | `/record` only |
| `browser-context` | Backend detection + API reference | Model only |

### Installation

#### Option 1: Project-level (recommended)
```bash
cp -r browser-automation-skills/ .claude/plugins/browser-automation-skills/
```

#### Option 2: User-level (global)
```bash
cp -r browser-automation-skills/skills/* ~/.claude/skills/
```

#### Option 3: Additional directory
```bash
claude --add-dir /path/to/browser-automation-skills
```

### How It Works

```
User request (e.g., "screenshot example.com")
    │
    ▼
AI Model — matches request → Skill (e.g., screenshot)
    │
    ├─ Has browser_subagent? ──→ Antigravity backend
    │                               └→ Chrome (via CDP)
    │
    └─ Has terminal access? ──→ scripts/browser.py
                                    └→ Playwright → Chrome (via CDP)
    │
    ▼
Result → verified via screenshot → reported to user
```

### Directory Structure

```
browser-automation-skills/
├── README.md
├── scripts/
│   └── browser.py                  ← Playwright CLI adapter (universal backend)
├── docs/
│   └── api-reference.md            ← Full API reference (bilingual)
└── skills/
    ├── browser-context/SKILL.md    ← Backend detection + tool mapping
    ├── navigate/SKILL.md
    ├── screenshot/SKILL.md
    ├── interact/SKILL.md
    ├── scrape/SKILL.md
    ├── debug/SKILL.md
    ├── test/SKILL.md
    └── record/SKILL.md
```

### License

MIT

---

<a id="中文"></a>

## 中文

一组教会 AI 模型如何控制本地 Google Chrome 浏览器的技能包。适用于**任何模型** — 在 Antigravity 上使用内置工具，在其他平台上使用自带的 Playwright CLI 脚本。

### 双后端，同一套 Skills

| 后端 | 平台 | 工作方式 |
|------|------|---------|
| **Antigravity** | Antigravity IDE | 内置 `browser_subagent` → Chrome DevTools Protocol → Chrome |
| **Playwright CLI** | 任何有终端权限的模型 | `scripts/browser.py` → Playwright → Chrome DevTools Protocol → Chrome |

两种方式都连接你**已有的 Chrome** — 不启动新实例，无"正受到自动测试软件的控制"横幅。

### 前提条件

- 本地安装 **Google Chrome**
- Antigravity 环境：无需额外安装
- 其他平台：`pip install playwright`（无需 `playwright install` — 使用你已有的 Chrome）

### 快速开始

```bash
# 1. 启动 Chrome 调试模式
chrome.exe --remote-debugging-port=9222

# 2. 安装依赖
pip install playwright

# 3. 试一试
python scripts/browser.py status
python scripts/browser.py navigate https://example.com
python scripts/browser.py screenshot
```

### 技能列表

| 技能 | 功能 | 调用方式 |
|------|------|---------|
| `navigate` | 打开 URL、读取页面内容 | 自动 + `/navigate` |
| `screenshot` | 截图（视口/全页/移动端/自定义分辨率） | 自动 + `/screenshot` |
| `interact` | 点击、输入、表单填写、滚动、拖拽 | 自动 + `/interact` |
| `scrape` | 提取文本、表格、结构化数据 | 自动 + `/scrape` |
| `debug` | 检查网络请求、控制台日志、DOM 错误 | 自动 + `/debug` |
| `test` | 自动化 QA 测试，PASS/FAIL 报告 | 仅 `/test` |
| `record` | 录制浏览器操作为 WebP 视频 | 仅 `/record` |
| `browser-context` | 后端检测 + API 参考 | 仅模型 |

### 安装方法

#### 方式一：项目级（推荐）
```bash
cp -r browser-automation-skills/ .claude/plugins/browser-automation-skills/
```

#### 方式二：用户级（全局）
```bash
cp -r browser-automation-skills/skills/* ~/.claude/skills/
```

#### 方式三：额外目录加载
```bash
claude --add-dir /path/to/browser-automation-skills
```

### 工作原理

```
用户请求（如："截个图看看 example.com"）
    │
    ▼
AI 模型 — 匹配请求 → 技能（如 screenshot）
    │
    ├─ 有 browser_subagent？──→ Antigravity 后端
    │                              └→ Chrome（通过 CDP）
    │
    └─ 有终端权限？────────────→ scripts/browser.py
                                   └→ Playwright → Chrome（通过 CDP）
    │
    ▼
结果 → 通过截图验证 → 报告给用户
```

### 目录结构

```
browser-automation-skills/
├── README.md
├── scripts/
│   └── browser.py                  ← Playwright CLI 适配器（通用后端）
├── docs/
│   └── api-reference.md            ← 完整 API 参考（中英双语）
└── skills/
    ├── browser-context/SKILL.md    ← 后端检测 + 工具映射
    ├── navigate/SKILL.md           ← 导航
    ├── screenshot/SKILL.md         ← 截图
    ├── interact/SKILL.md           ← 交互
    ├── scrape/SKILL.md             ← 抓取
    ├── debug/SKILL.md              ← 调试
    ├── test/SKILL.md               ← 测试（仅手动）
    └── record/SKILL.md             ← 录制（仅手动）
```

### 许可证

MIT


path: scripts/browser.py
content: #!/usr/bin/env python3
"""
Browser control adapter — provides a unified CLI for browser automation.
Controls the user's existing Google Chrome via Chrome DevTools Protocol (CDP).

浏览器控制适配器 — 提供统一的命令行接口来控制浏览器。
通过 Chrome DevTools Protocol (CDP) 控制用户已有的 Google Chrome，无检测横幅。

=== Setup / 初始设置 ===

Step 1: Start Chrome with remote debugging enabled / 启动 Chrome 并启用远程调试:

  Windows:
    chrome.exe --remote-debugging-port=9222

  macOS:
    /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=9222

  Linux:
    google-chrome --remote-debugging-port=9222

  Or add --remote-debugging-port=9222 to your Chrome shortcut properties.
  或在 Chrome 快捷方式属性中添加 --remote-debugging-port=9222。

Step 2: Install dependency / 安装依赖:
    pip install playwright

Step 3: Use this script / 使用本脚本:
    python browser.py navigate <url>
    python browser.py screenshot [url] [--output path.png] [--full]
    python browser.py dom
    python browser.py text
    python browser.py click <x> <y>
    python browser.py type <text>
    python browser.py key <key_name>
    python browser.py scroll <direction> [amount]
    python browser.py resize <width> <height>
    python browser.py network
    python browser.py console
    python browser.py status
    python browser.py lock          # Block user input (prevent interference)
    python browser.py unlock        # Restore user input

=== Why no "controlled by automated software" banner? ===
=== 为什么没有"正受到自动测试软件的控制"横幅？===

This script connects to your EXISTING Chrome via CDP (same as Antigravity).
It does NOT launch a new Chrome instance in automation mode.
Your Chrome is a normal user-launched browser — CDP just attaches to it.

本脚本通过 CDP 连接到你已有的 Chrome（与 Antigravity 相同方式）。
不会启动新的自动化模式 Chrome 实例。
你的 Chrome 是正常用户启动的浏览器 — CDP 只是附加连接。
"""

import argparse
import asyncio
import json
import sys
import os
from datetime import datetime

try:
    from playwright.async_api import async_playwright
except ImportError:
    print("ERROR: playwright not installed.")
    print("  Install: pip install playwright")
    print("  Note: No need to run 'playwright install' — we connect to your existing Chrome, not Playwright's bundled browser.")
    print()
    print("ERROR: 未安装 playwright。")
    print("  安装: pip install playwright")
    print("  注意: 无需运行 'playwright install' — 我们连接你已有的 Chrome，不使用 Playwright 自带浏览器。")
    sys.exit(1)

CDP_ENDPOINT = os.environ.get("BROWSER_CDP_ENDPOINT", "http://localhost:9222")


async def get_browser():
    """Connect to existing Chrome via CDP. Never launches a new browser."""
    p = await async_playwright().start()
    try:
        browser = await p.chromium.connect_over_cdp(CDP_ENDPOINT)
    except Exception as e:
        print(f"ERROR: Cannot connect to Chrome at {CDP_ENDPOINT}")
        print(f"  {e}")
        print()
        print("Make sure Chrome is running with remote debugging:")
        print(f'  chrome.exe --remote-debugging-port=9222')
        print()
        print(f"ERROR: 无法连接到 Chrome ({CDP_ENDPOINT})")
        print("请确保 Chrome 已启用远程调试:")
        print(f'  chrome.exe --remote-debugging-port=9222')
        sys.exit(1)
    return p, browser


async def get_page(browser):
    """Get the active page or create one."""
    contexts = browser.contexts
    if not contexts:
        context = await browser.new_context()
    else:
        context = contexts[0]

    pages = context.pages
    if not pages:
        page = await context.new_page()
    else:
        page = pages[-1]

    return page


async def set_input_lock(page, ignore: bool):
    """Lock/unlock user input via visual overlay (same approach as Antigravity).
    When locked: injects a full-screen overlay that intercepts all user input,
    shows 'AI is controlling' message, and provides a Stop button.
    锁定/解锁用户输入（与 Antigravity 相同的覆盖层方式）。
    锁定时：注入全屏覆盖层拦截所有用户操作，显示控制状态，提供停止按钮。"""
    if ignore:
        await page.evaluate("""() => {
            if (document.getElementById('__browser_automation_overlay')) return;

            const overlay = document.createElement('div');
            overlay.id = '__browser_automation_overlay';
            overlay.style.cssText = `
                position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
                z-index: 2147483647;
                background: rgba(0, 0, 0, 0.45);
                display: flex; flex-direction: column;
                align-items: center; justify-content: center;
                cursor: not-allowed;
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            `;

            // Pulsing border glow
            const borderEl = document.createElement('div');
            borderEl.style.cssText = `
                position: fixed; top: 0; left: 0; right: 0; bottom: 0;
                border: 3px solid rgba(59, 130, 246, 0.8);
                box-shadow: inset 0 0 30px rgba(59, 130, 246, 0.15);
                pointer-events: none;
                animation: __ba_pulse 2s ease-in-out infinite;
            `;
            overlay.appendChild(borderEl);

            // Status card
            const card = document.createElement('div');
            card.style.cssText = `
                background: rgba(15, 23, 42, 0.95);
                border: 1px solid rgba(59, 130, 246, 0.5);
                border-radius: 16px;
                padding: 32px 48px;
                text-align: center;
                box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
                backdrop-filter: blur(12px);
            `;

            const icon = document.createElement('div');
            icon.textContent = '🤖';
            icon.style.cssText = 'font-size: 48px; margin-bottom: 16px;';
            card.appendChild(icon);

            const title = document.createElement('div');
            title.textContent = 'AI is controlling the browser';
            title.style.cssText = `
                color: #e2e8f0; font-size: 18px; font-weight: 600;
                margin-bottom: 8px;
            `;
            card.appendChild(title);

            const subtitle = document.createElement('div');
            subtitle.textContent = 'AI 正在控制浏览器';
            subtitle.style.cssText = 'color: #94a3b8; font-size: 14px; margin-bottom: 24px;';
            card.appendChild(subtitle);

            // Animated dots
            const dots = document.createElement('div');
            dots.id = '__ba_dots';
            dots.style.cssText = `
                display: flex; gap: 8px; justify-content: center; margin-bottom: 24px;
            `;
            for (let i = 0; i < 3; i++) {
                const dot = document.createElement('div');
                dot.style.cssText = `
                    width: 10px; height: 10px; border-radius: 50%;
                    background: #3b82f6;
                    animation: __ba_bounce 1.4s ease-in-out ${i * 0.16}s infinite;
                `;
                dots.appendChild(dot);
            }
            card.appendChild(dots);

            // Stop button
            const btn = document.createElement('button');
            btn.textContent = '⏹ Stop / 停止';
            btn.style.cssText = `
                background: rgba(239, 68, 68, 0.15);
                border: 1px solid rgba(239, 68, 68, 0.5);
                color: #fca5a5; padding: 10px 28px;
                border-radius: 8px; font-size: 14px; font-weight: 500;
                cursor: pointer; transition: all 0.2s;
            `;
            btn.onmouseenter = () => {
                btn.style.background = 'rgba(239, 68, 68, 0.3)';
                btn.style.borderColor = '#ef4444';
                btn.style.color = '#fff';
            };
            btn.onmouseleave = () => {
                btn.style.background = 'rgba(239, 68, 68, 0.15)';
                btn.style.borderColor = 'rgba(239, 68, 68, 0.5)';
                btn.style.color = '#fca5a5';
            };
            btn.onclick = (e) => {
                e.stopPropagation();
                const el = document.getElementById('__browser_automation_overlay');
                if (el) el.remove();
                const style = document.getElementById('__ba_styles');
                if (style) style.remove();
                window.__browser_automation_stopped = true;
            };
            card.appendChild(btn);
            overlay.appendChild(card);

            // Keyframes
            const style = document.createElement('style');
            style.id = '__ba_styles';
            style.textContent = `
                @keyframes __ba_pulse {
                    0%, 100% { border-color: rgba(59, 130, 246, 0.8); }
                    50% { border-color: rgba(59, 130, 246, 0.3); }
                }
                @keyframes __ba_bounce {
                    0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
                    40% { transform: scale(1); opacity: 1; }
                }
            `;
            document.head.appendChild(style);
            document.body.appendChild(overlay);
            window.__browser_automation_stopped = false;
        }""")
    else:
        await page.evaluate("""() => {
            const el = document.getElementById('__browser_automation_overlay');
            if (el) el.remove();
            const style = document.getElementById('__ba_styles');
            if (style) style.remove();
            window.__browser_automation_stopped = false;
        }""")

async def cmd_status(args):
    p, browser = await get_browser()
    page = await get_page(browser)
    print(json.dumps({
        "status": "connected",
        "cdp": CDP_ENDPOINT,
        "url": page.url,
        "title": await page.title(),
        "viewport": page.viewport_size,
    }, ensure_ascii=False))


async def cmd_navigate(args):
    p, browser = await get_browser()
    page = await get_page(browser)
    await page.goto(args.url, wait_until="domcontentloaded")
    print(json.dumps({
        "status": "ok",
        "url": page.url,
        "title": await page.title()
    }, ensure_ascii=False))


async def cmd_screenshot(args):
    p, browser = await get_browser()
    page = await get_page(browser)

    if args.url:
        await page.goto(args.url, wait_until="domcontentloaded")

    output = args.output or f"screenshot_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
    await page.screenshot(path=output, full_page=args.full)
    print(json.dumps({
        "status": "ok",
        "path": os.path.abspath(output),
        "url": page.url,
        "title": await page.title()
    }, ensure_ascii=False))


async def cmd_dom(args):
    p, browser = await get_browser()
    page = await get_page(browser)
    content = await page.content()
    print(content)


async def cmd_text(args):
    p, browser = await get_browser()
    page = await get_page(browser)
    text = await page.inner_text("body")
    print(text)


async def cmd_lock(args):
    p, browser = await get_browser()
    page = await get_page(browser)
    await set_input_lock(page, True)
    print(json.dumps({"status": "ok", "input": "locked",
                      "note": "Overlay active — user input blocked / 覆盖层已激活，用户输入已锁定"}))


async def cmd_unlock(args):
    p, browser = await get_browser()
    page = await get_page(browser)
    await set_input_lock(page, False)
    print(json.dumps({"status": "ok", "input": "unlocked", "note": "User input restored / 用户输入已恢复"}))


async def cmd_click(args):
    p, browser = await get_browser()
    page = await get_page(browser)
    await page.mouse.click(args.x, args.y)
    print(json.dumps({"status": "ok", "clicked": [args.x, args.y]}))


async def cmd_type(args):
    p, browser = await get_browser()
    page = await get_page(browser)
    await page.keyboard.type(args.text)
    print(json.dumps({"status": "ok", "typed": args.text}))


async def cmd_key(args):
    p, browser = await get_browser()
    page = await get_page(browser)
    await page.keyboard.press(args.key_name)
    print(json.dumps({"status": "ok", "key": args.key_name}))


async def cmd_scroll(args):
    p, browser = await get_browser()
    page = await get_page(browser)
    delta = args.amount or 500
    dx, dy = 0, 0
    if args.direction == "down":
        dy = delta
    elif args.direction == "up":
        dy = -delta
    elif args.direction == "right":
        dx = delta
    elif args.direction == "left":
        dx = -delta
    await page.mouse.wheel(dx, dy)
    print(json.dumps({"status": "ok", "direction": args.direction, "amount": delta}))


async def cmd_resize(args):
    p, browser = await get_browser()
    page = await get_page(browser)
    await page.set_viewport_size({"width": args.width, "height": args.height})
    print(json.dumps({"status": "ok", "width": args.width, "height": args.height}))


async def cmd_network(args):
    p, browser = await get_browser()
    page = await get_page(browser)
    requests_log = []

    def on_response(response):
        requests_log.append({
            "url": response.url,
            "status": response.status,
            "method": response.request.method,
        })

    page.on("response", on_response)
    await page.reload(wait_until="domcontentloaded")
    await page.wait_for_timeout(2000)
    print(json.dumps({"status": "ok", "requests": requests_log}, ensure_ascii=False))


async def cmd_console(args):
    p, browser = await get_browser()
    page = await get_page(browser)
    logs = []

    def on_console(msg):
        logs.append({"type": msg.type, "text": msg.text})

    page.on("console", on_console)
    await page.reload(wait_until="domcontentloaded")
    await page.wait_for_timeout(2000)
    print(json.dumps({"status": "ok", "logs": logs}, ensure_ascii=False))


def main():
    parser = argparse.ArgumentParser(
        description="Browser automation CLI — controls existing Chrome via CDP\n"
                    "浏览器自动化 CLI — 通过 CDP 控制已有的 Chrome",
        formatter_class=argparse.RawDescriptionHelpFormatter
    )
    parser.add_argument("--cdp", default=None,
                        help="CDP endpoint (default: http://localhost:9222)")
    sub = parser.add_subparsers(dest="command")

    sub.add_parser("status", help="Check browser connection / 检查浏览器连接")

    p_nav = sub.add_parser("navigate", help="Navigate to URL / 导航到 URL")
    p_nav.add_argument("url")

    p_ss = sub.add_parser("screenshot", help="Capture screenshot / 截图")
    p_ss.add_argument("url", nargs="?", default=None)
    p_ss.add_argument("--output", "-o", default=None)
    p_ss.add_argument("--full", action="store_true", help="Full page / 全页截图")

    sub.add_parser("dom", help="Get page HTML / 获取页面 HTML")
    sub.add_parser("text", help="Get page text / 获取页面文本")

    p_click = sub.add_parser("click", help="Click at coordinates / 点击坐标")
    p_click.add_argument("x", type=float)
    p_click.add_argument("y", type=float)

    p_type = sub.add_parser("type", help="Type text / 输入文字")
    p_type.add_argument("text")

    p_key = sub.add_parser("key", help="Press a key / 按键")
    p_key.add_argument("key_name")

    p_scroll = sub.add_parser("scroll", help="Scroll page / 滚动页面")
    p_scroll.add_argument("direction", choices=["up", "down", "left", "right"])
    p_scroll.add_argument("amount", type=int, nargs="?", default=500)

    p_resize = sub.add_parser("resize", help="Resize viewport / 调整视口")
    p_resize.add_argument("width", type=int)
    p_resize.add_argument("height", type=int)

    sub.add_parser("network", help="Capture network requests / 捕获网络请求")
    sub.add_parser("console", help="Capture console logs / 捕获控制台日志")

    sub.add_parser("lock", help="Block user input (exclusive model control) / 锁定用户输入")
    sub.add_parser("unlock", help="Restore user input / 解锁用户输入")

    args = parser.parse_args()

    if args.cdp:
        global CDP_ENDPOINT
        CDP_ENDPOINT = args.cdp

    if not args.command:
        parser.print_help()
        sys.exit(1)

    cmd_map = {
        "status": cmd_status,
        "navigate": cmd_navigate, "screenshot": cmd_screenshot,
        "dom": cmd_dom, "text": cmd_text, "click": cmd_click,
        "type": cmd_type, "key": cmd_key, "scroll": cmd_scroll,
        "resize": cmd_resize, "network": cmd_network, "console": cmd_console,
        "lock": cmd_lock, "unlock": cmd_unlock,
    }
    asyncio.run(cmd_map[args.command](args))


if __name__ == "__main__":
    main()


path: docs/api-reference.md
content: # Browser Automation — API Reference / API 参考

> **[English](#english)** | **[中文](#中文)**

---

<a id="english"></a>

## English

> Shared reference documentation. Skills link here for detailed tool information.

### Calling Convention

You do NOT call browser tools directly. You call `browser_subagent` with these parameters:

| Parameter | Required | Description |
|-----------|----------|-------------|
| `TaskName` | ✅ | Human-readable title, e.g., "Navigate to URL" |
| `Task` | ✅ | Detailed instructions for the subagent |
| `TaskSummary` | ✅ | 1-2 sentence summary for UI display |
| `RecordingName` | ✅ | Filename for WebP recording (max 3 words, lowercase_underscores) |
| `MediaPaths` | ❌ | Up to 3 image/video paths to provide as context |
| `ReusedSubagentId` | ❌ | Resume from a previous subagent's context |

### Available Browser Tools (inside browser_subagent)

#### Navigation

| Tool | Description |
|------|-------------|
| `open_browser_url` | Navigate to a URL |

#### Observation

| Tool | Returns | Description |
|------|---------|-------------|
| `capture_browser_screenshot` | PNG file path | Visual snapshot of the current viewport |
| `browser_get_dom` | DOM tree (text) | Full DOM with element attributes and coordinates |
| `read_browser_page` | Markdown text | Page content converted to readable markdown |
| `browser_list_network_requests` | Request list | All HTTP requests with methods, URLs, status codes |
| `capture_browser_console_logs` | Log entries | JavaScript console output (errors, warnings, info) |

#### Interaction

| Tool | Parameters | Description |
|------|------------|-------------|
| `click_browser_pixel` | `x`, `y` | Click at pixel coordinates |
| `browser_press_key` | key name | Press a keyboard key (Enter, Tab, Escape, etc.) |
| `browser_scroll` | `direction`, `amount` | Scroll the page (up/down/left/right) |
| `browser_drag_pixel_to_pixel` | start/end coords | Drag from one point to another |

#### Window Control

| Tool | Parameters | Description |
|------|------------|-------------|
| `browser_resize_window` | `width`, `height` | Resize the browser viewport |

### Essential Patterns

#### Pattern 1: Observe → Act → Verify
1. **Observe**: `capture_browser_screenshot` + `browser_get_dom`
2. **Act**: `click_browser_pixel` / `browser_press_key` / `browser_scroll`
3. **Verify**: `capture_browser_screenshot` (confirm the action worked)

#### Pattern 2: Coordinate-Based Interaction
The browser uses **pixel coordinates** for clicking, not CSS selectors.
1. Use `browser_get_dom` to find the element
2. The DOM response includes position/coordinate information
3. Click at the element's coordinates using `click_browser_pixel`

#### Pattern 3: Reuse Subagent Context
For multi-step workflows on the same page:
1. First call: save the returned `SubagentId`
2. Subsequent calls: pass `ReusedSubagentId` to continue from the same browser state

#### Pattern 4: Screenshot Verification
After every subagent call:
1. Look for screenshot file paths in the response
2. Use `view_file` on the screenshot to verify what actually happened
3. **Never trust subagent claims without verification**

### Error Handling

| Error | Cause | Solution |
|-------|-------|----------|
| `open_browser_url` failed | Browser issue | Ask user how to proceed |
| Element not found in DOM | Page not loaded / wrong page | Wait and retry, or navigate first |
| Click didn't work | Wrong coordinates | Re-read DOM, recalculate coordinates |
| Page blank after navigation | JavaScript-heavy SPA | Wait longer for rendering |

### Limitations

- Cannot handle native OS dialogs (file pickers, print dialogs)
- Cannot interact with browser chrome (address bar, settings)
- Cannot handle HTTP auth popups natively
- Maximum 3 media files can be passed as context
- RecordingName is limited to 3 words

---

<a id="中文"></a>

## 中文

> 共享参考文档。各 Skill 通过链接引用此处的详细工具说明。

### 调用约定

你**不能直接调用**浏览器工具。你需要通过 `browser_subagent` 间接调用，参数如下：

| 参数 | 必需 | 说明 |
|------|------|------|
| `TaskName` | ✅ | 可读标题，如 "Navigate to URL" |
| `Task` | ✅ | 给 subagent 的详细指令 |
| `TaskSummary` | ✅ | 1-2 句简要描述，用于 UI 显示 |
| `RecordingName` | ✅ | WebP 录制文件名（最多3个词，小写下划线） |
| `MediaPaths` | ❌ | 最多 3 个图片/视频路径作为上下文 |
| `ReusedSubagentId` | ❌ | 从之前的 subagent 上下文继续执行 |

### 可用浏览器工具（browser_subagent 内部）

#### 导航

| 工具 | 说明 |
|------|------|
| `open_browser_url` | 导航到指定 URL |

#### 观察

| 工具 | 返回值 | 说明 |
|------|--------|------|
| `capture_browser_screenshot` | PNG 文件路径 | 当前视口的视觉快照 |
| `browser_get_dom` | DOM 树（文本） | 完整 DOM 结构，含元素属性和坐标 |
| `read_browser_page` | Markdown 文本 | 页面内容转换为可读 Markdown |
| `browser_list_network_requests` | 请求列表 | 所有 HTTP 请求（方法、URL、状态码） |
| `capture_browser_console_logs` | 日志条目 | JavaScript 控制台输出（错误、警告、信息） |

#### 交互

| 工具 | 参数 | 说明 |
|------|------|------|
| `click_browser_pixel` | `x`, `y` | 在像素坐标处点击 |
| `browser_press_key` | 键名 | 按键盘键（Enter、Tab、Escape 等） |
| `browser_scroll` | `direction`, `amount` | 滚动页面（上/下/左/右） |
| `browser_drag_pixel_to_pixel` | 起/终坐标 | 从一个点拖拽到另一个点 |

#### 窗口控制

| 工具 | 参数 | 说明 |
|------|------|------|
| `browser_resize_window` | `width`, `height` | 调整浏览器视口大小 |

### 核心模式

#### 模式一：观察 → 行动 → 验证
1. **观察**：`capture_browser_screenshot` + `browser_get_dom`
2. **行动**：`click_browser_pixel` / `browser_press_key` / `browser_scroll`
3. **验证**：`capture_browser_screenshot`（确认操作成功）

#### 模式二：基于坐标的交互
浏览器使用**像素坐标**进行点击，而非 CSS 选择器。
1. 用 `browser_get_dom` 找到元素
2. DOM 响应中包含位置/坐标信息
3. 用 `click_browser_pixel` 在元素坐标处点击

#### 模式三：复用 Subagent 上下文
用于同一页面上的多步操作：
1. 第一次调用：保存返回的 `SubagentId`
2. 后续调用：传入 `ReusedSubagentId` 以继续同一浏览器状态

#### 模式四：截图验证
每次 subagent 调用后：
1. 在响应中查找截图文件路径
2. 用 `view_file` 查看截图验证实际结果
3. **永远不要不经验证就相信 subagent 的声明**

### 错误处理

| 错误 | 原因 | 解决方案 |
|------|------|----------|
| `open_browser_url` 失败 | 浏览器问题 | 询问用户如何处理 |
| DOM 中找不到元素 | 页面未加载/错误页面 | 等待重试，或先导航 |
| 点击无效 | 坐标错误 | 重新读取 DOM，重新计算坐标 |
| 导航后页面空白 | JavaScript 重型 SPA | 等待更长时间让页面渲染 |

### 限制

- 无法处理原生系统对话框（文件选择器、打印对话框）
- 无法操作浏览器自身（地址栏、设置页面）
- 无法原生处理 HTTP 认证弹窗
- 最多传递 3 个媒体文件作为上下文
- RecordingName 限制最多 3 个词


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)