browser-automation-skill 0.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -0
  3. package/SECURITY.md +39 -0
  4. package/SKILL.md +206 -0
  5. package/bin/cli.mjs +55 -0
  6. package/install.sh +143 -0
  7. package/package.json +54 -0
  8. package/references/adapter-candidates.md +40 -0
  9. package/references/browser-mcp-cheatsheet.md +132 -0
  10. package/references/browser-stats-cheatsheet.md +155 -0
  11. package/references/chrome-devtools-mcp-cheatsheet.md +232 -0
  12. package/references/midscene-integration.md +359 -0
  13. package/references/obscura-cheatsheet.md +103 -0
  14. package/references/playwright-cli-cheatsheet.md +64 -0
  15. package/references/playwright-lib-cheatsheet.md +90 -0
  16. package/references/recipes/add-a-tool-adapter.md +134 -0
  17. package/references/recipes/agent-workflows/README.md +37 -0
  18. package/references/recipes/agent-workflows/cache-driven-bulk-operation.md +110 -0
  19. package/references/recipes/agent-workflows/flow-record-and-replay.md +102 -0
  20. package/references/recipes/agent-workflows/incremental-pattern-discovery.md +125 -0
  21. package/references/recipes/agent-workflows/login-then-scrape.md +100 -0
  22. package/references/recipes/anti-patterns-tool-extension.md +182 -0
  23. package/references/recipes/body-bytes-not-body.md +139 -0
  24. package/references/recipes/cache-write-security.md +210 -0
  25. package/references/recipes/fingerprint-rescue.md +154 -0
  26. package/references/recipes/model-routing.md +143 -0
  27. package/references/recipes/path-security.md +138 -0
  28. package/references/recipes/privacy-canary.md +96 -0
  29. package/references/recipes/visual-rescue-hook.md +182 -0
  30. package/references/stats-prices.json +42 -0
  31. package/references/stats-schema.json +77 -0
  32. package/references/tool-versions.md +8 -0
  33. package/scripts/browser-add-site.sh +113 -0
  34. package/scripts/browser-assert.sh +106 -0
  35. package/scripts/browser-audit.sh +68 -0
  36. package/scripts/browser-baseline.sh +135 -0
  37. package/scripts/browser-click.sh +100 -0
  38. package/scripts/browser-creds-add.sh +254 -0
  39. package/scripts/browser-creds-list.sh +67 -0
  40. package/scripts/browser-creds-migrate.sh +122 -0
  41. package/scripts/browser-creds-remove.sh +69 -0
  42. package/scripts/browser-creds-rotate-totp.sh +109 -0
  43. package/scripts/browser-creds-show.sh +82 -0
  44. package/scripts/browser-creds-totp.sh +94 -0
  45. package/scripts/browser-do.sh +630 -0
  46. package/scripts/browser-doctor.sh +365 -0
  47. package/scripts/browser-drag.sh +90 -0
  48. package/scripts/browser-extract.sh +192 -0
  49. package/scripts/browser-fill.sh +142 -0
  50. package/scripts/browser-flow.sh +316 -0
  51. package/scripts/browser-history.sh +187 -0
  52. package/scripts/browser-hover.sh +92 -0
  53. package/scripts/browser-inspect.sh +188 -0
  54. package/scripts/browser-list-sessions.sh +78 -0
  55. package/scripts/browser-list-sites.sh +42 -0
  56. package/scripts/browser-login.sh +279 -0
  57. package/scripts/browser-mcp.sh +65 -0
  58. package/scripts/browser-migrate.sh +195 -0
  59. package/scripts/browser-open.sh +134 -0
  60. package/scripts/browser-press.sh +80 -0
  61. package/scripts/browser-remove-session.sh +72 -0
  62. package/scripts/browser-remove-site.sh +68 -0
  63. package/scripts/browser-replay.sh +206 -0
  64. package/scripts/browser-route.sh +174 -0
  65. package/scripts/browser-select.sh +122 -0
  66. package/scripts/browser-show-session.sh +57 -0
  67. package/scripts/browser-show-site.sh +37 -0
  68. package/scripts/browser-snapshot.sh +176 -0
  69. package/scripts/browser-stats.sh +522 -0
  70. package/scripts/browser-tab-close.sh +112 -0
  71. package/scripts/browser-tab-list.sh +70 -0
  72. package/scripts/browser-tab-switch.sh +111 -0
  73. package/scripts/browser-upload.sh +132 -0
  74. package/scripts/browser-use.sh +60 -0
  75. package/scripts/browser-vlm.sh +707 -0
  76. package/scripts/browser-wait.sh +97 -0
  77. package/scripts/install-git-hooks.sh +16 -0
  78. package/scripts/lib/capture.sh +356 -0
  79. package/scripts/lib/common.sh +262 -0
  80. package/scripts/lib/credential.sh +237 -0
  81. package/scripts/lib/fingerprint-rescue.js +123 -0
  82. package/scripts/lib/flow.sh +448 -0
  83. package/scripts/lib/flow_record.sh +210 -0
  84. package/scripts/lib/mask.sh +49 -0
  85. package/scripts/lib/memory.sh +427 -0
  86. package/scripts/lib/migrate.sh +390 -0
  87. package/scripts/lib/migrators/README.md +23 -0
  88. package/scripts/lib/migrators/memory/v1_to_v2.sh +15 -0
  89. package/scripts/lib/migrators/recent_urls/README.md +13 -0
  90. package/scripts/lib/migrators/stats/README.md +24 -0
  91. package/scripts/lib/node/chrome-devtools-bridge.mjs +1812 -0
  92. package/scripts/lib/node/mcp-server.mjs +531 -0
  93. package/scripts/lib/node/mcp-tools.json +68 -0
  94. package/scripts/lib/node/playwright-driver.mjs +1104 -0
  95. package/scripts/lib/node/totp-core.mjs +52 -0
  96. package/scripts/lib/node/totp.mjs +52 -0
  97. package/scripts/lib/node/url-pattern-cluster.mjs +102 -0
  98. package/scripts/lib/node/url-pattern-resolver.mjs +77 -0
  99. package/scripts/lib/output.sh +79 -0
  100. package/scripts/lib/router.sh +342 -0
  101. package/scripts/lib/sanitize.sh +107 -0
  102. package/scripts/lib/secret/keychain.sh +91 -0
  103. package/scripts/lib/secret/libsecret.sh +74 -0
  104. package/scripts/lib/secret/plaintext.sh +75 -0
  105. package/scripts/lib/secret_backend_select.sh +57 -0
  106. package/scripts/lib/session.sh +153 -0
  107. package/scripts/lib/site.sh +126 -0
  108. package/scripts/lib/stats.sh +419 -0
  109. package/scripts/lib/tool/.gitkeep +0 -0
  110. package/scripts/lib/tool/chrome-devtools-mcp.sh +349 -0
  111. package/scripts/lib/tool/obscura.sh +249 -0
  112. package/scripts/lib/tool/playwright-cli.sh +155 -0
  113. package/scripts/lib/tool/playwright-lib.sh +106 -0
  114. package/scripts/lib/verb_helpers.sh +222 -0
  115. package/scripts/lib/visual-rescue-default.sh +145 -0
  116. package/scripts/regenerate-docs.sh +99 -0
  117. package/uninstall.sh +51 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nick Cao
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # browser-automation-skill
2
+
3
+ A [Claude Code](https://claude.com/claude-code) skill for driving real browsers from an LLM. **44 verbs + a per-action audit surface** routed across four tools (chrome-devtools-mcp / playwright-cli / playwright-lib / obscura), with a 5-tier cache defense chain (cached selector → fingerprint rescue → local-VLM rescue → cloud LLM → user fixup) that lets agents skip LLM ref-resolution on repeat actions and per-schema state migration tooling. Credentials and sessions stay strictly local under `$HOME/.browser-skill/`.
4
+
5
+ > **Status:** Phases 1–14 ✅ ALL COMPLETE. **Phase 14 (local-VLM cache rescue + auto-managed VLM stack + MCP server) ✅ shipped** — `scripts/browser-vlm.sh` wraps `llama-server` with idle-stop watchdog + lazy-start; `scripts/lib/visual-rescue-default.sh` is the canonical Path 3 probe (gated by `BROWSER_SKILL_VISION_FALLBACK=1`); `scripts/lib/node/mcp-server.mjs` publishes 5 verbs (open/snapshot/click/fill/extract) over JSON-RPC NDJSON for external agents (auto-discovers TOOLS from adapter capabilities + `mcp-tools.json` allowlist); `browser-stats prune` closes the telemetry feedback loop by auto-detecting cache-pollution from `oblivious_success` clusters. **Production-ready v1.3.** Full bats: 1086/1086 green. Architecture map: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md). New contributors: [`CONTRIBUTING.md`](CONTRIBUTING.md).
6
+ >
7
+ > **One-command enable Path 3 cache rescue:** `bash scripts/browser-vlm.sh install-env` (idempotent — appends env exports to `~/.zshrc`; lazy auto-start handles the rest).
8
+
9
+ ## What it does
10
+
11
+ - **Sites + sessions + credentials.** Register sites; capture/restore Playwright `storageState`; store credentials in keychain (macOS) / libsecret (Linux) / plaintext-with-typed-confirmation; rotate TOTP shared secrets.
12
+ - **Navigation + interaction.** `open` · `snapshot` (eN-indexed accessibility tree) · `click`/`fill`/`hover`/`press`/`select`/`drag`/`upload` by `--ref eN` or `--selector CSS` · `wait` · `route` (network mock) · multi-tab (`tab-list`/`tab-switch`/`tab-close`).
13
+ - **Capture pipelines.** `inspect` aggregates console + network (sanitized HAR) + screenshot. `audit` runs Lighthouse. All captures persist under `~/.browser-skill/captures/<NNN>/` with `meta.json` + per-aspect files; auto-prune at retention thresholds (default: 500 captures / 14 days; baselines exempt).
14
+ - **Declarative flow runner.** `flow run task.flow.yaml` executes a YAML flow with `${var}` + `${refs.NAME}` templating. `flow record` wraps `playwright codegen` (password-canary write-side: `/password/i` becomes `${secrets.password}` placeholder; literal dropped). `replay <id>` re-executes a capture's steps + emits structured per-step diff. `history list/show/diff/clear` + `baseline save/list/remove` for managing the capture corpus.
15
+ - **Per-archetype memory cache (Phase 11).** `browser-do --intent "click delete" --pattern '/devices/:id'` looks up cached selector for the `(site, archetype, intent)` triple; on hit, dispatches the existing verb at zero LLM tokens; on miss, emits `cache_miss` event. `browser-do record` for explicit write-back. `browser-do propose` auto-clusters URLs into patterns. Self-heal: 4 consecutive failures disable the cached selector; agent re-resolves + re-records to heal.
16
+ - **Per-action telemetry + balance-triangle audit (Phase 12).** Every adapter call (`open`/`click`/`fill`/`snapshot`/`extract`) emits one OTel-shaped JSONL event to `~/.browser-skill/memory/stats.jsonl` (mode 0600). `browser-stats report --pareto` rolls events into a route × verb table: success rate, post-condition hit rate, token-proxy byte counts, p50 duration, $$ cost (when `CLAUDE_USAGE_*` env injected), 14-value failure-mode histogram (Phase-14 added `unknown_failure` catch-all), and **`oblivious_success` detection** (adapter said ok but post-condition assertion failed — the dominant invisible-error class for browser agents). `browser-stats tune` surfaces worst-performing `(verb, route)` candidates for `/autoresearch` handoff. **`browser-stats prune` (Phase 14)** closes the feedback loop: finds (site, selector) tuples with ≥3 `oblivious_success` events; `--apply` disables the matching cache interactions so cloud LLM re-derives. `browser-stats mark <span> success|fail[:reason]` records user overrides. Schema follows OpenInference + OTel GenAI v1.40 naming for forward-compat with Langfuse/Phoenix/Jaeger via OTLP exporter. See [`references/browser-stats-cheatsheet.md`](references/browser-stats-cheatsheet.md).
17
+ - **Local-VLM cache rescue (Phase 14, Path 3).** 5th tier in the cache defense chain — between Phase-13 fingerprint rescue and cloud-LLM fallback. When `BROWSER_SKILL_VISION_FALLBACK=1` + `BROWSER_SKILL_VISUAL_RESCUE_CMD=<path>` set, browser-do invokes an external hook that probes whether the cached element is still semantically present. Bundled canonical probe `scripts/lib/visual-rescue-default.sh` (text-mode v1) reads the accessibility-tree snapshot + asks a local OpenAI-compatible LLM (default `http://127.0.0.1:8080` — matches `bash scripts/browser-vlm.sh start`) yes/no. Smart-skip when `fail_count ≥ 3` (cache likely fundamentally broken; skip the probe). One env var pair via `bash scripts/browser-vlm.sh install-env` enables everything; lazy-start + 10-min idle-stop watchdog manage the llama-server lifecycle. See [`references/recipes/visual-rescue-hook.md`](references/recipes/visual-rescue-hook.md).
18
+ - **MCP server (Phase 14).** `bash scripts/browser-mcp.sh serve` publishes 5 verbs (open / snapshot / click / fill / extract) over JSON-RPC NDJSON for external agents (Claude Code, midscene, agent-browser, Stagehand, Continue, Cline). TOOLS auto-discovered from each adapter's `tool_capabilities()` + `scripts/lib/node/mcp-tools.json` allowlist — adding a verb to MCP is a 1-JSON-entry change. Env-var passthrough is whitelisted (AP-7: client's `OPENAI_API_KEY` and other foreign secrets are filtered; only `BROWSER_SKILL_*` / `MIDSCENE_MODEL_*` / `CLAUDE_*` / `PLAYWRIGHT_*` / etc inherit). `browser_fill` has no `secret` field and `additionalProperties: false` — secrets stay on the bash entry point via `--secret-stdin`. See [`references/browser-mcp-cheatsheet.md`](references/browser-mcp-cheatsheet.md).
19
+
20
+ ## Security at a glance
21
+
22
+ - Credentials are on disk only at `$HOME/.browser-skill/` (mode 0700 dir, 0600 files).
23
+ - Credentials never appear on argv, in `ps`, in git, or in the Claude transcript (AP-7 stdin-only pattern enforced via `tests/argv_leak.bats`).
24
+ - Cache writes refuse `PASSWORD-CANARY` sentinel (privacy guard in `browser-do record`).
25
+ - `.gitignore` blocks every credential / session / capture / memory pattern from the repo.
26
+ - `.githooks/pre-commit` rejects any staged file or diff that looks like a credential.
27
+ - See `SECURITY.md` for the full threat model + `references/recipes/{privacy-canary,cache-write-security,path-security}.md` for codified discipline.
28
+
29
+ ## Requirements
30
+
31
+ **Skill itself (always required):**
32
+ - bash **≥ 5.0** (`brew install bash` on macOS — system bash 3.2 is too old; bash 5.0 needed for `$EPOCHREALTIME` fast path used by the Phase-12 telemetry emitter)
33
+ - `jq`
34
+ - `sqlite3` (Phase 12 — lazy-built SQLite mirror at `memory/stats.db`; standard on macOS and most Linux distros)
35
+
36
+ **For real browser flows (install at least one):**
37
+ - **chrome-devtools-mcp** (recommended; most-complete adapter): `npx -y chrome-devtools-mcp@latest`
38
+ - **playwright-cli**: `npm i -g playwright @playwright/test @playwright/cli && playwright install chromium`
39
+ - **playwright-lib**: requires `node` + `npm i -g playwright` (driver lazy-imports)
40
+ - **obscura** (single-binary; scrape + stealth-only): download from https://github.com/h4ckf0r0day/obscura/releases
41
+
42
+ **For tests:** `bats-core` (`brew install bats-core`)
43
+
44
+ `browser doctor` reports which adapters are present + install hints for missing ones.
45
+
46
+ ## Install
47
+
48
+ ### Personal (one machine, all your projects)
49
+
50
+ ```bash
51
+ git clone https://github.com/xicv/browser-automation-skill ~/Projects/browser-automation-skill
52
+ cd ~/Projects/browser-automation-skill
53
+ ./install.sh --with-hooks # --with-hooks enables the credential-leak pre-commit blocker
54
+ ```
55
+
56
+ Symlinks `~/.claude/skills/browser-automation-skill` → repo. Creates `~/.browser-skill/` mode 0700. Runs `doctor` at the end.
57
+
58
+ ## Verify (in Claude Code)
59
+
60
+ ```
61
+ /browser doctor
62
+ ```
63
+
64
+ Expected: exit 0; final line is a JSON summary with `"status":"ok"`. Doctor also enumerates installed adapters.
65
+
66
+ ## Quickstart
67
+
68
+ ```bash
69
+ # Register your first site
70
+ bash scripts/browser-add-site.sh --name myapp --url 'https://app.example.com'
71
+ bash scripts/browser-use.sh --set myapp
72
+
73
+ # Open + snapshot (uses chrome-devtools-mcp by default)
74
+ bash scripts/browser-open.sh --url 'https://app.example.com'
75
+ bash scripts/browser-snapshot.sh
76
+ # → emits aria_yaml + eN refs you can pass to click/fill/hover/etc.
77
+
78
+ # Click a ref
79
+ bash scripts/browser-click.sh --ref e3
80
+
81
+ # Or click by CSS (cacheable; required for browser-do cache dispatch)
82
+ bash scripts/browser-click.sh --selector 'button.delete'
83
+
84
+ # Phase 11 cache: record a learned selector once, dispatch zero-LLM-token thereafter
85
+ bash scripts/browser-do.sh record \
86
+ --site myapp --intent "click delete" \
87
+ --selector "button.delete" \
88
+ --url 'https://app.example.com/devices/123'
89
+
90
+ bash scripts/browser-do.sh \
91
+ --site myapp --verb click \
92
+ --intent "click delete" \
93
+ --pattern '/devices/:id'
94
+ # → cache hit; dispatches click; bumps success_count
95
+
96
+ # Phase 12 telemetry: every adapter call above emits one stats event automatically.
97
+ # Review the audit:
98
+ bash scripts/browser-stats.sh rebuild
99
+ bash scripts/browser-stats.sh report --days 7 --pareto
100
+
101
+ # Assert a post-condition so the audit can flag oblivious_success:
102
+ BROWSER_STATS_EXPECT_TYPE=url \
103
+ BROWSER_STATS_EXPECT_MATCH=include \
104
+ BROWSER_STATS_EXPECT_VALUE='/devices/123' \
105
+ bash scripts/browser-open.sh --url 'https://app.example.com/devices/123'
106
+ ```
107
+
108
+ ## Output contract
109
+
110
+ Every verb prints zero or more streaming JSON lines, then ends with a single-line JSON summary. Parse with `jq`; route on `.status` (`ok`, `partial`, `error`, `empty`, `aborted`).
111
+
112
+ ```bash
113
+ $ bash scripts/browser-doctor.sh | tail -1 | jq .
114
+ {"verb":"doctor","tool":"none","why":"health-check","status":"ok","problems":0,"adapters_ok":4,"duration_ms":42}
115
+ ```
116
+
117
+ ## Layout
118
+
119
+ ```
120
+ install.sh # preflight + state dir + symlink + (opt) hooks
121
+ uninstall.sh # remove symlink (state preserved)
122
+ SKILL.md # Claude Code skill manifest (verb table; updated at every phase ship)
123
+ SECURITY.md # threat model + disclosure
124
+ .gitignore # blocks credential / session / capture / memory patterns
125
+ .githooks/pre-commit # credential-leak blocker
126
+ scripts/ # 42 verbs + browser-stats + 7 lib/ + 4 lib/tool/ adapters + lib/node/ driver helpers + lib/fingerprint-rescue.js + lib/migrators/{memory,recent_urls,stats}
127
+ tests/ # 1002 bats (25 new across Phases 12 + 13); runs in <60s
128
+ references/ # routing-heuristics + recipes (incl. fingerprint-rescue.md) + browser-stats-cheatsheet + stats-schema.json + stats-prices.json
129
+ docs/superpowers/ # design specs + per-phase plan-docs + HANDOFF.md
130
+ ```
131
+
132
+ ## Uninstall
133
+
134
+ ```bash
135
+ ./uninstall.sh
136
+ ```
137
+
138
+ Removes the `~/.claude/skills/browser-automation-skill` symlink. State at `~/.browser-skill/` is preserved by default.
139
+
140
+ ## Roadmap
141
+
142
+ See `docs/superpowers/specs/2026-04-27-browser-automation-skill-design.md` for the design and `docs/superpowers/plans/` for executable plans. Current "what's next" lives in `docs/superpowers/HANDOFF.md` (refreshed after every shipped PR).
143
+
144
+ **v1.2 work ✅ COMPLETE.** Remaining hardening (all opt-in, none blocking): Phase 11 v2 backlog A2-A6 (slug heuristic / `--auto-record` / pattern-equivalence canonicalization / `self_heal_history[]` audit trail / active observation `recent_urls.jsonl`); daemon e2e for playwright-lib selector path; press cache-scope decision codification; Phase 12 backlog (TOON output mode for tabular verbs, plugin-wrapper distribution shape, wire remaining 25 verbs to `stats_run_adapter_emit`); Phase 13 backlog (strong-fingerprint mode that captures dimensions at `browser-do record` time instead of parsing them out of the cached selector string; LLM-judge upgrade for the `semantic` post-condition matcher).
package/SECURITY.md ADDED
@@ -0,0 +1,39 @@
1
+ # Security policy
2
+
3
+ ## Threat model
4
+
5
+ This skill is for single-developer, local-machine use.
6
+
7
+ ### In scope (we defend against)
8
+ - Credentials leaking via argv / `ps` / shell history / git / Claude transcript
9
+ - Captures (HARs / console / screenshots) leaking auth tokens (Phase 7 sanitization)
10
+ - Sessions injected into the wrong origin (Phase 5 origin binding)
11
+ - Accidental commits of any credential-shaped file
12
+
13
+ ### Out of scope
14
+ - Malware on your machine
15
+ - Compromised macOS / Linux kernel
16
+ - OS keychain compromise
17
+ - Compromised upstream tool (Playwright, chrome-devtools-mcp, Obscura)
18
+ - Compromised npm / cargo dependency
19
+ - Targeted nation-state attacker
20
+
21
+ ## Reporting vulnerabilities
22
+
23
+ Use GitHub Security Advisories (private disclosure path) for any vulnerability. Do **not** open a public issue for security bugs.
24
+
25
+ PGP key: (TBD on first release).
26
+
27
+ ## Defense layers (full set lands across phases)
28
+
29
+ | Layer | Phase |
30
+ |---|---|
31
+ | Filesystem perms (0700/0600, umask 077) | 1 |
32
+ | Pre-commit credential-leak blocker | 1 |
33
+ | Process argv invariants (creds via stdin only) | 5 |
34
+ | Origin binding (sessions refuse cross-origin) | 5 |
35
+ | OS keychain backend | 5 |
36
+ | Typed-phrase confirmations for risky paths | 5 |
37
+ | Capture sanitization (HAR + console + DOM) | 7 |
38
+
39
+ See `docs/superpowers/specs/2026-04-27-browser-automation-skill-design.md` §8 for the full security design.
package/SKILL.md ADDED
@@ -0,0 +1,206 @@
1
+ ---
2
+ name: browser-automation-skill
3
+ description: Drives a real browser from Claude Code by routing across four backends (chrome-devtools-mcp, playwright-cli, playwright-lib, obscura), so verbs like open/click/fill/scrape/inspect/audit pick the cheapest adapter that supports each operation. Persists credentials, sessions, captures, and per-action telemetry strictly local under $HOME/.browser-skill/ (mode 0700 dir, 0600 files); secrets never appear on argv, in git, or in the Claude transcript. Surfaces a balance-of-tokens-accuracy-latency audit via browser-stats.
4
+ when_to_use: User mentions a browser task — registering a site, capturing a session, verifying a page, filling a form, capturing console errors, running a lighthouse audit, scraping multiple URLs, debugging a UI bug iteratively, replaying a recorded flow, or auditing skill efficiency (browser-stats report/tune).
5
+ argument-hint: [verb] [--site NAME] [--session NAME] [--tool NAME] [--dry-run]
6
+ allowed-tools: Bash(bash *) Bash(jq *) Bash(chmod *) Bash(mkdir *) Bash(stat *) Bash(rm *) Bash(mv *) Bash(cat *) Bash(sqlite3 *) Bash(awk *) Bash(sed *) Bash(grep *) Bash(openssl *) Bash(date *) Bash(wc *) Bash(tr *) Bash(tail *) Bash(head *) Bash(sleep *) Bash(printf *) Bash(python3 *)
7
+ model: sonnet
8
+ effort: low
9
+ ---
10
+
11
+ # browser-automation-skill
12
+
13
+ Drive a real browser from Claude Code via four routed tools (chrome-devtools-mcp / playwright-cli / playwright-lib / obscura). 42 verbs covering site/session/credential management, navigation, snapshot+ref-based interaction, capture pipelines (console/network/screenshot/Lighthouse), declarative flow runner with replay+diff, a per-archetype memory cache (`browser-do`) that lets agents skip LLM ref-resolution on repeat actions, and per-schema state migration tooling (`browser-migrate`).
14
+
15
+ ## Verbs
16
+
17
+ ### Site + session + credential management
18
+
19
+ | Verb | What it does | Example |
20
+ |---|---|---|
21
+ | `doctor` | Health check: deps, state dir mode, disk encryption, adapters | `bash "${CLAUDE_SKILL_DIR}/scripts/browser-doctor.sh"` |
22
+ | `add-site` | Register a site profile | `… add-site --name prod --url https://app.example.com` |
23
+ | `list-sites` | List registered sites | `… list-sites` |
24
+ | `show-site` | Show one site's profile JSON | `… show-site --name prod` |
25
+ | `remove-site` | Typed-name confirmed delete | `… remove-site --name prod --yes-i-know` |
26
+ | `use` | Get / set / clear current site | `… use --set prod` |
27
+ | `login` | Capture a Playwright storageState into a session | `… login --site prod --as prod--admin --interactive` |
28
+ | `list-sessions` | List captured sessions (optionally filter by site) | `… list-sessions --site prod` |
29
+ | `show-session` | Show session metadata (NEVER cookie/token values) | `… show-session --as prod--admin` |
30
+ | `remove-session`| Typed-name confirmed delete of a captured session | `… remove-session --as prod--admin --yes-i-know` |
31
+ | `creds-add` | Register credential (smart per-OS backend; AP-7 stdin-only; declares `--auth-flow`) | `printf 'pw' \| … creds-add --site prod --as prod--admin --password-stdin --auth-flow single-step-username-password` |
32
+ | `creds-list` | List credentials (optional `--site` filter; metadata only) | `… creds-list --site prod` |
33
+ | `creds-show` | Show credential metadata (NEVER secret unless `--reveal` typed-phrase confirmed) | `… creds-show --as prod--admin` |
34
+ | `creds-remove` | Typed-name confirmed delete | `… creds-remove --as prod--admin --yes-i-know` |
35
+ | `creds-migrate` | Move credential between backends (fail-safe ordering) | `… creds-migrate --as prod--admin --to keychain --yes-i-know` |
36
+ | `creds-totp` | Generate current 6-digit TOTP code (RFC 6238) | `… creds-totp --as prod--admin` |
37
+ | `creds-rotate-totp` | Re-enroll TOTP shared secret (typed-phrase confirmed) | `printf '%s' NEW_BASE32 \| … creds-rotate-totp --as prod--admin --totp-secret-stdin --yes-i-know` |
38
+
39
+ ### Navigation + interaction
40
+
41
+ | Verb | What it does | Example |
42
+ |---|---|---|
43
+ | `open` | Open a URL in the picked browser adapter | `… open --url https://app.example.com` |
44
+ | `snapshot` | Capture an `eN`-indexed accessibility snapshot | `… snapshot` |
45
+ | `click` | Click element by `--ref eN` or `--selector CSS` | `… click --ref e3` |
46
+ | `fill` | Fill input — `--text VALUE` or `--secret-stdin`; `--ref eN` or `--selector CSS` | `… fill --ref e3 --text "search query"` |
47
+ | `hover` | Pointer hover — `--ref eN` or `--selector CSS` | `… hover --ref e5` |
48
+ | `press` | Keyboard key (Enter, Tab, Cmd+S, etc.) — focused element | `… press --key Enter` |
49
+ | `select` | Pick option from `<select>` — `--ref eN`/`--selector CSS` + `--value`/`--label`/`--index` | `… select --ref e7 --value US` |
50
+ | `drag` | Drag from `--src-ref` to `--dst-ref` | `… drag --src-ref e3 --dst-ref e9` |
51
+ | `wait` | Wait for selector / state | `… wait --selector .toast --state visible --timeout 5000` |
52
+ | `upload` | Upload file to `<input type=file>` ref | `… upload --ref e2 --file path.png` |
53
+ | `route` | Network mock / fulfill pattern | `… route --pattern '*/api/users' --status 200 --body '{}'` |
54
+ | `tab-list` | List open tabs | `… tab-list` |
55
+ | `tab-switch` | Switch active tab | `… tab-switch --to tab2` |
56
+ | `tab-close` | Close a tab | `… tab-close --to tab2` |
57
+
58
+ ### Capture + extract + audit
59
+
60
+ | Verb | What it does | Example |
61
+ |---|---|---|
62
+ | `inspect` | Page inspection — `--capture-console`, `--capture-network`, `--screenshot`, `--selector` (multi-flag aggregation; sanitized HAR + console; cdt-mcp real-mode) | `… inspect --capture-console --capture-network --capture` |
63
+ | `audit` | Lighthouse / perf-trace audit (cdt-mcp real-mode) | `… audit --lighthouse` |
64
+ | `extract` | Selector or JS extraction — `--selector CSS` / `--eval JS` (cdt-mcp); `--scrape u1 u2 ...` / `--stealth URL --eval EXPR` (obscura) | `… extract --selector .title` · `… extract --scrape https://a https://b --format json` |
65
+ | `assert` | Assertion — `--selector` + `--text-contains` predicate | `… assert --selector .toast-success --text-contains "Saved"` |
66
+
67
+ ### Flow runner
68
+
69
+ | Verb | What it does | Example |
70
+ |---|---|---|
71
+ | `flow run` | Execute a `.flow.yaml` file (declarative steps; `${var}` + `${refs.NAME}` templating; whole-flow capture) | `… flow run task.flow.yaml --var url_path=/users` |
72
+ | `flow record` | Wrap `playwright codegen`; emit `.flow.yaml`; password-canary write-side | `… flow record --site prod --out task.flow.yaml` |
73
+ | `replay` | Re-execute a capture's steps; structured per-step diff | `… replay 042 --strict` |
74
+ | `history list` | Enumerate captures (newest first) | `… history list --limit 10` |
75
+ | `history show` | Show one capture's meta + steps | `… history show 042` |
76
+ | `history diff` | Diff two captures' step events | `… history diff 041 042` |
77
+ | `history clear` | Manual prune (`--keep N` / `--days D` / `--not-baseline`); honors `is_baseline:true` skip-rule | `… history clear --keep 100` |
78
+ | `baseline save` | Mark capture as baseline (`meta.is_baseline:true` + `baselines.json` entry) | `… baseline save 042 --as after-redesign` |
79
+ | `baseline list` | List named baselines | `… baseline list` |
80
+ | `baseline remove` | Remove baseline marker (capture dir untouched) | `… baseline remove after-redesign --yes-i-know` |
81
+
82
+ ### Telemetry / audit / tuning (`browser-stats`)
83
+
84
+ | Verb | What it does | Example |
85
+ |---|---|---|
86
+ | `stats rebuild` | Tail `memory/stats.jsonl` from cursor → upsert into `memory/stats.db`. Idempotent; builds schema on first run. | `bash scripts/browser-stats.sh rebuild` |
87
+ | `stats report` | Human-readable per-route × verb summary: success rate, post-condition hit-rate, p50 token-proxy bytes, avg duration, failure-mode histogram, oblivious_success count, cost ($) when `CLAUDE_USAGE_*` env injected. `--pareto` adds composite efficiency score. | `bash scripts/browser-stats.sh report --days 7 --pareto` |
88
+ | `stats mark` | User override: record `success` / `fail[:reason]` for one `span_id`. Audit-report applies overrides over self-reported outcomes. | `bash scripts/browser-stats.sh mark a1b2c3d4e5f6a7b8 fail:wrong_element_acted` |
89
+ | `stats tune` | Surface worst-performing `(verb, route)` candidates over last N days for `/autoresearch` handoff. Human-in-loop — never auto-mutates the skill. | `bash scripts/browser-stats.sh tune --days 30` |
90
+
91
+ Per-action events are emitted automatically by `open`, `click`, `fill`,
92
+ `snapshot`, and `extract` (covering all 4 routes). Adding emission to a new
93
+ verb = 3 lines (see [`references/browser-stats-cheatsheet.md`](references/browser-stats-cheatsheet.md)).
94
+ Schema: [`references/stats-schema.json`](references/stats-schema.json) — follows
95
+ OpenInference + OTel GenAI v1.40 conventions for forward-compat with
96
+ Langfuse/Phoenix/Jaeger exporters.
97
+
98
+ ### Memory cache (`browser-do`)
99
+
100
+ | Verb | What it does | Example |
101
+ |---|---|---|
102
+ | `do --intent` | Look up cached selector for `(site, archetype, intent)`; on hit dispatch existing verb (zero LLM tokens); on miss emit `cache_miss` event | `… do --site prod --verb click --intent "click delete" --pattern '/devices/:id'` |
103
+ | `do record` | Explicit cache write-back; auto-derives pattern + archetype-id; refuses `PASSWORD-CANARY` | `… do record --site prod --intent "click delete" --selector "button.delete" --url 'https://prod/devices/123'` |
104
+ | `do propose` | Auto-cluster URLs into URL patterns (`:id`, `:uuid`); emits proposals for clusters >= threshold; suppresses already-known | `… do propose --site prod --threshold 3 --url 'https://x/devices/1' --url '...'` |
105
+
106
+ ### Schema migration (`browser-migrate`)
107
+
108
+ | Verb | What it does | Example |
109
+ |---|---|---|
110
+ | `migrate check` | Read-only — enumerate pending migrations (one `_kind:migration_needed` event per registered migrator with current schema_version == from). No lock acquired; safe to call any time (and `doctor` does). | `bash scripts/browser-migrate.sh check` |
111
+ | `migrate status` | Echo current per-schema versions from `~/.browser-skill/versions.json`. Read-only. | `bash scripts/browser-migrate.sh status` |
112
+ | `migrate run` | Apply registered migrators. Atomic-swap + automatic backup; refuses bump on JSON validation failure. Destructive: requires `--yes` flag OR interactive typed-phrase `migrate now`. `--schema NAME` narrows scope. PID-tracked lock prevents concurrent runs. | `bash scripts/browser-migrate.sh run --yes --schema memory` |
113
+ | `migrate rollback` | Restore one schema from its most-recent backup. Requires `--schema NAME`. Destructive: requires `--yes` OR typed-phrase `migrate rollback <schema>`. | `bash scripts/browser-migrate.sh rollback --schema memory --yes` |
114
+ | `migrate clean-backups` | Prune old backups; keep newest `--keep N` per schema (default 5). Destructive: requires `--yes` OR typed-phrase `clean backups`. | `bash scripts/browser-migrate.sh clean-backups --keep 3 --yes` |
115
+
116
+ ## Migration & schema evolution
117
+
118
+ Skill state (`~/.browser-skill/`) is versioned per-schema (`versions.json`). Each schema (sites / sessions / credentials / captures / baselines / memory / config) carries its own `schema_version`; migrating one doesn't touch the others. When the skill ships a schema bump, it lands a migrator under `scripts/lib/migrators/<schema>/v<from>_to_<to>.sh`; the migrator becomes pending on every machine until the user runs `browser-migrate run`.
119
+
120
+ Key invariants:
121
+ - **Doctor never auto-migrates.** It only surfaces pending count as a `warn:` line; user runs `browser-migrate run` explicitly.
122
+ - **Atomic-swap + automatic backup.** Each migrated file is backed up to `backups/<schema>/<basename>.bak.v<prior_version>` (mode 0600) before the migrator runs. JSON validation via `jq -e .` precedes the version bump; failure restores from backup.
123
+ - **Manual rollback.** Single-step `rollback --schema NAME` restores from the newest backup. Multi-version chains require multiple invocations.
124
+ - **Lock file** (`~/.browser-skill/.migrate.lock`) prevents concurrent runs; stale PID auto-cleared.
125
+
126
+ Today's only real migration is the no-op `memory v1_to_v2` identity bump (bumps `schema_version` from 1 to 2; no data shape change). Future per-schema migrators land case-by-case (~30 LOC + ~3 bats per new migrator).
127
+
128
+ `${CLAUDE_SKILL_DIR}` is the absolute path Claude Code injects when invoking the skill — symlink under `~/.claude/skills/`.
129
+
130
+ `${CLAUDE_SKILL_DIR}` is the absolute path that Claude Code injects when it
131
+ invokes the skill — it points at the symlink under `~/.claude/skills/`. Use it
132
+ in command examples so they work whether the user installed at `--user` or
133
+ `--project` scope.
134
+
135
+ ## Agent-workflow recipes (end-to-end command sequences)
136
+
137
+ See [`references/recipes/agent-workflows/`](references/recipes/agent-workflows/README.md) for tutorial-shaped walkthroughs:
138
+
139
+ - [`login-then-scrape.md`](references/recipes/agent-workflows/login-then-scrape.md) — first task: register site, capture session, bulk scrape
140
+ - [`incremental-pattern-discovery.md`](references/recipes/agent-workflows/incremental-pattern-discovery.md) — passive observation → propose → cache-hit loop end-to-end
141
+ - [`flow-record-and-replay.md`](references/recipes/agent-workflows/flow-record-and-replay.md) — capture a manual interaction, replay, diff against baseline
142
+ - [`cache-driven-bulk-operation.md`](references/recipes/agent-workflows/cache-driven-bulk-operation.md) — 50+ actions at zero LLM tokens (ROI proof)
143
+
144
+ For pattern recipes (codified discipline: privacy-canary, path-security, cache-write-security, etc.) see [`references/recipes/`](references/recipes/).
145
+
146
+ ## Tools
147
+
148
+ The skill routes verbs to one of these underlying tools (precedence is decided
149
+ by [router.sh](scripts/lib/router.sh); see [routing heuristics](references/routing-heuristics.md)
150
+ for the rules):
151
+
152
+ <!-- BEGIN AUTOGEN: tools-table — generated by scripts/regenerate-docs.sh -->
153
+ | Tool | Strengths | Cheatsheet |
154
+ |---|---|---|
155
+ | chrome-devtools-mcp | declares 18 verbs | [references/chrome-devtools-mcp-cheatsheet.md](references/chrome-devtools-mcp-cheatsheet.md) |
156
+ | obscura | declares 1 verbs | [references/obscura-cheatsheet.md](references/obscura-cheatsheet.md) |
157
+ | playwright-cli | declares 4 verbs | [references/playwright-cli-cheatsheet.md](references/playwright-cli-cheatsheet.md) |
158
+ | playwright-lib | declares 5 verbs | [references/playwright-lib-cheatsheet.md](references/playwright-lib-cheatsheet.md) |
159
+ <!-- END AUTOGEN: tools-table -->
160
+
161
+ ## Before running anything
162
+
163
+ If `doctor` reports `~/.browser-skill` missing, run `./install.sh` (or
164
+ `./install.sh --with-hooks` for the credential-leak blocker).
165
+
166
+ `doctor` also surfaces (advisory; never fails):
167
+
168
+ - **Pending schema migrations** — `warn: N pending migration(s) — run 'browser-migrate check' for details`.
169
+ Doctor never auto-migrates (MIG4 invariant from Phase 10 design); apply via `browser-migrate run`.
170
+ - **Memory cache hit-rate** — `ok: memory cache hit rate: X% (H/T events)` once
171
+ `browser-do --intent` has run at least once (writer landed in Phase 11 v2 part 1;
172
+ events.jsonl is lazy-created mode 0600 inside the mode-0700 memory dir).
173
+ Cheapest daily ROI signal: high hit-rate = the cache is paying for itself; low/empty = repetition isn't compounding yet.
174
+
175
+ ## Output contract
176
+
177
+ Every verb prints zero or more streaming JSON lines, then ends with a
178
+ single-line JSON summary. Parse with jq; route on `.status` (`ok`,
179
+ `partial`, `error`, `empty`, `aborted`).
180
+
181
+ ```
182
+ $ bash scripts/browser-doctor.sh | tail -1 | jq .
183
+ {"verb":"doctor","tool":"none","why":"health-check","status":"ok","problems":0,"duration_ms":42}
184
+ ```
185
+
186
+ ## Storage layout
187
+
188
+ ```
189
+ ~/.browser-skill/ # mode 0700
190
+ ├── version # schema marker
191
+ ├── config.json # mode 0600; retention thresholds
192
+ ├── current # current site name (mode 0600, [personal])
193
+ ├── baselines.json # mode 0600; named baseline registry (Phase 9)
194
+ ├── sites/ <name>.json + .meta.json # mode 0600 ([shareable])
195
+ ├── sessions/ <name>.json + .meta.json # mode 0600 ([PERSONAL — gitignored])
196
+ ├── credentials/ # Phase 5 (keychain / libsecret / plaintext)
197
+ ├── captures/ <NNN>/ # Phase 7 (snapshot.json, console.json, network.har, steps.jsonl, meta.json)
198
+ └── memory/ <site>/ # Phase 11 ([PERSONAL — gitignored])
199
+ ├── patterns.json # mode 0600; URL pattern → archetype-id
200
+ └── archetypes/<id>.json # mode 0600; cached interactions per archetype
201
+ ```
202
+
203
+ ## Roadmap
204
+
205
+ See `docs/superpowers/specs/2026-04-27-browser-automation-skill-design.md` for
206
+ the full design and `docs/superpowers/plans/` for phase plans.
package/bin/cli.mjs ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ // bin/cli.mjs — symlink-safe entry-point for the browser-automation-skill MCP
3
+ // server. npm installs this file as ~/.../.bin/browser-automation-skill via
4
+ // a symlink; the bash script we delegate to (scripts/browser-mcp.sh) uses
5
+ // `cd "$(dirname ...)" && pwd` which does NOT resolve the symlink and would
6
+ // look for scripts/lib/common.sh inside .bin/ if invoked directly.
7
+ //
8
+ // import.meta.url is resolved by Node against the realpath of the loaded
9
+ // module, so fileURLToPath(...) here returns this file's true location
10
+ // inside the published package — letting us reliably locate scripts/.
11
+ //
12
+ // We forward argv + stdio so the spawned bash process speaks JSON-RPC
13
+ // over the same stdio channel its MCP client opened to us.
14
+
15
+ import { spawn } from 'node:child_process';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { dirname, resolve } from 'node:path';
18
+ import { existsSync } from 'node:fs';
19
+
20
+ const here = dirname(fileURLToPath(import.meta.url));
21
+ const pkgRoot = resolve(here, '..');
22
+ const script = resolve(pkgRoot, 'scripts/browser-mcp.sh');
23
+
24
+ if (!existsSync(script)) {
25
+ console.error(
26
+ `browser-automation-skill: missing entry script at ${script}.\n` +
27
+ 'Reinstall the package or report a packaging bug.'
28
+ );
29
+ process.exit(1);
30
+ }
31
+
32
+ const args = process.argv.slice(2);
33
+ const child = spawn('bash', [script, ...(args.length ? args : ['serve'])], {
34
+ stdio: 'inherit',
35
+ env: process.env,
36
+ });
37
+
38
+ child.on('error', (err) => {
39
+ console.error(`browser-automation-skill: failed to spawn bash: ${err.message}`);
40
+ process.exit(127);
41
+ });
42
+
43
+ child.on('exit', (code, signal) => {
44
+ if (signal) {
45
+ process.kill(process.pid, signal);
46
+ return;
47
+ }
48
+ process.exit(code ?? 0);
49
+ });
50
+
51
+ for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
52
+ process.on(sig, () => {
53
+ if (!child.killed) child.kill(sig);
54
+ });
55
+ }
package/install.sh ADDED
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env bash
2
+ # install.sh — preflight + state dir + symlink + (opt) git hooks. Idempotent.
3
+ set -euo pipefail
4
+ IFS=$'\n\t'
5
+
6
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ # shellcheck source=scripts/lib/common.sh
8
+ # shellcheck disable=SC1091
9
+ source "${REPO_ROOT}/scripts/lib/common.sh"
10
+
11
+ WITH_HOOKS=0
12
+ DRY_RUN=0
13
+ MODE=user # phase-1 only supports --user; --project arrives in a later phase
14
+
15
+ usage() {
16
+ cat <<'USAGE'
17
+ Usage: ./install.sh [options]
18
+
19
+ --user (default) symlink to ~/.claude/skills/, state at ~/.browser-skill/
20
+ --with-hooks enable .githooks/pre-commit credential-leak blocker
21
+ --dry-run print what would happen, change nothing
22
+ -h, --help this message
23
+ USAGE
24
+ }
25
+
26
+ for arg in "$@"; do
27
+ case "${arg}" in
28
+ --user) MODE=user ;;
29
+ --with-hooks) WITH_HOOKS=1 ;;
30
+ --dry-run) DRY_RUN=1 ;;
31
+ -h|--help) usage; exit 0 ;;
32
+ *) warn "ignoring unknown arg: ${arg}" ;;
33
+ esac
34
+ done
35
+
36
+ preflight() {
37
+ command -v jq >/dev/null 2>&1 || die "${EXIT_PREFLIGHT_FAILED}" "jq required but not found. Remediation: brew install jq (macOS) or apt install jq (Debian)"
38
+ ok "jq found: $(command -v jq)"
39
+ command -v python3 >/dev/null 2>&1 || die "${EXIT_PREFLIGHT_FAILED}" "python3 required but not found"
40
+ ok "python3 found: $(command -v python3)"
41
+ local major="${BASH_VERSINFO[0]:-0}"
42
+ [ "${major}" -ge 4 ] || die "${EXIT_PREFLIGHT_FAILED}" "bash >= 4 required (have ${BASH_VERSION}). Remediation: brew install bash"
43
+ ok "bash version: ${BASH_VERSION}"
44
+ }
45
+
46
+ ok "browser-automation-skill installer (mode=${MODE} dry-run=${DRY_RUN})"
47
+ preflight
48
+
49
+ if [ "${DRY_RUN}" = "1" ]; then
50
+ init_paths
51
+ ok "dry-run: would create ${BROWSER_SKILL_HOME} and symlink to ${HOME}/.claude/skills/browser-automation-skill"
52
+ exit 0
53
+ fi
54
+
55
+ init_paths
56
+
57
+ create_state_dir() {
58
+ mkdir -p \
59
+ "${BROWSER_SKILL_HOME}" \
60
+ "${SITES_DIR}" \
61
+ "${SESSIONS_DIR}" \
62
+ "${CREDENTIALS_DIR}" \
63
+ "${CAPTURES_DIR}" \
64
+ "${FLOWS_DIR}"
65
+ chmod 700 \
66
+ "${BROWSER_SKILL_HOME}" \
67
+ "${SITES_DIR}" \
68
+ "${SESSIONS_DIR}" \
69
+ "${CREDENTIALS_DIR}" \
70
+ "${CAPTURES_DIR}" \
71
+ "${FLOWS_DIR}"
72
+ # Defense in depth: if this dir ever ends up inside a git repo, ignore it.
73
+ printf '*\n' > "${BROWSER_SKILL_HOME}/.gitignore"
74
+ # Schema version marker.
75
+ printf '1\n' > "${BROWSER_SKILL_HOME}/version"
76
+ # Phase 7 part 1-v: default capture-retention config. Idempotent — never
77
+ # overwrite an existing user-edited config. Defaults per parent spec §4.5.
78
+ if [ ! -f "${CONFIG_FILE}" ]; then
79
+ cat > "${CONFIG_FILE}" <<'EOF'
80
+ {
81
+ "schema_version": 1,
82
+ "retention_days": 14,
83
+ "retention_count": 500,
84
+ "warn_at_pct": 90
85
+ }
86
+ EOF
87
+ chmod 600 "${CONFIG_FILE}"
88
+ fi
89
+ ok "state dir ready: ${BROWSER_SKILL_HOME}"
90
+ }
91
+
92
+ create_state_dir
93
+
94
+ install_symlink() {
95
+ local skills_dir="${HOME}/.claude/skills"
96
+ local link="${skills_dir}/browser-automation-skill"
97
+ mkdir -p "${skills_dir}"
98
+
99
+ if [ -L "${link}" ]; then
100
+ ln -sfn "${REPO_ROOT}" "${link}"
101
+ ok "updated existing symlink: ${link} -> ${REPO_ROOT}"
102
+ elif [ -e "${link}" ]; then
103
+ die "${EXIT_PREFLIGHT_FAILED}" "${link} exists and is not a symlink; refusing to overwrite. Move it aside and re-run."
104
+ else
105
+ ln -s "${REPO_ROOT}" "${link}"
106
+ ok "created symlink: ${link} -> ${REPO_ROOT}"
107
+ fi
108
+ }
109
+
110
+ install_symlink
111
+
112
+ if [ "${WITH_HOOKS}" = "1" ]; then
113
+ bash "${REPO_ROOT}/scripts/install-git-hooks.sh"
114
+ fi
115
+
116
+ ok "running doctor..."
117
+ doctor_rc=0
118
+ doctor_out="$(bash "${REPO_ROOT}/scripts/browser-doctor.sh" 2>&1)" || doctor_rc=$?
119
+ printf '%s\n' "${doctor_out}"
120
+
121
+ # Count adapters_ok from the doctor JSON summary line (last line).
122
+ adapters_ok="$(printf '%s\n' "${doctor_out}" | tail -1 | jq -r '.adapters_ok // 0' 2>/dev/null || printf '0')"
123
+
124
+ ok "install complete; next steps:"
125
+ ok " 1. /browser doctor (verify in Claude Code)"
126
+ ok " 2. /browser add-site --name NAME --url URL (register your first site)"
127
+ ok " 3. /browser use --set NAME (set as current)"
128
+
129
+ if [ "${doctor_rc}" -ne 0 ]; then
130
+ warn "doctor reported issues (exit ${doctor_rc}); run 'bash scripts/browser-doctor.sh' to review"
131
+ fi
132
+
133
+ # v1-polish: when no adapters installed, surface the install-adapter guidance
134
+ # explicitly so first-time users don't have to decode the doctor JSON.
135
+ if [ "${adapters_ok}" = "0" ] || [ -z "${adapters_ok}" ]; then
136
+ warn ""
137
+ warn "no browser adapters installed. install at least one to drive a real browser:"
138
+ warn " - chrome-devtools-mcp (recommended; most-complete): npx -y chrome-devtools-mcp@latest"
139
+ warn " - playwright-cli (npm; supports headless+headed): npm i -g playwright @playwright/test @playwright/cli && playwright install chromium"
140
+ warn " - obscura (single-binary; scrape+stealth-only): https://github.com/h4ckf0r0day/obscura/releases"
141
+ warn ""
142
+ warn "without an adapter: site/session/credential management + cache record + propose work; navigation/interaction/capture verbs return EXIT_TOOL_MISSING (21)."
143
+ fi