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.
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/SECURITY.md +39 -0
- package/SKILL.md +206 -0
- package/bin/cli.mjs +55 -0
- package/install.sh +143 -0
- package/package.json +54 -0
- package/references/adapter-candidates.md +40 -0
- package/references/browser-mcp-cheatsheet.md +132 -0
- package/references/browser-stats-cheatsheet.md +155 -0
- package/references/chrome-devtools-mcp-cheatsheet.md +232 -0
- package/references/midscene-integration.md +359 -0
- package/references/obscura-cheatsheet.md +103 -0
- package/references/playwright-cli-cheatsheet.md +64 -0
- package/references/playwright-lib-cheatsheet.md +90 -0
- package/references/recipes/add-a-tool-adapter.md +134 -0
- package/references/recipes/agent-workflows/README.md +37 -0
- package/references/recipes/agent-workflows/cache-driven-bulk-operation.md +110 -0
- package/references/recipes/agent-workflows/flow-record-and-replay.md +102 -0
- package/references/recipes/agent-workflows/incremental-pattern-discovery.md +125 -0
- package/references/recipes/agent-workflows/login-then-scrape.md +100 -0
- package/references/recipes/anti-patterns-tool-extension.md +182 -0
- package/references/recipes/body-bytes-not-body.md +139 -0
- package/references/recipes/cache-write-security.md +210 -0
- package/references/recipes/fingerprint-rescue.md +154 -0
- package/references/recipes/model-routing.md +143 -0
- package/references/recipes/path-security.md +138 -0
- package/references/recipes/privacy-canary.md +96 -0
- package/references/recipes/visual-rescue-hook.md +182 -0
- package/references/stats-prices.json +42 -0
- package/references/stats-schema.json +77 -0
- package/references/tool-versions.md +8 -0
- package/scripts/browser-add-site.sh +113 -0
- package/scripts/browser-assert.sh +106 -0
- package/scripts/browser-audit.sh +68 -0
- package/scripts/browser-baseline.sh +135 -0
- package/scripts/browser-click.sh +100 -0
- package/scripts/browser-creds-add.sh +254 -0
- package/scripts/browser-creds-list.sh +67 -0
- package/scripts/browser-creds-migrate.sh +122 -0
- package/scripts/browser-creds-remove.sh +69 -0
- package/scripts/browser-creds-rotate-totp.sh +109 -0
- package/scripts/browser-creds-show.sh +82 -0
- package/scripts/browser-creds-totp.sh +94 -0
- package/scripts/browser-do.sh +630 -0
- package/scripts/browser-doctor.sh +365 -0
- package/scripts/browser-drag.sh +90 -0
- package/scripts/browser-extract.sh +192 -0
- package/scripts/browser-fill.sh +142 -0
- package/scripts/browser-flow.sh +316 -0
- package/scripts/browser-history.sh +187 -0
- package/scripts/browser-hover.sh +92 -0
- package/scripts/browser-inspect.sh +188 -0
- package/scripts/browser-list-sessions.sh +78 -0
- package/scripts/browser-list-sites.sh +42 -0
- package/scripts/browser-login.sh +279 -0
- package/scripts/browser-mcp.sh +65 -0
- package/scripts/browser-migrate.sh +195 -0
- package/scripts/browser-open.sh +134 -0
- package/scripts/browser-press.sh +80 -0
- package/scripts/browser-remove-session.sh +72 -0
- package/scripts/browser-remove-site.sh +68 -0
- package/scripts/browser-replay.sh +206 -0
- package/scripts/browser-route.sh +174 -0
- package/scripts/browser-select.sh +122 -0
- package/scripts/browser-show-session.sh +57 -0
- package/scripts/browser-show-site.sh +37 -0
- package/scripts/browser-snapshot.sh +176 -0
- package/scripts/browser-stats.sh +522 -0
- package/scripts/browser-tab-close.sh +112 -0
- package/scripts/browser-tab-list.sh +70 -0
- package/scripts/browser-tab-switch.sh +111 -0
- package/scripts/browser-upload.sh +132 -0
- package/scripts/browser-use.sh +60 -0
- package/scripts/browser-vlm.sh +707 -0
- package/scripts/browser-wait.sh +97 -0
- package/scripts/install-git-hooks.sh +16 -0
- package/scripts/lib/capture.sh +356 -0
- package/scripts/lib/common.sh +262 -0
- package/scripts/lib/credential.sh +237 -0
- package/scripts/lib/fingerprint-rescue.js +123 -0
- package/scripts/lib/flow.sh +448 -0
- package/scripts/lib/flow_record.sh +210 -0
- package/scripts/lib/mask.sh +49 -0
- package/scripts/lib/memory.sh +427 -0
- package/scripts/lib/migrate.sh +390 -0
- package/scripts/lib/migrators/README.md +23 -0
- package/scripts/lib/migrators/memory/v1_to_v2.sh +15 -0
- package/scripts/lib/migrators/recent_urls/README.md +13 -0
- package/scripts/lib/migrators/stats/README.md +24 -0
- package/scripts/lib/node/chrome-devtools-bridge.mjs +1812 -0
- package/scripts/lib/node/mcp-server.mjs +531 -0
- package/scripts/lib/node/mcp-tools.json +68 -0
- package/scripts/lib/node/playwright-driver.mjs +1104 -0
- package/scripts/lib/node/totp-core.mjs +52 -0
- package/scripts/lib/node/totp.mjs +52 -0
- package/scripts/lib/node/url-pattern-cluster.mjs +102 -0
- package/scripts/lib/node/url-pattern-resolver.mjs +77 -0
- package/scripts/lib/output.sh +79 -0
- package/scripts/lib/router.sh +342 -0
- package/scripts/lib/sanitize.sh +107 -0
- package/scripts/lib/secret/keychain.sh +91 -0
- package/scripts/lib/secret/libsecret.sh +74 -0
- package/scripts/lib/secret/plaintext.sh +75 -0
- package/scripts/lib/secret_backend_select.sh +57 -0
- package/scripts/lib/session.sh +153 -0
- package/scripts/lib/site.sh +126 -0
- package/scripts/lib/stats.sh +419 -0
- package/scripts/lib/tool/.gitkeep +0 -0
- package/scripts/lib/tool/chrome-devtools-mcp.sh +349 -0
- package/scripts/lib/tool/obscura.sh +249 -0
- package/scripts/lib/tool/playwright-cli.sh +155 -0
- package/scripts/lib/tool/playwright-lib.sh +106 -0
- package/scripts/lib/verb_helpers.sh +222 -0
- package/scripts/lib/visual-rescue-default.sh +145 -0
- package/scripts/regenerate-docs.sh +99 -0
- package/uninstall.sh +51 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Recipe — visual-rescue hook (Path 3)
|
|
2
|
+
|
|
3
|
+
`browser-do.sh` exposes a hook seam BETWEEN Phase-13 fingerprint-rescue
|
|
4
|
+
failure and the cloud-LLM fall-through. The hook decides whether a cached
|
|
5
|
+
selector is still semantically the right target — typically by looking at
|
|
6
|
+
a screenshot through a local VLM. When the hook says yes, the cache is
|
|
7
|
+
preserved + no cloud-LLM round-trip happens.
|
|
8
|
+
|
|
9
|
+
## When this fires
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
cached selector + page fingerprint match → 0 LLM tokens
|
|
13
|
+
↓ DOM diff detected
|
|
14
|
+
Phase-13 silent fingerprint rescue → 0 LLM tokens
|
|
15
|
+
↓ rescue failed (no good selector candidate)
|
|
16
|
+
[HOOK FIRES HERE] ← Path 3
|
|
17
|
+
↓ hook returns "yes" → keep cache, 0 cloud tokens
|
|
18
|
+
↓ hook returns "no" / unreachable → fall through
|
|
19
|
+
cloud LLM ref-resolution → 1 LLM round-trip
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Enabling the hook
|
|
23
|
+
|
|
24
|
+
Two env vars must be set when invoking `browser-do --intent ...`:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
export BROWSER_SKILL_VISION_FALLBACK=1
|
|
28
|
+
export BROWSER_SKILL_VISUAL_RESCUE_CMD=/abs/path/to/your-hook.sh
|
|
29
|
+
chmod +x "${BROWSER_SKILL_VISUAL_RESCUE_CMD}"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If either is unset (or the hook isn't executable), the tier is skipped
|
|
33
|
+
silently and behaviour matches today's baseline.
|
|
34
|
+
|
|
35
|
+
### Fast start — use the bundled default probe
|
|
36
|
+
|
|
37
|
+
A ready-to-use canonical probe ships at `scripts/lib/visual-rescue-default.sh`
|
|
38
|
+
(text-mode v1 — see "Modes" below). Wire it directly:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
export BROWSER_SKILL_VISION_FALLBACK=1
|
|
42
|
+
export BROWSER_SKILL_VISUAL_RESCUE_CMD="$(realpath scripts/lib/visual-rescue-default.sh)"
|
|
43
|
+
# Optional — defaults to http://127.0.0.1:8080 (matches `bash scripts/browser-vlm.sh start`)
|
|
44
|
+
# export BROWSER_SKILL_VLM_PORT=8080
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
That gets Path 3 live with zero custom code. Confirm wiring via doctor:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
bash scripts/browser-doctor.sh | grep "local VLM"
|
|
51
|
+
# ok: local VLM reachable @ http://127.0.0.1:8080 …
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Modes
|
|
55
|
+
|
|
56
|
+
**v1 — text-mode (the bundled default).** Reads the accessibility-tree YAML
|
|
57
|
+
snapshot, sends `(intent, selector, snapshot-text)` to the local LLM, asks
|
|
58
|
+
yes/no. Cheap (~200ms total), works against any OpenAI-compatible endpoint
|
|
59
|
+
(local llama-server, vLLM, ollama, etc — not just VLMs).
|
|
60
|
+
|
|
61
|
+
**v2 — vision-mode (planned).** Crops a screenshot of the cached element's
|
|
62
|
+
bbox, sends image to a VLM, asks yes/no. Stronger signal for visual-only UIs
|
|
63
|
+
(canvas elements, custom-painted widgets), but needs screenshot-from-live-
|
|
64
|
+
session infrastructure that isn't shipped yet. Roll your own per the example
|
|
65
|
+
below until v2 lands.
|
|
66
|
+
|
|
67
|
+
## Hook contract
|
|
68
|
+
|
|
69
|
+
The hook is invoked with **three positional args**:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
your-hook.sh SITE INTENT CACHED_SELECTOR
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- `SITE` — registered site name (e.g. `prod-app`)
|
|
76
|
+
- `INTENT` — natural-language intent from the original `browser-do --intent` call
|
|
77
|
+
- `CACHED_SELECTOR` — the CSS selector that was retrieved from `~/.browser-skill/memory/<site>/archetypes/<id>.json` (the one Phase-13 rescue couldn't salvage)
|
|
78
|
+
|
|
79
|
+
The hook must write **exactly `yes` or `no` to stdout** (single line, no
|
|
80
|
+
JSON envelope). Any other output is treated as "no". Exit code:
|
|
81
|
+
|
|
82
|
+
- `0` + stdout `yes` → cache preserved; verb dispatch reports success
|
|
83
|
+
- `0` + stdout `no` → fall through to cloud LLM
|
|
84
|
+
- non-zero exit → fall through (treated as "unreachable")
|
|
85
|
+
|
|
86
|
+
Stderr is ignored by `browser-do`; use it for hook-internal logging.
|
|
87
|
+
|
|
88
|
+
## Reference implementation — llama.cpp + Qwen3-VL-4B
|
|
89
|
+
|
|
90
|
+
A minimum hook that screenshots the current page (via `browser-snapshot.sh`
|
|
91
|
+
+ `browser-inspect.sh --screenshot`) and asks a local Qwen3-VL through
|
|
92
|
+
`llama-server` is shown below. This is a SAMPLE — write your own to taste.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
#!/usr/bin/env bash
|
|
96
|
+
# ~/.browser-skill/hooks/visual-rescue-llama.sh
|
|
97
|
+
set -euo pipefail
|
|
98
|
+
site="$1"; intent="$2"; selector="$3"
|
|
99
|
+
|
|
100
|
+
vlm_host="${BROWSER_SKILL_VLM_HOST:-127.0.0.1}"
|
|
101
|
+
vlm_port="${BROWSER_SKILL_VLM_PORT:-8080}"
|
|
102
|
+
endpoint="http://${vlm_host}:${vlm_port}/v1/chat/completions"
|
|
103
|
+
|
|
104
|
+
# Quick reachability gate — silent skip if VLM not running.
|
|
105
|
+
if ! curl -sfm 2 "http://${vlm_host}:${vlm_port}/health" >/dev/null; then
|
|
106
|
+
printf 'no\n'
|
|
107
|
+
exit 1
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# Take a transient screenshot via the inspect verb (Phase 7 captures dir).
|
|
111
|
+
# Find the most-recent screenshot file under captures/.
|
|
112
|
+
SCRIPTS_DIR="${BROWSER_SKILL_SCRIPTS_DIR:-${HOME}/.claude/skills/browser-automation-skill/scripts}"
|
|
113
|
+
bash "${SCRIPTS_DIR}/browser-inspect.sh" --site "${site}" --screenshot --capture \
|
|
114
|
+
>/dev/null 2>&1 || { printf 'no\n'; exit 1; }
|
|
115
|
+
captures_dir="${BROWSER_SKILL_HOME:-${HOME}/.browser-skill}/captures"
|
|
116
|
+
latest_id="$(jq -r '.latest' "${captures_dir}/_index.json" 2>/dev/null)"
|
|
117
|
+
png_path="${captures_dir}/${latest_id}/inspect-screenshot.png"
|
|
118
|
+
[ -f "${png_path}" ] || { printf 'no\n'; exit 1; }
|
|
119
|
+
|
|
120
|
+
# Ask the VLM: yes/no probe.
|
|
121
|
+
b64="$(base64 -i "${png_path}" | tr -d '\n')"
|
|
122
|
+
prompt="The user wants to: '${intent}'. The cached element was at CSS selector '${selector}'. Looking at this page, is there still a target element that matches the intent? Answer with ONLY 'yes' or 'no'."
|
|
123
|
+
|
|
124
|
+
resp="$(curl -sS -m 30 "${endpoint}" -H 'Content-Type: application/json' \
|
|
125
|
+
-d "$(jq -n --arg img "data:image/png;base64,${b64}" --arg p "${prompt}" '
|
|
126
|
+
{model:"q",max_tokens:5,messages:[{role:"user",content:[
|
|
127
|
+
{type:"text",text:$p},
|
|
128
|
+
{type:"image_url",image_url:{url:$img}}
|
|
129
|
+
]}]}')" 2>/dev/null)"
|
|
130
|
+
|
|
131
|
+
completion="$(printf '%s' "${resp}" | jq -r '.choices[0].message.content // ""' 2>/dev/null)"
|
|
132
|
+
case "${completion,,}" in
|
|
133
|
+
*yes*) printf 'yes\n' ;;
|
|
134
|
+
*) printf 'no\n' ;;
|
|
135
|
+
esac
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Smoke test the hook in isolation:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
echo "site=prod-app intent='click submit' selector='button.submit'"
|
|
142
|
+
~/.browser-skill/hooks/visual-rescue-llama.sh prod-app 'click submit' 'button.submit'
|
|
143
|
+
# → yes / no
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Telemetry
|
|
147
|
+
|
|
148
|
+
When the hook reports "yes" and `browser-do` accepts the rescue:
|
|
149
|
+
|
|
150
|
+
- A `_kind:"visual_rescue"` event line appears on stdout (machine-readable)
|
|
151
|
+
- A separate event lands in `~/.browser-skill/memory/stats.jsonl` with
|
|
152
|
+
`gen_ai_tool_name:"browser-do.visual_rescue"` and `rescued:true`. Run
|
|
153
|
+
`browser-stats report --route browser-do` to see your visual-rescue rate.
|
|
154
|
+
|
|
155
|
+
When the hook reports "no" or fails, NO visual_rescue event is emitted
|
|
156
|
+
(the original Phase-13 cache_miss + fail_count path runs unchanged).
|
|
157
|
+
|
|
158
|
+
## Cost frame
|
|
159
|
+
|
|
160
|
+
Rough numbers from the Phase-14 bench session (M3 Pro + Qwen3-VL-4B-q4_K_M):
|
|
161
|
+
|
|
162
|
+
| Step | Latency | Cloud tokens |
|
|
163
|
+
|---|---:|---:|
|
|
164
|
+
| Screenshot via inspect | ~1.5 s | 0 |
|
|
165
|
+
| base64 + curl + VLM yes/no | ~0.4 s | 0 |
|
|
166
|
+
| **Path 3 total** | **~2 s** | **0** |
|
|
167
|
+
| Cloud LLM ref-resolution (alternative) | ~1 s | full prompt + response |
|
|
168
|
+
|
|
169
|
+
Path 3 wins when avoided cloud-LLM cost exceeds local latency cost. For
|
|
170
|
+
high-volume cache flows (your registered prod sites with repeat actions),
|
|
171
|
+
this is "always" — the 2 s pays off after one avoided LLM round-trip.
|
|
172
|
+
|
|
173
|
+
## Why a hook, not a built-in
|
|
174
|
+
|
|
175
|
+
The skill is intentionally agnostic about HOW the visual probe reasons —
|
|
176
|
+
some users will want screenshot-crop + Qwen3-VL, others UI-TARS-7B,
|
|
177
|
+
others a yes/no LLM-as-judge against a text snapshot. Hardcoding one
|
|
178
|
+
approach would close the design space. The hook contract lets each user
|
|
179
|
+
ship their preferred probe without forking the skill.
|
|
180
|
+
|
|
181
|
+
A built-in default probe is planned for a future release; this recipe
|
|
182
|
+
will become the canonical reference once it lands.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_doc": "Anthropic model prices in USD per token. Sourced from docs.anthropic.com/pricing as of 2026-05-15. Used by `browser-stats report` to convert token counts into $ at query time (re-priceable). Override path via env: BROWSER_STATS_PRICES_FILE.",
|
|
3
|
+
"_last_updated": "2026-05-15",
|
|
4
|
+
"models": {
|
|
5
|
+
"claude-opus-4-7": {
|
|
6
|
+
"input": 0.000005,
|
|
7
|
+
"output": 0.000025,
|
|
8
|
+
"cache_create_5m": 0.00000625,
|
|
9
|
+
"cache_create_1h": 0.00001000,
|
|
10
|
+
"cache_create": 0.00000625,
|
|
11
|
+
"cache_read": 0.00000050
|
|
12
|
+
},
|
|
13
|
+
"claude-sonnet-4-6": {
|
|
14
|
+
"input": 0.000003,
|
|
15
|
+
"output": 0.000015,
|
|
16
|
+
"cache_create_5m": 0.00000375,
|
|
17
|
+
"cache_create_1h": 0.00000600,
|
|
18
|
+
"cache_create": 0.00000375,
|
|
19
|
+
"cache_read": 0.00000030
|
|
20
|
+
},
|
|
21
|
+
"claude-haiku-4-5": {
|
|
22
|
+
"input": 0.000001,
|
|
23
|
+
"output": 0.000005,
|
|
24
|
+
"cache_create_5m": 0.00000125,
|
|
25
|
+
"cache_create_1h": 0.00000200,
|
|
26
|
+
"cache_create": 0.00000125,
|
|
27
|
+
"cache_read": 0.00000010
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"modifiers": {
|
|
31
|
+
"_doc": "Multipliers applied at report time. `inference_geo=us` adds 1.1x; fast_mode is 6x and stacks; batch is 0.5x (mutually exclusive with fast_mode).",
|
|
32
|
+
"inference_geo_us": 1.1,
|
|
33
|
+
"fast_mode": 6.0,
|
|
34
|
+
"batch": 0.5
|
|
35
|
+
},
|
|
36
|
+
"server_tools": {
|
|
37
|
+
"_doc": "Server-side tool use billed per request, not tokens.",
|
|
38
|
+
"web_search_request": 0.01,
|
|
39
|
+
"web_fetch_request": 0.0,
|
|
40
|
+
"code_execution_session_hour": 0.08
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/xicv/browser-automation-skill/references/stats-schema.json",
|
|
4
|
+
"title": "browser-automation-skill stats event",
|
|
5
|
+
"description": "One JSONL line per adapter invocation written to ${BROWSER_SKILL_HOME}/memory/stats.jsonl. Field naming follows OpenInference + OTel GenAI v1.40 conventions (snake_case dot-name flattening) for forward-compat with OTLP exporters and Langfuse/Phoenix.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version",
|
|
9
|
+
"ts",
|
|
10
|
+
"span_id",
|
|
11
|
+
"trace_id",
|
|
12
|
+
"verb",
|
|
13
|
+
"adapter_route",
|
|
14
|
+
"outcome",
|
|
15
|
+
"rc",
|
|
16
|
+
"duration_ms",
|
|
17
|
+
"argv_bytes",
|
|
18
|
+
"stdout_bytes",
|
|
19
|
+
"stderr_bytes"
|
|
20
|
+
],
|
|
21
|
+
"properties": {
|
|
22
|
+
"schema_version": {"type": "integer", "const": 1},
|
|
23
|
+
"ts": {"type": "string", "format": "date-time", "description": "ISO 8601 with ms precision (UTC)."},
|
|
24
|
+
"span_id": {"type": "string", "pattern": "^[0-9a-f]{16}$"},
|
|
25
|
+
"trace_id": {"type": "string", "pattern": "^[0-9a-f]{16}$"},
|
|
26
|
+
"parent_span_id": {"type": ["string", "null"]},
|
|
27
|
+
"session_id": {"type": ["string", "null"], "description": "$CLAUDE_SESSION_ID if injected."},
|
|
28
|
+
|
|
29
|
+
"gen_ai_operation_name": {"type": "string", "const": "execute_tool"},
|
|
30
|
+
"gen_ai_tool_name": {"type": "string", "description": "e.g. 'chrome-devtools-mcp.click'. OTel-compliant."},
|
|
31
|
+
"gen_ai_tool_type": {"type": "string", "enum": ["function", "extension", "datastore"]},
|
|
32
|
+
|
|
33
|
+
"verb": {"type": "string", "description": "browser-* verb (open, click, fill, extract, ...)."},
|
|
34
|
+
"adapter_route": {"type": "string", "enum": ["chrome-devtools-mcp", "playwright-cli", "playwright-lib", "obscura"]},
|
|
35
|
+
"site": {"type": ["string", "null"]},
|
|
36
|
+
|
|
37
|
+
"selector_kind": {"type": "string", "enum": ["a11y_ref", "css", "xpath", "role", "text", "none"]},
|
|
38
|
+
"selector_value": {"type": ["string", "null"]},
|
|
39
|
+
|
|
40
|
+
"duration_ms": {"type": "integer", "minimum": 0},
|
|
41
|
+
"argv_bytes": {"type": "integer", "minimum": 0, "description": "Total argv bytes — input-token proxy when SDK fields absent."},
|
|
42
|
+
"stdout_bytes": {"type": "integer", "minimum": 0, "description": "Captured stdout bytes — output-token proxy."},
|
|
43
|
+
"stderr_bytes": {"type": "integer", "minimum": 0},
|
|
44
|
+
"rc": {"type": "integer", "description": "Adapter exit code (lib/common.sh EXIT_* table)."},
|
|
45
|
+
|
|
46
|
+
"outcome": {"type": "string", "enum": ["success", "fail", "partial", "skipped"]},
|
|
47
|
+
"failure_mode": {
|
|
48
|
+
"type": ["string", "null"],
|
|
49
|
+
"enum": [null,
|
|
50
|
+
"element_not_found", "element_ambiguous", "wrong_element_acted",
|
|
51
|
+
"stale_ref", "action_timeout", "navigation_mismatch",
|
|
52
|
+
"js_not_ready", "network_error", "captcha_blocked",
|
|
53
|
+
"auth_required", "popup_intercept", "extraction_mismatch",
|
|
54
|
+
"oblivious_success", "unknown_failure"
|
|
55
|
+
],
|
|
56
|
+
"description": "Phase 14 (Bundle #3) added unknown_failure as the catch-all so rc!=0 events with no classifier match surface in stats tune histograms instead of being dropped as null."
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
"model": {"type": ["string", "null"], "description": "From $CLAUDE_MODEL env. Null when invoked outside Claude Code."},
|
|
60
|
+
"service_tier": {"type": ["string", "null"], "enum": [null, "standard", "priority", "batch"]},
|
|
61
|
+
|
|
62
|
+
"gen_ai_usage_input_tokens": {"type": ["integer", "null"], "minimum": 0},
|
|
63
|
+
"gen_ai_usage_output_tokens": {"type": ["integer", "null"], "minimum": 0},
|
|
64
|
+
"gen_ai_usage_cache_read_input_tokens": {"type": ["integer", "null"], "minimum": 0},
|
|
65
|
+
"gen_ai_usage_cache_creation_input_tokens":{"type": ["integer", "null"], "minimum": 0},
|
|
66
|
+
|
|
67
|
+
"post_condition_target_type": {"type": ["string", "null"], "enum": [null, "url", "element_path", "element_value"]},
|
|
68
|
+
"post_condition_matcher": {"type": ["string", "null"], "enum": [null, "exact", "include", "semantic"]},
|
|
69
|
+
"post_condition_expected": {"type": ["string", "null"]},
|
|
70
|
+
"post_condition_observed": {"type": ["string", "null"]},
|
|
71
|
+
"post_condition_hit": {"type": ["boolean", "null"]},
|
|
72
|
+
|
|
73
|
+
"rescued": {"type": ["boolean", "null"], "description": "Phase 13 fingerprint rescue outcome. true=cache silently healed (selector overwritten, fail_count reset); false=rescue attempted but failed; null=rescue not attempted (default for adapter-direct events)."},
|
|
74
|
+
"fingerprint_from_selector": {"type": ["string", "null"], "description": "Phase 13: the cached selector that went stale. Null when rescued != true."},
|
|
75
|
+
"fingerprint_to_selector": {"type": ["string", "null"], "description": "Phase 13: the rescued selector that replaced the stale one. Null when rescued != true."}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Tool versions (autogenerated — do not edit; run scripts/regenerate-docs.sh)
|
|
2
|
+
|
|
3
|
+
| Tool | Version pin | Install hint | Cheatsheet |
|
|
4
|
+
|---|---|---|---|
|
|
5
|
+
| chrome-devtools-mcp | 0.x | npm i -g chrome-devtools-mcp (or run via 'npx chrome-devtools-mcp@latest' over stdio MCP) | [references/chrome-devtools-mcp-cheatsheet.md](../references/chrome-devtools-mcp-cheatsheet.md) |
|
|
6
|
+
| obscura | 0.x | download release from https://github.com/h4ckf0r0day/obscura/releases (no Chrome/Node required); keep obscura + obscura-worker side-by-side | [references/obscura-cheatsheet.md](../references/obscura-cheatsheet.md) |
|
|
7
|
+
| playwright-cli | 1.49.x | npm i -g playwright @playwright/test @playwright/cli && playwright install chromium | [references/playwright-cli-cheatsheet.md](../references/playwright-cli-cheatsheet.md) |
|
|
8
|
+
| playwright-lib | 1.59.x | npm i -g playwright @playwright/test && playwright install chromium | [references/playwright-lib-cheatsheet.md](../references/playwright-lib-cheatsheet.md) |
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# add-site — register a site profile under sites/<name>.json.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
IFS=$'\n\t'
|
|
5
|
+
umask 077
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
# shellcheck source=lib/common.sh
|
|
9
|
+
# shellcheck disable=SC1091
|
|
10
|
+
source "${SCRIPT_DIR}/lib/common.sh"
|
|
11
|
+
# shellcheck source=lib/site.sh
|
|
12
|
+
# shellcheck disable=SC1091
|
|
13
|
+
source "${SCRIPT_DIR}/lib/site.sh"
|
|
14
|
+
init_paths
|
|
15
|
+
|
|
16
|
+
name=""; url=""; viewport="1280x800"; user_agent=""; stealth="false"
|
|
17
|
+
default_session=""; default_tool=""; label=""
|
|
18
|
+
force=0; dry_run=0
|
|
19
|
+
|
|
20
|
+
usage() {
|
|
21
|
+
cat <<'USAGE'
|
|
22
|
+
Usage: add-site --name NAME --url URL [options]
|
|
23
|
+
|
|
24
|
+
--name NAME site name (required, used as filename)
|
|
25
|
+
--url URL site URL (must start with http:// or https://)
|
|
26
|
+
--viewport WxH viewport (default 1280x800)
|
|
27
|
+
--user-agent UA override user agent
|
|
28
|
+
--stealth set stealth flag (default false)
|
|
29
|
+
--default-session NAME default session for verbs that omit --session
|
|
30
|
+
--default-tool NAME default tool for verbs that omit --tool
|
|
31
|
+
--label TEXT human-readable description
|
|
32
|
+
--force overwrite an existing site
|
|
33
|
+
--dry-run print planned action; write nothing
|
|
34
|
+
-h, --help this message
|
|
35
|
+
USAGE
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
while [ $# -gt 0 ]; do
|
|
39
|
+
case "$1" in
|
|
40
|
+
--name) name="$2"; shift 2 ;;
|
|
41
|
+
--url) url="$2"; shift 2 ;;
|
|
42
|
+
--viewport) viewport="$2"; shift 2 ;;
|
|
43
|
+
--user-agent) user_agent="$2"; shift 2 ;;
|
|
44
|
+
--stealth) stealth="true"; shift ;;
|
|
45
|
+
--default-session) default_session="$2"; shift 2 ;;
|
|
46
|
+
--default-tool) default_tool="$2"; shift 2 ;;
|
|
47
|
+
--label) label="$2"; shift 2 ;;
|
|
48
|
+
--force) force=1; shift ;;
|
|
49
|
+
--dry-run) dry_run=1; shift ;;
|
|
50
|
+
-h|--help) usage; exit 0 ;;
|
|
51
|
+
*) die "${EXIT_USAGE_ERROR}" "unknown flag: $1" ;;
|
|
52
|
+
esac
|
|
53
|
+
done
|
|
54
|
+
|
|
55
|
+
[ -n "${name}" ] || { usage; die "${EXIT_USAGE_ERROR}" "--name is required"; }
|
|
56
|
+
[ -n "${url}" ] || { usage; die "${EXIT_USAGE_ERROR}" "--url is required"; }
|
|
57
|
+
case "${url}" in
|
|
58
|
+
http://*|https://*) ;;
|
|
59
|
+
*) die "${EXIT_USAGE_ERROR}" "url must start with http:// or https:// (got: ${url})" ;;
|
|
60
|
+
esac
|
|
61
|
+
[[ "${viewport}" =~ ^[0-9]+x[0-9]+$ ]] \
|
|
62
|
+
|| die "${EXIT_USAGE_ERROR}" "viewport must be WIDTHxHEIGHT (got: ${viewport})"
|
|
63
|
+
# Defense in depth: validate name fields the script is about to embed in paths.
|
|
64
|
+
assert_safe_name "${name}" "site-name"
|
|
65
|
+
[ -z "${default_session}" ] || assert_safe_name "${default_session}" "default-session"
|
|
66
|
+
vw="${viewport%x*}"; vh="${viewport#*x}"
|
|
67
|
+
|
|
68
|
+
started_at_ms="$(now_ms)"
|
|
69
|
+
|
|
70
|
+
if site_exists "${name}" && [ "${force}" -ne 1 ]; then
|
|
71
|
+
die "${EXIT_USAGE_ERROR}" "site already exists: ${name} (use --force to overwrite)"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
profile_json="$(jq -nc \
|
|
75
|
+
--arg n "${name}" \
|
|
76
|
+
--arg u "${url}" \
|
|
77
|
+
--argjson vw "${vw}" --argjson vh "${vh}" \
|
|
78
|
+
--arg ua "${user_agent}" \
|
|
79
|
+
--argjson stealth "${stealth}" \
|
|
80
|
+
--arg ds "${default_session}" \
|
|
81
|
+
--arg dt "${default_tool}" \
|
|
82
|
+
--arg lbl "${label}" \
|
|
83
|
+
'{
|
|
84
|
+
name: $n, url: $u,
|
|
85
|
+
viewport: {width: $vw, height: $vh},
|
|
86
|
+
user_agent: (if $ua == "" then null else $ua end),
|
|
87
|
+
stealth: $stealth,
|
|
88
|
+
default_session: (if $ds == "" then null else $ds end),
|
|
89
|
+
default_tool: (if $dt == "" then null else $dt end),
|
|
90
|
+
label: $lbl,
|
|
91
|
+
schema_version: 1
|
|
92
|
+
}')"
|
|
93
|
+
|
|
94
|
+
now_ts="$(now_iso)"
|
|
95
|
+
meta_json="$(jq -nc \
|
|
96
|
+
--arg n "${name}" \
|
|
97
|
+
--arg now "${now_ts}" \
|
|
98
|
+
'{name: $n, created_at: $now, last_used_at: $now}')"
|
|
99
|
+
|
|
100
|
+
if [ "${dry_run}" -eq 1 ]; then
|
|
101
|
+
ok "dry-run: would write ${SITES_DIR}/${name}.json"
|
|
102
|
+
duration_ms=$(( $(now_ms) - started_at_ms ))
|
|
103
|
+
summary_json verb=add-site tool=none why=dry-run status=ok would_run=true \
|
|
104
|
+
site="${name}" duration_ms="${duration_ms}"
|
|
105
|
+
exit "${EXIT_OK}"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
site_save "${name}" "${profile_json}" "${meta_json}"
|
|
109
|
+
ok "site added: ${name}"
|
|
110
|
+
|
|
111
|
+
duration_ms=$(( $(now_ms) - started_at_ms ))
|
|
112
|
+
summary_json verb=add-site tool=none why=write-profile status=ok \
|
|
113
|
+
site="${name}" duration_ms="${duration_ms}"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/browser-assert.sh — verify-style assertion verb (Phase 9 part 1-ii).
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# bash scripts/browser-assert.sh --selector CSS --text-contains TEXT \
|
|
6
|
+
# [--site NAME] [--tool NAME] [--dry-run]
|
|
7
|
+
#
|
|
8
|
+
# Thin wrapper: shells to `bash scripts/browser-extract.sh --selector CSS`
|
|
9
|
+
# (subprocess; routes through router + chrome-devtools-mcp by default);
|
|
10
|
+
# parses the extracted text; bash-side compares against --text-contains
|
|
11
|
+
# predicate. NO new tool_assert function on adapters — composition over ABI
|
|
12
|
+
# extension (per design doc §6 + plan-doc 2026-05-10-phase-09-part-1-ii §A1).
|
|
13
|
+
#
|
|
14
|
+
# Exit codes:
|
|
15
|
+
# 0 — assertion passed
|
|
16
|
+
# 13 — EXIT_ASSERTION_FAILED — predicate did not match
|
|
17
|
+
# 2 — EXIT_USAGE_ERROR (missing required flag)
|
|
18
|
+
# 1 — EXIT_GENERIC_ERROR (extract subprocess failed)
|
|
19
|
+
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
IFS=$'\n\t'
|
|
22
|
+
|
|
23
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
24
|
+
# shellcheck source=lib/common.sh
|
|
25
|
+
# shellcheck disable=SC1091
|
|
26
|
+
source "${SCRIPT_DIR}/lib/common.sh"
|
|
27
|
+
# shellcheck source=lib/output.sh
|
|
28
|
+
# shellcheck disable=SC1091
|
|
29
|
+
source "${SCRIPT_DIR}/lib/output.sh"
|
|
30
|
+
# shellcheck source=lib/verb_helpers.sh
|
|
31
|
+
# shellcheck disable=SC1091
|
|
32
|
+
source "${SCRIPT_DIR}/lib/verb_helpers.sh"
|
|
33
|
+
|
|
34
|
+
init_paths
|
|
35
|
+
|
|
36
|
+
SUMMARY_T0="$(now_ms)"; export SUMMARY_T0
|
|
37
|
+
|
|
38
|
+
parse_verb_globals "$@"
|
|
39
|
+
|
|
40
|
+
selector=""
|
|
41
|
+
text_contains=""
|
|
42
|
+
i=0
|
|
43
|
+
while [ "${i}" -lt "${#REMAINING_ARGV[@]}" ]; do
|
|
44
|
+
case "${REMAINING_ARGV[i]}" in
|
|
45
|
+
--selector)
|
|
46
|
+
selector="${REMAINING_ARGV[i+1]:-}"
|
|
47
|
+
[ -n "${selector}" ] || die "${EXIT_USAGE_ERROR}" "--selector requires a value"
|
|
48
|
+
i=$((i + 2))
|
|
49
|
+
;;
|
|
50
|
+
--text-contains)
|
|
51
|
+
text_contains="${REMAINING_ARGV[i+1]:-}"
|
|
52
|
+
[ -n "${text_contains}" ] || die "${EXIT_USAGE_ERROR}" "--text-contains requires a value"
|
|
53
|
+
i=$((i + 2))
|
|
54
|
+
;;
|
|
55
|
+
*)
|
|
56
|
+
i=$((i + 1))
|
|
57
|
+
;;
|
|
58
|
+
esac
|
|
59
|
+
done
|
|
60
|
+
|
|
61
|
+
[ -n "${selector}" ] || die "${EXIT_USAGE_ERROR}" "assert requires --selector CSS"
|
|
62
|
+
[ -n "${text_contains}" ] || die "${EXIT_USAGE_ERROR}" "assert requires --text-contains TEXT"
|
|
63
|
+
|
|
64
|
+
if [ "${ARG_DRY_RUN:-0}" = "1" ]; then
|
|
65
|
+
ok "dry-run: would assert selector=${selector} text_contains=${text_contains}"
|
|
66
|
+
emit_summary verb=assert tool=none why=dry-run status=ok \
|
|
67
|
+
selector="${selector}" text_contains="${text_contains}" dry_run=true
|
|
68
|
+
exit 0
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# Compose: shell to browser-extract.sh to get the selector's text. The extract
|
|
72
|
+
# verb's stdout is one event line + one summary line. We parse the event line.
|
|
73
|
+
set +e
|
|
74
|
+
extract_out="$(bash "${SCRIPT_DIR}/browser-extract.sh" --selector "${selector}" 2>&1)"
|
|
75
|
+
extract_rc=$?
|
|
76
|
+
set -e
|
|
77
|
+
|
|
78
|
+
if [ "${extract_rc}" -ne 0 ]; then
|
|
79
|
+
emit_summary verb=assert tool=extract why=composition status=error \
|
|
80
|
+
selector="${selector}" text_contains="${text_contains}" \
|
|
81
|
+
error="extract subprocess failed (rc=${extract_rc})"
|
|
82
|
+
exit "${EXIT_GENERIC_ERROR}"
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Find the extract event line; collect all matched text. The shipped extract
|
|
86
|
+
# event shape is {"event":"extract","selector":"...","matches":["Welcome","Hello"]}
|
|
87
|
+
# (matches[] is array of strings — see tests/fixtures/chrome-devtools-mcp/
|
|
88
|
+
# 05efe417...json). Fall through to .text for the playwright-cli shape.
|
|
89
|
+
got_text="$(
|
|
90
|
+
printf '%s\n' "${extract_out}" \
|
|
91
|
+
| jq -r -s '
|
|
92
|
+
map(select(.event == "extract")) | .[0] |
|
|
93
|
+
(.text // (.matches // []) | if type == "array" then join("\n") else . end)' 2>/dev/null \
|
|
94
|
+
|| printf ''
|
|
95
|
+
)"
|
|
96
|
+
|
|
97
|
+
if printf '%s' "${got_text}" | grep -qF -- "${text_contains}"; then
|
|
98
|
+
emit_summary verb=assert tool=extract why=composition status=ok \
|
|
99
|
+
selector="${selector}" text_contains="${text_contains}"
|
|
100
|
+
exit 0
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
emit_summary verb=assert tool=extract why=composition status=error \
|
|
104
|
+
selector="${selector}" text_contains="${text_contains}" \
|
|
105
|
+
expected="${text_contains}" got="${got_text}"
|
|
106
|
+
exit "${EXIT_ASSERTION_FAILED}"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/browser-audit.sh — run a Lighthouse / perf-trace audit.
|
|
3
|
+
# Usage: bash scripts/browser-audit.sh [--site NAME] [--tool NAME] [--dry-run]
|
|
4
|
+
# [--raw] [--lighthouse] [--perf-trace]
|
|
5
|
+
#
|
|
6
|
+
# Routes to chrome-devtools-mcp by default (post-1d router promotion — only
|
|
7
|
+
# adapter with `lighthouse_audit` and `performance_*` MCP tools per parent
|
|
8
|
+
# spec Appendix B). `--lighthouse` is the implicit default; `--perf-trace`
|
|
9
|
+
# can coexist.
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
IFS=$'\n\t'
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
# shellcheck source=lib/common.sh
|
|
16
|
+
# shellcheck disable=SC1091
|
|
17
|
+
source "${SCRIPT_DIR}/lib/common.sh"
|
|
18
|
+
# shellcheck source=lib/output.sh
|
|
19
|
+
# shellcheck disable=SC1091
|
|
20
|
+
source "${SCRIPT_DIR}/lib/output.sh"
|
|
21
|
+
# shellcheck source=lib/router.sh
|
|
22
|
+
# shellcheck disable=SC1091
|
|
23
|
+
source "${SCRIPT_DIR}/lib/router.sh"
|
|
24
|
+
# shellcheck source=lib/verb_helpers.sh
|
|
25
|
+
# shellcheck disable=SC1091
|
|
26
|
+
source "${SCRIPT_DIR}/lib/verb_helpers.sh"
|
|
27
|
+
|
|
28
|
+
init_paths
|
|
29
|
+
|
|
30
|
+
SUMMARY_T0="$(now_ms)"; export SUMMARY_T0
|
|
31
|
+
|
|
32
|
+
parse_verb_globals "$@"
|
|
33
|
+
|
|
34
|
+
resolve_session_storage_state
|
|
35
|
+
|
|
36
|
+
verb_argv=("${REMAINING_ARGV[@]}")
|
|
37
|
+
|
|
38
|
+
# Default to --lighthouse when neither flag is provided. Adapter still sees
|
|
39
|
+
# the flag in argv so router rules + capability filter can react.
|
|
40
|
+
if ! _has_flag --lighthouse "${verb_argv[@]}" && ! _has_flag --perf-trace "${verb_argv[@]}"; then
|
|
41
|
+
verb_argv+=(--lighthouse)
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
if [ "${ARG_DRY_RUN:-0}" = "1" ]; then
|
|
45
|
+
ok "dry-run: would run audit"
|
|
46
|
+
emit_summary verb=audit tool=none why=dry-run status=ok dry_run=true
|
|
47
|
+
exit 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
picked="$(pick_tool audit "${verb_argv[@]}")"
|
|
51
|
+
tool_name="${picked%%$'\t'*}"
|
|
52
|
+
why="${picked#*$'\t'}"
|
|
53
|
+
|
|
54
|
+
source_picked_adapter "${tool_name}"
|
|
55
|
+
|
|
56
|
+
set +e
|
|
57
|
+
adapter_out="$(invoke_with_retry audit "${verb_argv[@]}")"
|
|
58
|
+
adapter_rc=$?
|
|
59
|
+
set -e
|
|
60
|
+
|
|
61
|
+
[ -n "${adapter_out}" ] && printf '%s\n' "${adapter_out}"
|
|
62
|
+
|
|
63
|
+
if [ "${adapter_rc}" -eq 0 ]; then
|
|
64
|
+
emit_summary verb=audit tool="${tool_name}" why="${why}" status=ok
|
|
65
|
+
exit 0
|
|
66
|
+
fi
|
|
67
|
+
emit_summary verb=audit tool="${tool_name}" why="${why}" status=error
|
|
68
|
+
exit "${adapter_rc}"
|