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
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/browser-mcp.sh — launch the MCP server that exposes browser-skill
3
+ # verbs as MCP tools (stdio JSON-RPC NDJSON, protocol version 2024-11-05).
4
+ #
5
+ # Usage:
6
+ # bash scripts/browser-mcp.sh serve # default; reads JSON-RPC on stdin
7
+ # bash scripts/browser-mcp.sh --help # usage
8
+ #
9
+ # Wire format: NDJSON. Each request/response is a single JSON object terminated
10
+ # by '\n'. Matches the framing of our existing chrome-devtools-bridge.mjs
11
+ # CLIENT so the codebase converges on one wire shape.
12
+ #
13
+ # Tools exposed (Stage 1): browser_open, browser_snapshot. See
14
+ # scripts/lib/node/mcp-server.mjs::TOOLS for the live registry.
15
+ #
16
+ # Phase 14 (Proposal 2 from midscene research): lets agent-browser / midscene
17
+ # / Stagehand / browser-use reuse our cache + telemetry + secrets vault
18
+ # without re-implementing them.
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
+
28
+ BROWSER_SKILL_NODE_BIN="${BROWSER_SKILL_NODE_BIN:-node}"
29
+
30
+ case "${1:-serve}" in
31
+ serve)
32
+ if ! command -v "${BROWSER_SKILL_NODE_BIN}" >/dev/null 2>&1; then
33
+ die "${EXIT_TOOL_MISSING}" \
34
+ "node not on PATH (set BROWSER_SKILL_NODE_BIN to override)"
35
+ fi
36
+ exec "${BROWSER_SKILL_NODE_BIN}" "${SCRIPT_DIR}/lib/node/mcp-server.mjs"
37
+ ;;
38
+ --help|-h|help)
39
+ cat <<'USAGE'
40
+ browser-mcp — MCP server for browser-skill verbs
41
+
42
+ Usage:
43
+ bash scripts/browser-mcp.sh serve # start JSON-RPC server on stdio
44
+ bash scripts/browser-mcp.sh --help # this message
45
+
46
+ Tools exposed (Stage 1):
47
+ browser_open — open a URL
48
+ browser_snapshot — capture eN-indexed accessibility snapshot
49
+
50
+ Protocol: MCP 2024-11-05, NDJSON over stdio. Spawn this server from any
51
+ MCP-capable client (Claude Code, Continue, Cline, agent-browser, midscene,
52
+ etc.) to drive our cache + telemetry without re-implementing it.
53
+
54
+ Smoke test (manual):
55
+ printf '%s\n' \
56
+ '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
57
+ '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
58
+ | bash scripts/browser-mcp.sh serve
59
+ USAGE
60
+ exit 0
61
+ ;;
62
+ *)
63
+ die "${EXIT_USAGE_ERROR}" "unknown subcommand '${1}' — see --help"
64
+ ;;
65
+ esac
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/browser-migrate.sh
3
+ # Phase 10 part 1-ii — schema migration verb. Sub-mode dispatch over
4
+ # lib/migrate.sh's pure read/write API (10-1-i).
5
+ #
6
+ # Sub-modes:
7
+ # check read-only; reports schemas needing migration
8
+ # run [--yes] [--schema NAME] run all (or one schema); --yes bypasses typed-phrase
9
+ # rollback --schema NAME [--yes] single-step rollback for SCHEMA
10
+ # status echoes versions.json
11
+ # clean-backups [--keep N] [--yes] discard backups beyond newest N (default 5)
12
+ #
13
+ # Destructive sub-modes (run/rollback/clean-backups) require either --yes
14
+ # OR a TTY-interactive typed-phrase confirmation. They also acquire a
15
+ # PID-tracked lock at ${BROWSER_SKILL_HOME}/.migrate.lock to prevent
16
+ # concurrent migrations.
17
+
18
+ set -euo pipefail
19
+ IFS=$'\n\t'
20
+ umask 077
21
+
22
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23
+ SCRIPTS_DIR="${SCRIPT_DIR}"
24
+ export SCRIPTS_DIR
25
+
26
+ # shellcheck source=lib/common.sh
27
+ # shellcheck disable=SC1091
28
+ source "${SCRIPT_DIR}/lib/common.sh"
29
+ # shellcheck source=lib/output.sh
30
+ # shellcheck disable=SC1091
31
+ source "${SCRIPT_DIR}/lib/output.sh"
32
+ # shellcheck source=lib/migrate.sh
33
+ # shellcheck disable=SC1091
34
+ source "${SCRIPT_DIR}/lib/migrate.sh"
35
+
36
+ init_paths
37
+
38
+ SUMMARY_T0="$(now_ms)"
39
+
40
+ usage() {
41
+ cat <<'USAGE'
42
+ Usage:
43
+ browser-migrate check read-only; reports schemas needing migration
44
+ browser-migrate status echoes versions.json
45
+ browser-migrate run [--yes] [--schema NAME] run all (or one schema)
46
+ browser-migrate rollback --schema NAME [--yes] single-step rollback for SCHEMA
47
+ browser-migrate clean-backups [--keep N] [--yes] discard backups beyond newest N
48
+
49
+ Destructive sub-modes (run, rollback, clean-backups) require --yes OR
50
+ TTY-interactive typed-phrase confirmation.
51
+ USAGE
52
+ }
53
+
54
+ # --- Lock helpers ---
55
+
56
+ _acquire_migrate_lock() {
57
+ local lock_path="${BROWSER_SKILL_HOME}/.migrate.lock"
58
+ if [ -f "${lock_path}" ]; then
59
+ local owner_pid
60
+ owner_pid="$(jq -r '.pid // empty' "${lock_path}" 2>/dev/null || true)"
61
+ if [ -n "${owner_pid}" ] && [ "${owner_pid}" != "null" ] && kill -0 "${owner_pid}" 2>/dev/null; then
62
+ die "${EXIT_USAGE_ERROR}" "browser-migrate: another migration in progress (pid ${owner_pid}); wait or kill it"
63
+ fi
64
+ warn "browser-migrate: stale lock from pid ${owner_pid:-?} cleared"
65
+ rm -f "${lock_path}"
66
+ fi
67
+ printf '%s' "$(jq -nc --arg pid "$$" --arg now "$(now_iso)" \
68
+ '{pid:($pid|tonumber), acquired_at:$now}')" > "${lock_path}"
69
+ chmod 600 "${lock_path}"
70
+ trap '_release_migrate_lock' EXIT
71
+ }
72
+
73
+ _release_migrate_lock() {
74
+ rm -f "${BROWSER_SKILL_HOME}/.migrate.lock"
75
+ }
76
+
77
+ # --- Confirmation ---
78
+
79
+ # _confirm_phrase EXPECTED
80
+ # When --yes flag set (ARG_YES=1), skip. Otherwise require interactive TTY +
81
+ # read one line from stdin matching EXPECTED. Refuse on mismatch.
82
+ _confirm_phrase() {
83
+ local expected="$1"
84
+ if [ "${arg_yes:-0}" = "1" ]; then return 0; fi
85
+ if [ ! -t 0 ]; then
86
+ die "${EXIT_TTY_REQUIRED}" "browser-migrate: --yes flag required when no TTY (interactive confirmation needs TTY)"
87
+ fi
88
+ printf "type '%s' to confirm:\n" "${expected}" >&2
89
+ local line
90
+ IFS= read -r line
91
+ [ "${line}" = "${expected}" ] || die "${EXIT_USAGE_ERROR}" "browser-migrate: confirmation mismatch; aborted"
92
+ }
93
+
94
+ # --- Sub-mode dispatch ---
95
+
96
+ sub_mode="${1:-}"
97
+ [ -n "${sub_mode}" ] || { usage >&2; die "${EXIT_USAGE_ERROR}" "browser-migrate: missing sub-mode (check / run / rollback / status / clean-backups)"; }
98
+ shift
99
+
100
+ case "${sub_mode}" in
101
+ -h|--help) usage; exit 0 ;;
102
+ check|status|run|rollback|clean-backups) ;;
103
+ *) die "${EXIT_USAGE_ERROR}" "browser-migrate: unknown sub-mode '${sub_mode}' (expected: check / run / rollback / status / clean-backups)" ;;
104
+ esac
105
+
106
+ # --- check ---
107
+
108
+ if [ "${sub_mode}" = "check" ]; then
109
+ while [ "$#" -gt 0 ]; do
110
+ case "$1" in
111
+ -h|--help) usage; exit 0 ;;
112
+ *) die "${EXIT_USAGE_ERROR}" "browser-migrate check: unknown flag '$1'" ;;
113
+ esac
114
+ done
115
+ migrate_check
116
+ exit 0
117
+ fi
118
+
119
+ # --- status ---
120
+
121
+ if [ "${sub_mode}" = "status" ]; then
122
+ while [ "$#" -gt 0 ]; do
123
+ case "$1" in
124
+ -h|--help) usage; exit 0 ;;
125
+ *) die "${EXIT_USAGE_ERROR}" "browser-migrate status: unknown flag '$1'" ;;
126
+ esac
127
+ done
128
+ migrate_status
129
+ exit 0
130
+ fi
131
+
132
+ # --- run ---
133
+
134
+ if [ "${sub_mode}" = "run" ]; then
135
+ arg_yes=0 arg_schema=""
136
+ while [ "$#" -gt 0 ]; do
137
+ case "$1" in
138
+ --yes) arg_yes=1; shift ;;
139
+ --schema) arg_schema="$2"; shift 2 ;;
140
+ -h|--help) usage; exit 0 ;;
141
+ *) die "${EXIT_USAGE_ERROR}" "browser-migrate run: unknown flag '$1'" ;;
142
+ esac
143
+ done
144
+ _confirm_phrase "migrate now"
145
+ _acquire_migrate_lock
146
+ if [ -n "${arg_schema}" ]; then
147
+ migrate_run "${arg_schema}"
148
+ else
149
+ migrate_run
150
+ fi
151
+ exit 0
152
+ fi
153
+
154
+ # --- rollback ---
155
+
156
+ if [ "${sub_mode}" = "rollback" ]; then
157
+ arg_yes=0 arg_schema=""
158
+ while [ "$#" -gt 0 ]; do
159
+ case "$1" in
160
+ --yes) arg_yes=1; shift ;;
161
+ --schema) arg_schema="$2"; shift 2 ;;
162
+ -h|--help) usage; exit 0 ;;
163
+ *) die "${EXIT_USAGE_ERROR}" "browser-migrate rollback: unknown flag '$1'" ;;
164
+ esac
165
+ done
166
+ [ -n "${arg_schema}" ] || die "${EXIT_USAGE_ERROR}" "browser-migrate rollback: --schema NAME required"
167
+ _confirm_phrase "migrate rollback ${arg_schema}"
168
+ _acquire_migrate_lock
169
+ migrate_rollback "${arg_schema}"
170
+ duration_ms=$(( $(now_ms) - SUMMARY_T0 ))
171
+ summary_json verb=migrate mode=rollback schema="${arg_schema}" \
172
+ duration_ms="${duration_ms}" status=ok
173
+ exit 0
174
+ fi
175
+
176
+ # --- clean-backups ---
177
+
178
+ if [ "${sub_mode}" = "clean-backups" ]; then
179
+ arg_yes=0 arg_keep="5"
180
+ while [ "$#" -gt 0 ]; do
181
+ case "$1" in
182
+ --yes) arg_yes=1; shift ;;
183
+ --keep) arg_keep="$2"; shift 2 ;;
184
+ -h|--help) usage; exit 0 ;;
185
+ *) die "${EXIT_USAGE_ERROR}" "browser-migrate clean-backups: unknown flag '$1'" ;;
186
+ esac
187
+ done
188
+ _confirm_phrase "clean backups"
189
+ _acquire_migrate_lock
190
+ migrate_clean_backups "${arg_keep}"
191
+ duration_ms=$(( $(now_ms) - SUMMARY_T0 ))
192
+ summary_json verb=migrate mode=clean-backups keep="${arg_keep}" \
193
+ duration_ms="${duration_ms}" status=ok
194
+ exit 0
195
+ fi
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/browser-open.sh — open a URL via the routed adapter.
3
+ # Usage: bash scripts/browser-open.sh [--site NAME] [--tool NAME] [--dry-run]
4
+ # [--raw] --url <URL>
5
+ # Emits one streaming JSON line per adapter event (if any), then a single
6
+ # JSON summary line. See docs/superpowers/specs/2026-04-27-browser-automation-skill-design.md §5.4
7
+ # and docs/superpowers/specs/2026-05-01-token-efficient-adapter-output-design.md §3.
8
+
9
+ set -euo pipefail
10
+ IFS=$'\n\t'
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ # shellcheck source=lib/common.sh
14
+ # shellcheck disable=SC1091
15
+ source "${SCRIPT_DIR}/lib/common.sh"
16
+ # shellcheck source=lib/output.sh
17
+ # shellcheck disable=SC1091
18
+ source "${SCRIPT_DIR}/lib/output.sh"
19
+ # shellcheck source=lib/router.sh
20
+ # shellcheck disable=SC1091
21
+ source "${SCRIPT_DIR}/lib/router.sh"
22
+ # shellcheck source=lib/verb_helpers.sh
23
+ # shellcheck disable=SC1091
24
+ source "${SCRIPT_DIR}/lib/verb_helpers.sh"
25
+ # shellcheck source=lib/site.sh
26
+ # shellcheck disable=SC1091
27
+ source "${SCRIPT_DIR}/lib/site.sh"
28
+ # shellcheck source=lib/memory.sh
29
+ # shellcheck disable=SC1091
30
+ source "${SCRIPT_DIR}/lib/memory.sh"
31
+ # shellcheck source=lib/stats.sh
32
+ # shellcheck disable=SC1091
33
+ source "${SCRIPT_DIR}/lib/stats.sh"
34
+
35
+ init_paths
36
+
37
+ SUMMARY_T0="$(now_ms)"; export SUMMARY_T0
38
+
39
+ parse_verb_globals "$@"
40
+
41
+ # Resolve site/session → BROWSER_SKILL_STORAGE_STATE (no-op if neither set).
42
+ # Router's rule_session_required reads the env var to prefer playwright-lib.
43
+ resolve_session_storage_state
44
+
45
+ url=""
46
+ verb_argv=()
47
+ i=0
48
+ while [ "${i}" -lt "${#REMAINING_ARGV[@]}" ]; do
49
+ case "${REMAINING_ARGV[i]}" in
50
+ --url)
51
+ url="${REMAINING_ARGV[i+1]:-}"
52
+ [ -n "${url}" ] || die "${EXIT_USAGE_ERROR}" "--url requires a value"
53
+ verb_argv+=(--url "${url}")
54
+ i=$((i + 2))
55
+ ;;
56
+ *)
57
+ verb_argv+=("${REMAINING_ARGV[i]}")
58
+ i=$((i + 1))
59
+ ;;
60
+ esac
61
+ done
62
+
63
+ [ -n "${url}" ] || die "${EXIT_USAGE_ERROR}" "--url <URL> is required"
64
+
65
+ if [ "${ARG_DRY_RUN:-0}" = "1" ]; then
66
+ ok "dry-run: would open ${url}"
67
+ emit_summary verb=open tool=none why=dry-run status=ok url="${url}" dry_run=true
68
+ exit 0
69
+ fi
70
+
71
+ picked="$(pick_tool open "${verb_argv[@]}")"
72
+ tool_name="${picked%%$'\t'*}"
73
+ why="${picked#*$'\t'}"
74
+
75
+ source_picked_adapter "${tool_name}"
76
+
77
+ stats_t0="$(now_ms)"
78
+ set +e
79
+ adapter_out="$(invoke_with_retry open "${verb_argv[@]}")"
80
+ adapter_rc=$?
81
+ set -e
82
+
83
+ # Phase 12 part 1 + Phase 14 (Bundle #2): per-action telemetry with auto-derived
84
+ # post-condition. The adapter's stdout for `open` typically echoes the navigated
85
+ # URL (e.g. `{"event":"navigate","url":"…","status":200}`); using that as
86
+ # OBSERVED lets us detect redirect-to-login / app-router rewrites that current
87
+ # self-reported success would silently miss (the cheatsheet's killer
88
+ # "oblivious_success" signal). Caller-set env wins via `:=` parameter expansion.
89
+ if [ "${adapter_rc}" -eq 0 ] && [ -n "${adapter_out}" ]; then
90
+ : "${BROWSER_STATS_EXPECT_TYPE:=url}"
91
+ : "${BROWSER_STATS_EXPECT_MATCH:=include}"
92
+ : "${BROWSER_STATS_EXPECT_VALUE:=${url}}"
93
+ : "${BROWSER_STATS_OBSERVED:=${adapter_out}}"
94
+ else
95
+ : "${BROWSER_STATS_OBSERVED:=${url}}"
96
+ fi
97
+ export BROWSER_STATS_EXPECT_TYPE BROWSER_STATS_EXPECT_MATCH BROWSER_STATS_EXPECT_VALUE BROWSER_STATS_OBSERVED
98
+
99
+ stats_run_adapter_emit \
100
+ "open" "${tool_name}" "${stats_t0}" "${adapter_rc}" "${adapter_out}" "" \
101
+ -- "${verb_argv[@]}" || true
102
+
103
+ [ -n "${adapter_out}" ] && printf '%s\n' "${adapter_out}"
104
+
105
+ if [ "${adapter_rc}" -eq 0 ]; then
106
+ # Pick A6: passive observation. Best-effort tee to recent_urls.jsonl when a
107
+ # site is in scope. --site flag (ARG_SITE from parse_verb_globals) wins;
108
+ # falls back to current_get (sticky current site). Site-less navigations
109
+ # skip the tee — recent_urls is site-scoped for `propose --from-recent`.
110
+ # Failure emits warn: in the helper and continues; never taints exit code.
111
+ _open_site="${ARG_SITE:-$(current_get 2>/dev/null || true)}"
112
+ if [ -n "${_open_site}" ]; then
113
+ memory_record_recent_url "${_open_site}" "${url}" "open" 2>/dev/null || true
114
+ # Phase 14 B1: opportunistic URL-pattern clustering. Now that recent_urls
115
+ # has a fresh entry, ask browser-do propose to (a) cluster recent URLs by
116
+ # templated path, (b) emit patterns that meet threshold ≥3, (c) write
117
+ # any new patterns to memory/<site>/patterns.json. Compounds zero-token
118
+ # clicks for repeat actions — the `browser-do` cache engine has been
119
+ # shipped since Phase 11 but was idle because nothing triggered propose.
120
+ # Gated by env so existing flows that don't want this overhead can opt out:
121
+ # BROWSER_SKILL_OPEN_PROPOSE=0 → skip (default: 1 / on)
122
+ # Cost: ~50ms (jq + node url-pattern-cluster.mjs). Best-effort: failure
123
+ # emits its own warn in browser-do; never taints open's exit code.
124
+ if [ "${BROWSER_SKILL_OPEN_PROPOSE:-1}" = "1" ]; then
125
+ bash "${SCRIPT_DIR}/browser-do.sh" propose \
126
+ --site "${_open_site}" --from-recent --auto-record --threshold 3 \
127
+ >/dev/null 2>&1 || true
128
+ fi
129
+ fi
130
+ emit_summary verb=open tool="${tool_name}" why="${why}" status=ok url="${url}"
131
+ exit 0
132
+ fi
133
+ emit_summary verb=open tool="${tool_name}" why="${why}" status=error url="${url}"
134
+ exit "${adapter_rc}"
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/browser-press.sh — keyboard press via the picked adapter.
3
+ # Usage: bash scripts/browser-press.sh [--site NAME] [--tool NAME] [--dry-run]
4
+ # [--raw] --key KEY
5
+ #
6
+ # Routes to chrome-devtools-mcp by default (Phase 6 part 1 introduces press
7
+ # via cdt-mcp; playwright-cli/lib don't declare press today). KEY is a
8
+ # Playwright/CDP key name like "Enter", "Tab", "Escape", "ArrowDown",
9
+ # "Cmd+S", "PageDown".
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
+ key=""
37
+ verb_argv=()
38
+ i=0
39
+ while [ "${i}" -lt "${#REMAINING_ARGV[@]}" ]; do
40
+ case "${REMAINING_ARGV[i]}" in
41
+ --key)
42
+ key="${REMAINING_ARGV[i+1]:-}"
43
+ [ -n "${key}" ] || die "${EXIT_USAGE_ERROR}" "--key requires a value"
44
+ verb_argv+=(--key "${key}")
45
+ i=$((i + 2))
46
+ ;;
47
+ *)
48
+ verb_argv+=("${REMAINING_ARGV[i]}")
49
+ i=$((i + 1))
50
+ ;;
51
+ esac
52
+ done
53
+
54
+ [ -n "${key}" ] || die "${EXIT_USAGE_ERROR}" "press requires --key KEY (e.g. --key Enter)"
55
+
56
+ if [ "${ARG_DRY_RUN:-0}" = "1" ]; then
57
+ ok "dry-run: would press ${key}"
58
+ emit_summary verb=press tool=none why=dry-run status=ok key="${key}" dry_run=true
59
+ exit 0
60
+ fi
61
+
62
+ picked="$(pick_tool press "${verb_argv[@]}")"
63
+ tool_name="${picked%%$'\t'*}"
64
+ why="${picked#*$'\t'}"
65
+
66
+ source_picked_adapter "${tool_name}"
67
+
68
+ set +e
69
+ adapter_out="$(invoke_with_retry press "${verb_argv[@]}")"
70
+ adapter_rc=$?
71
+ set -e
72
+
73
+ [ -n "${adapter_out}" ] && printf '%s\n' "${adapter_out}"
74
+
75
+ if [ "${adapter_rc}" -eq 0 ]; then
76
+ emit_summary verb=press tool="${tool_name}" why="${why}" status=ok key="${key}"
77
+ exit 0
78
+ fi
79
+ emit_summary verb=press tool="${tool_name}" why="${why}" status=error key="${key}"
80
+ exit "${adapter_rc}"
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bash
2
+ # remove-session — typed-name confirmation, then delete storageState + meta.
3
+ #
4
+ # Does NOT clear site.default_session pointers that reference this session.
5
+ # Dangling pointers surface clearly: `open --site X` with a deleted default
6
+ # session exits EXIT_SESSION_EXPIRED via resolve_session_storage_state with
7
+ # a self-healing "run login" hint. Cascade-clearing is Phase 5 territory.
8
+
9
+ set -euo pipefail
10
+ IFS=$'\n\t'
11
+ umask 077
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ # shellcheck source=lib/common.sh
15
+ # shellcheck disable=SC1091
16
+ source "${SCRIPT_DIR}/lib/common.sh"
17
+ # shellcheck source=lib/session.sh
18
+ # shellcheck disable=SC1091
19
+ source "${SCRIPT_DIR}/lib/session.sh"
20
+ init_paths
21
+
22
+ name=""; yes=0; dry_run=0
23
+ usage() {
24
+ cat <<'USAGE'
25
+ Usage: remove-session --as NAME [--yes-i-know] [--dry-run]
26
+
27
+ --as NAME session to remove (required)
28
+ --yes-i-know skip the typed-name confirmation
29
+ --dry-run print planned action; remove nothing
30
+ USAGE
31
+ }
32
+ while [ $# -gt 0 ]; do
33
+ case "$1" in
34
+ --as) name="$2"; shift 2 ;;
35
+ --yes-i-know) yes=1; shift ;;
36
+ --dry-run) dry_run=1; shift ;;
37
+ -h|--help) usage; exit 0 ;;
38
+ *) die "${EXIT_USAGE_ERROR}" "unknown flag: $1" ;;
39
+ esac
40
+ done
41
+ [ -n "${name}" ] || { usage; die "${EXIT_USAGE_ERROR}" "--as is required"; }
42
+ assert_safe_name "${name}" "session-name"
43
+
44
+ started_at_ms="$(now_ms)"
45
+
46
+ if ! session_exists "${name}"; then
47
+ die "${EXIT_SESSION_EXPIRED}" "session not found: ${name}"
48
+ fi
49
+
50
+ if [ "${dry_run}" -eq 1 ]; then
51
+ ok "dry-run: would remove session ${name}"
52
+ duration_ms=$(( $(now_ms) - started_at_ms ))
53
+ summary_json verb=remove-session tool=none why=dry-run status=ok would_run=true \
54
+ session="${name}" duration_ms="${duration_ms}"
55
+ exit "${EXIT_OK}"
56
+ fi
57
+
58
+ if [ "${yes}" -ne 1 ]; then
59
+ printf 'Type the session name (%s) to confirm removal: ' "${name}" >&2
60
+ answer=""
61
+ IFS= read -r answer || true
62
+ if [ "${answer}" != "${name}" ]; then
63
+ die "${EXIT_USAGE_ERROR}" "removal aborted (confirmation mismatch)"
64
+ fi
65
+ fi
66
+
67
+ session_delete "${name}"
68
+ ok "session removed: ${name}"
69
+
70
+ duration_ms=$(( $(now_ms) - started_at_ms ))
71
+ summary_json verb=remove-session tool=none why=delete status=ok \
72
+ session="${name}" duration_ms="${duration_ms}"
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env bash
2
+ # remove-site — typed-name confirmation, then delete profile + meta.
3
+ # Cascade: if CURRENT_FILE points at this site, lib/site.sh::site_delete
4
+ # clears it. (Sessions / credentials linked to this site are NOT removed in
5
+ # Phase 2 — that lands with the credential lifecycle in Phase 5.)
6
+ set -euo pipefail
7
+ IFS=$'\n\t'
8
+ umask 077
9
+
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ # shellcheck source=lib/common.sh
12
+ # shellcheck disable=SC1091
13
+ source "${SCRIPT_DIR}/lib/common.sh"
14
+ # shellcheck source=lib/site.sh
15
+ # shellcheck disable=SC1091
16
+ source "${SCRIPT_DIR}/lib/site.sh"
17
+ init_paths
18
+
19
+ name=""; yes=0; dry_run=0
20
+ usage() {
21
+ cat <<'USAGE'
22
+ Usage: remove-site --name NAME [--yes-i-know] [--dry-run]
23
+
24
+ --name NAME site to remove (required)
25
+ --yes-i-know skip the typed-name confirmation
26
+ --dry-run print planned action; remove nothing
27
+ USAGE
28
+ }
29
+ while [ $# -gt 0 ]; do
30
+ case "$1" in
31
+ --name) name="$2"; shift 2 ;;
32
+ --yes-i-know) yes=1; shift ;;
33
+ --dry-run) dry_run=1; shift ;;
34
+ -h|--help) usage; exit 0 ;;
35
+ *) die "${EXIT_USAGE_ERROR}" "unknown flag: $1" ;;
36
+ esac
37
+ done
38
+ [ -n "${name}" ] || { usage; die "${EXIT_USAGE_ERROR}" "--name is required"; }
39
+
40
+ started_at_ms="$(now_ms)"
41
+
42
+ if ! site_exists "${name}"; then
43
+ die "${EXIT_SITE_NOT_FOUND}" "site not found: ${name}"
44
+ fi
45
+
46
+ if [ "${dry_run}" -eq 1 ]; then
47
+ ok "dry-run: would remove site ${name}"
48
+ duration_ms=$(( $(now_ms) - started_at_ms ))
49
+ summary_json verb=remove-site tool=none why=dry-run status=ok would_run=true \
50
+ site="${name}" duration_ms="${duration_ms}"
51
+ exit "${EXIT_OK}"
52
+ fi
53
+
54
+ if [ "${yes}" -ne 1 ]; then
55
+ printf 'Type the site name (%s) to confirm removal: ' "${name}" >&2
56
+ answer=""
57
+ IFS= read -r answer || true
58
+ if [ "${answer}" != "${name}" ]; then
59
+ die "${EXIT_USAGE_ERROR}" "removal aborted (confirmation mismatch)"
60
+ fi
61
+ fi
62
+
63
+ site_delete "${name}"
64
+ ok "site removed: ${name}"
65
+
66
+ duration_ms=$(( $(now_ms) - started_at_ms ))
67
+ summary_json verb=remove-site tool=none why=delete status=ok \
68
+ site="${name}" duration_ms="${duration_ms}"