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,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}"
|