$npx -y skills add appautomaton/webmaton --skill nodriver-browserPersistent Chrome/Chromium browser automation skill built on nodriver. Use when a page needs JavaScript rendering, authorized login/session continuity, clicking or typing, DOM snapshots with stable refs, screenshots, or multi-step look-think-act flows that ordinary WebFetch/searc
| 1 | # nodriver-browser |
| 2 | |
| 3 | A persistent Chrome/Chromium browser that **stays alive between Claude's turns**. Built on `nodriver` (CDP-direct, no Selenium, no `navigator.webdriver`). Every script attaches to the same long-running browser, performs one action, exits — the browser and its tab keep going. |
| 4 | |
| 5 | **Core invariant: ONE daemon, ONE persistent tab (`tabs[0]`).** Every script reports `tabs_open` in its output. If you ever see `tabs_open > 1`, treat it as a real signal that something opened a stray tab — read the warning and act on it. |
| 6 | |
| 7 | ## When to use this skill |
| 8 | |
| 9 | - The page **needs JavaScript** to render (SPA, infinite scroll, lazy load) |
| 10 | - WebFetch is blocked by **anti-bot** systems (Cloudflare, DataDome, Imperva, hCaptcha) |
| 11 | - The task requires **interaction**: clicking buttons, filling forms, multi-step flows, dropdown selection |
| 12 | - You need **session state** across multiple actions (logged-in scraping, multi-page checkout, OAuth flows) |
| 13 | - You need **visual proof** of a page (screenshot) for debugging or reporting |
| 14 | |
| 15 | ## When NOT to use this skill |
| 16 | |
| 17 | - **Static HTML** that loads fully on first GET → use `WebFetch` |
| 18 | - **One-shot search query** → use `WebSearch` (or grok-search skill if it exists) |
| 19 | - **A JSON API endpoint** → use `curl` via Bash, you don't need a browser at all |
| 20 | - **A single quick scrape with no interactivity** → consider a one-off Python script, not this skill |
| 21 | |
| 22 | This skill spawns a Chromium process (~150-200 MB RAM) that stays alive until you explicitly stop it. Worth it for interactive flows; overkill for one URL. |
| 23 | |
| 24 | ## Quick start |
| 25 | |
| 26 | Scripts live in `scripts/` next to this SKILL.md — resolve paths relative to the skill root. |
| 27 | |
| 28 | Run them directly as executables; the `#!/usr/bin/env -S uv run --script` |
| 29 | shebang invokes uv and reads the PEP 723 metadata for you. Do **not** run these |
| 30 | scripts with `python`, `python3`, or `python -m`; that bypasses the shebang, |
| 31 | dependency metadata, and Python version pin. If executable dispatch is |
| 32 | unavailable, use `uv run --script scripts/nav.py ...`. |
| 33 | |
| 34 | ```bash |
| 35 | # 1. Navigate (auto-starts daemon on first call — no manual start needed) |
| 36 | scripts/nav.py https://news.ycombinator.com |
| 37 | |
| 38 | # Optional: visible browser window, using the user's Chrome profile |
| 39 | scripts/nav.py --headed --user-profile https://example.com |
| 40 | |
| 41 | # 2. Snapshot the page — gives you text + numbered refs for every interactive element |
| 42 | scripts/snapshot.py |
| 43 | |
| 44 | # 3. Click something by ref id from the snapshot |
| 45 | scripts/click.py r17 |
| 46 | |
| 47 | # 4. Read state any time — it's the same tab as before, even from a fresh process |
| 48 | scripts/state.py |
| 49 | |
| 50 | # 5. When done with the session, stop the daemon (frees ~190 MB) |
| 51 | scripts/stop_daemon.py |
| 52 | ``` |
| 53 | |
| 54 | ## Script reference |
| 55 | |
| 56 | All runnable scripts use `uv run --script` with PEP 723 metadata and return JSON to stdout. Invoke them directly, not through `python` or `python3`. Every script (except daemon control) appends `tabs_open: N` and emits a `warning` field if `N > 1`. |
| 57 | |
| 58 | Leading browser options work on `start_daemon.py` and every script that auto-starts/attaches: |
| 59 | |
| 60 | | Option | Purpose | |
| 61 | |---|---| |
| 62 | | `--headless` | Start a headless daemon. This is the default when no daemon is running. | |
| 63 | | `--headed` | Start a visible Chrome/Chromium window. If a daemon is already running in headless mode, stop it first. | |
| 64 | | `--skill-profile` | Use the isolated profile at `~/.cache/nodriver-skill/profile/`. This is the default. | |
| 65 | | `--user-profile` | Use the user's Chrome profile root. Useful for existing logged-in state; requires that regular Chrome is not already locking the same profile. | |
| 66 | | `--profile-directory NAME` | Use a Chrome profile directory such as `Default` or `Profile 1`; implies `--user-profile`. | |
| 67 | | `--user-data-dir PATH` | Override the Chrome user-data root; implies `--user-profile`. | |
| 68 | | `--no-sandbox` | Disable Chrome's OS sandbox. Use only when Chrome cannot start in constrained environments such as PRoot/container/root setups. Do not use for normal system Chrome or the user's Chrome profile. | |
| 69 | |
| 70 | Environment equivalents: `NODRIVER_SKILL_MODE=headed|headless`, `NODRIVER_SKILL_PROFILE=skill|user`, `NODRIVER_CHROME_PROFILE_DIRECTORY="Profile 1"`, `NODRIVER_CHROME_USER_DATA_DIR=/path/to/User Data`, `NODRIVER_CHROME_NO_SANDBOX=1`. |
| 71 | |
| 72 | ### Daemon control |
| 73 | |
| 74 | | Script | Purpose | Output | |
| 75 | |---|---|---| |
| 76 | | `start_daemon.py` | Idempotent start. No-op if already running. Supports leading browser options. | `{ok, pid, port, mode, profile, no_sandbox, already_running}` | |
| 77 | | `stop_daemon.py` | K |