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,206 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/browser-replay.sh — re-execute a prior capture's steps + diff against
|
|
3
|
+
# original. Phase 9 part 1-iv.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# bash scripts/browser-replay.sh <capture-id> [--strict] [--session NAME] [--dry-run]
|
|
7
|
+
#
|
|
8
|
+
# Loads ${CAPTURES_DIR}/<capture-id>/meta.json + steps.jsonl. Re-dispatches
|
|
9
|
+
# each step via flow_dispatch (composes 9-1-i + 9-1-ii). Writes a NEW capture
|
|
10
|
+
# with `replay_of: <capture-id>` + `replay_match: bool`. Emits per-step
|
|
11
|
+
# replay_diff event lines + one replay_diff_summary on stdout.
|
|
12
|
+
#
|
|
13
|
+
# Status mapping (per design doc §3 F5 + plan locked decision D3):
|
|
14
|
+
# All steps match → status:ok replay_match:true exit 0
|
|
15
|
+
# Mixed match/diverge → status:partial replay_match:false exit 0
|
|
16
|
+
# All steps diverged OR aborted → status:error exit non-zero
|
|
17
|
+
#
|
|
18
|
+
# --strict flag: ANY divergence → exit 13 (EXIT_ASSERTION_FAILED), matching
|
|
19
|
+
# the assert verb's exit code. Composes with CI scripts that grep for 13.
|
|
20
|
+
|
|
21
|
+
set -euo pipefail
|
|
22
|
+
IFS=$'\n\t'
|
|
23
|
+
|
|
24
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
25
|
+
SCRIPTS_DIR="${SCRIPT_DIR}"
|
|
26
|
+
export SCRIPTS_DIR
|
|
27
|
+
|
|
28
|
+
# shellcheck source=lib/common.sh
|
|
29
|
+
# shellcheck disable=SC1091
|
|
30
|
+
source "${SCRIPT_DIR}/lib/common.sh"
|
|
31
|
+
# shellcheck source=lib/output.sh
|
|
32
|
+
# shellcheck disable=SC1091
|
|
33
|
+
source "${SCRIPT_DIR}/lib/output.sh"
|
|
34
|
+
# shellcheck source=lib/capture.sh
|
|
35
|
+
# shellcheck disable=SC1091
|
|
36
|
+
source "${SCRIPT_DIR}/lib/capture.sh"
|
|
37
|
+
# shellcheck source=lib/flow.sh
|
|
38
|
+
# shellcheck disable=SC1091
|
|
39
|
+
source "${SCRIPT_DIR}/lib/flow.sh"
|
|
40
|
+
|
|
41
|
+
init_paths
|
|
42
|
+
|
|
43
|
+
SUMMARY_T0="$(now_ms)"; export SUMMARY_T0
|
|
44
|
+
|
|
45
|
+
capture_id="${1:-}"
|
|
46
|
+
[ -n "${capture_id}" ] || die "${EXIT_USAGE_ERROR}" "browser-replay: missing <capture-id>"
|
|
47
|
+
shift
|
|
48
|
+
|
|
49
|
+
strict=0
|
|
50
|
+
dry_run=0
|
|
51
|
+
session_override=""
|
|
52
|
+
while [ "$#" -gt 0 ]; do
|
|
53
|
+
case "$1" in
|
|
54
|
+
--strict) strict=1; shift ;;
|
|
55
|
+
--dry-run) dry_run=1; shift ;;
|
|
56
|
+
--session) session_override="$2"; shift 2 ;;
|
|
57
|
+
*) die "${EXIT_USAGE_ERROR}" "browser-replay: unknown flag '$1'" ;;
|
|
58
|
+
esac
|
|
59
|
+
done
|
|
60
|
+
|
|
61
|
+
old_dir="${CAPTURES_DIR}/${capture_id}"
|
|
62
|
+
old_meta="${old_dir}/meta.json"
|
|
63
|
+
old_steps="${old_dir}/steps.jsonl"
|
|
64
|
+
|
|
65
|
+
[ -d "${old_dir}" ] || die "${EXIT_USAGE_ERROR}" "browser-replay: no such capture '${capture_id}' at ${old_dir}"
|
|
66
|
+
[ -f "${old_meta}" ] || die "${EXIT_USAGE_ERROR}" "browser-replay: no meta.json at ${old_meta}"
|
|
67
|
+
|
|
68
|
+
# Reject non-flow captures — only `verb: flow` (or `verb: replay`) captures
|
|
69
|
+
# carry steps.jsonl. Per plan sub-scope: replay only consumes flow captures.
|
|
70
|
+
old_verb="$(jq -r '.verb' "${old_meta}")"
|
|
71
|
+
case "${old_verb}" in
|
|
72
|
+
flow|replay) ;;
|
|
73
|
+
*) die "${EXIT_USAGE_ERROR}" "browser-replay: capture ${capture_id} is not a flow capture (verb=${old_verb})" ;;
|
|
74
|
+
esac
|
|
75
|
+
|
|
76
|
+
[ -f "${old_steps}" ] || die "${EXIT_USAGE_ERROR}" "browser-replay: no steps.jsonl at ${old_steps}"
|
|
77
|
+
|
|
78
|
+
# Dry-run pre-pass: print planned steps; no execution.
|
|
79
|
+
if [ "${dry_run}" = "1" ]; then
|
|
80
|
+
ok "dry-run: would replay ${capture_id} (verb=${old_verb})"
|
|
81
|
+
printf '%s\n' "$(cat "${old_steps}")"
|
|
82
|
+
emit_summary verb=replay tool=none why=dry-run status=ok \
|
|
83
|
+
replay_of="${capture_id}" dry_run=true
|
|
84
|
+
exit 0
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# Real run: capture pipeline + per-step re-dispatch + per-step diff.
|
|
88
|
+
capture_start "replay"
|
|
89
|
+
new_meta="${CAPTURE_DIR}/meta.json"
|
|
90
|
+
tmp="${new_meta}.tmp.$$"
|
|
91
|
+
jq --arg ro "${capture_id}" '.replay_of = $ro' "${new_meta}" > "${tmp}"
|
|
92
|
+
chmod 600 "${tmp}"
|
|
93
|
+
mv "${tmp}" "${new_meta}"
|
|
94
|
+
|
|
95
|
+
new_steps="${CAPTURE_DIR}/steps.jsonl"
|
|
96
|
+
: > "${new_steps}"
|
|
97
|
+
chmod 600 "${new_steps}"
|
|
98
|
+
|
|
99
|
+
# Reset flow state in this shell (replay starts fresh; no inherited refs).
|
|
100
|
+
declare -gA FLOW_VARS=()
|
|
101
|
+
declare -gA FLOW_REFS=()
|
|
102
|
+
|
|
103
|
+
total_steps=0
|
|
104
|
+
matched_steps=0
|
|
105
|
+
diverged_steps=0
|
|
106
|
+
last_exit=0
|
|
107
|
+
|
|
108
|
+
# Iterate the OLD steps in order; re-dispatch each; emit diff event.
|
|
109
|
+
while IFS= read -r old_step; do
|
|
110
|
+
[ -z "${old_step}" ] && continue
|
|
111
|
+
total_steps=$((total_steps + 1))
|
|
112
|
+
|
|
113
|
+
# Re-build the step input for flow_dispatch from the OLD step's verb + args.
|
|
114
|
+
# (We don't carry over the OLD status / summary — we want the FRESH outcome.)
|
|
115
|
+
step_input="$(printf '%s' "${old_step}" | jq -c '{step_index, verb, args}')"
|
|
116
|
+
new_step_event="$(flow_dispatch "${step_input}")"
|
|
117
|
+
printf '%s\n' "${new_step_event}" >> "${new_steps}"
|
|
118
|
+
|
|
119
|
+
# Diff old vs new.
|
|
120
|
+
set +e
|
|
121
|
+
diff_event="$(flow_diff_steps "${old_step}" "${new_step_event}")"
|
|
122
|
+
diff_rc=$?
|
|
123
|
+
set -e
|
|
124
|
+
printf '%s\n' "${diff_event}"
|
|
125
|
+
|
|
126
|
+
if [ "${diff_rc}" = "0" ]; then
|
|
127
|
+
matched_steps=$((matched_steps + 1))
|
|
128
|
+
else
|
|
129
|
+
diverged_steps=$((diverged_steps + 1))
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# Track step-level errors for status mapping.
|
|
133
|
+
step_status="$(printf '%s' "${new_step_event}" | jq -r '.status')"
|
|
134
|
+
if [ "${step_status}" != "ok" ]; then
|
|
135
|
+
last_exit="$(printf '%s' "${new_step_event}" | jq -r '.exit_code')"
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# Phase 9-1-ii ref-harvest semantics also apply during replay.
|
|
139
|
+
refs_for_step="$(printf '%s' "${new_step_event}" | jq -c '.refs // null')"
|
|
140
|
+
if [ "${refs_for_step}" != "null" ]; then
|
|
141
|
+
FLOW_REFS=()
|
|
142
|
+
while IFS=$'\t' read -r ref_text ref_id; do
|
|
143
|
+
[ -z "${ref_text}" ] && continue
|
|
144
|
+
FLOW_REFS["${ref_text}"]="${ref_id}"
|
|
145
|
+
done <<< "$(printf '%s' "${refs_for_step}" | jq -r '.[] | "\(.text)\t\(.ref)"')"
|
|
146
|
+
fi
|
|
147
|
+
done < "${old_steps}"
|
|
148
|
+
|
|
149
|
+
# Determine replay_match + status.
|
|
150
|
+
if [ "${diverged_steps}" = "0" ] && [ "${total_steps}" -gt "0" ]; then
|
|
151
|
+
replay_match=true
|
|
152
|
+
flow_status="ok"
|
|
153
|
+
elif [ "${matched_steps}" = "0" ] && [ "${diverged_steps}" -gt "0" ]; then
|
|
154
|
+
replay_match=false
|
|
155
|
+
flow_status="error"
|
|
156
|
+
else
|
|
157
|
+
replay_match=false
|
|
158
|
+
flow_status="partial"
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
# Append replay-specific fields to meta.json.
|
|
162
|
+
tmp="${new_meta}.tmp.$$"
|
|
163
|
+
jq \
|
|
164
|
+
--arg ro "${capture_id}" \
|
|
165
|
+
--argjson rm "${replay_match}" \
|
|
166
|
+
--argjson tot "${total_steps}" \
|
|
167
|
+
--argjson mat "${matched_steps}" \
|
|
168
|
+
--argjson div "${diverged_steps}" \
|
|
169
|
+
'. + {replay_of: $ro, replay_match: $rm, total_steps: $tot, matched_steps: $mat, diverged_steps: $div}' \
|
|
170
|
+
"${new_meta}" > "${tmp}"
|
|
171
|
+
chmod 600 "${tmp}"
|
|
172
|
+
mv "${tmp}" "${new_meta}"
|
|
173
|
+
|
|
174
|
+
capture_finish "${flow_status}" true
|
|
175
|
+
|
|
176
|
+
# Emit replay_diff_summary line.
|
|
177
|
+
jq -nc \
|
|
178
|
+
--arg event "replay_diff_summary" \
|
|
179
|
+
--arg old_capture_id "${capture_id}" \
|
|
180
|
+
--arg new_capture_id "${CAPTURE_ID}" \
|
|
181
|
+
--argjson total_steps "${total_steps}" \
|
|
182
|
+
--argjson matched_steps "${matched_steps}" \
|
|
183
|
+
--argjson diverged_steps "${diverged_steps}" \
|
|
184
|
+
--argjson replay_match "${replay_match}" \
|
|
185
|
+
'{event: $event, old_capture_id: $old_capture_id, new_capture_id: $new_capture_id,
|
|
186
|
+
total_steps: $total_steps, matched_steps: $matched_steps, diverged_steps: $diverged_steps,
|
|
187
|
+
replay_match: $replay_match}'
|
|
188
|
+
|
|
189
|
+
# --strict: any divergence → exit 13 (EXIT_ASSERTION_FAILED).
|
|
190
|
+
if [ "${strict}" = "1" ] && [ "${diverged_steps}" -gt "0" ]; then
|
|
191
|
+
emit_summary verb=replay tool=none why=run status=error \
|
|
192
|
+
replay_of="${capture_id}" capture_id="${CAPTURE_ID}" \
|
|
193
|
+
replay_match="${replay_match}" \
|
|
194
|
+
total_steps="${total_steps}" matched_steps="${matched_steps}" diverged_steps="${diverged_steps}"
|
|
195
|
+
exit "${EXIT_ASSERTION_FAILED}"
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
emit_summary verb=replay tool=none why=run status="${flow_status}" \
|
|
199
|
+
replay_of="${capture_id}" capture_id="${CAPTURE_ID}" \
|
|
200
|
+
replay_match="${replay_match}" \
|
|
201
|
+
total_steps="${total_steps}" matched_steps="${matched_steps}" diverged_steps="${diverged_steps}"
|
|
202
|
+
|
|
203
|
+
if [ "${flow_status}" = "ok" ] || [ "${flow_status}" = "partial" ]; then
|
|
204
|
+
exit 0
|
|
205
|
+
fi
|
|
206
|
+
exit "${last_exit}"
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/browser-route.sh — register a network-route rule on the daemon.
|
|
3
|
+
# Usage: bash scripts/browser-route.sh [--site NAME] [--tool NAME]
|
|
4
|
+
# [--dry-run] [--raw]
|
|
5
|
+
# --pattern URL_PATTERN
|
|
6
|
+
# --action allow|block|fulfill
|
|
7
|
+
# [--status N] (fulfill)
|
|
8
|
+
# [--body STR | --body-stdin] (fulfill)
|
|
9
|
+
#
|
|
10
|
+
# Routes to chrome-devtools-mcp by default. Daemon-state-mutating: registers
|
|
11
|
+
# the rule in the daemon's routeRules array. Daemon-required.
|
|
12
|
+
#
|
|
13
|
+
# Phase 6 part 7-i: action ∈ {allow, block}.
|
|
14
|
+
# Phase 6 part 7-ii: action fulfill adds synthetic responses (--status / --body
|
|
15
|
+
# or --body-stdin). Body via stdin uses the same passthrough pattern as
|
|
16
|
+
# fill --secret-stdin (browser-fill.sh:87): the bash script forwards the
|
|
17
|
+
# --body-stdin flag and stdin inherits naturally to the bridge subprocess.
|
|
18
|
+
#
|
|
19
|
+
# Body binary-safety: any non-NUL byte sequence rides through. NUL itself
|
|
20
|
+
# can't ride bash variables or JSON strings — multipart bodies legitimately
|
|
21
|
+
# containing NUL would need a different transport (out of scope for 7-ii).
|
|
22
|
+
|
|
23
|
+
set -euo pipefail
|
|
24
|
+
IFS=$'\n\t'
|
|
25
|
+
|
|
26
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
27
|
+
# shellcheck source=lib/common.sh
|
|
28
|
+
# shellcheck disable=SC1091
|
|
29
|
+
source "${SCRIPT_DIR}/lib/common.sh"
|
|
30
|
+
# shellcheck source=lib/output.sh
|
|
31
|
+
# shellcheck disable=SC1091
|
|
32
|
+
source "${SCRIPT_DIR}/lib/output.sh"
|
|
33
|
+
# shellcheck source=lib/router.sh
|
|
34
|
+
# shellcheck disable=SC1091
|
|
35
|
+
source "${SCRIPT_DIR}/lib/router.sh"
|
|
36
|
+
# shellcheck source=lib/verb_helpers.sh
|
|
37
|
+
# shellcheck disable=SC1091
|
|
38
|
+
source "${SCRIPT_DIR}/lib/verb_helpers.sh"
|
|
39
|
+
|
|
40
|
+
init_paths
|
|
41
|
+
|
|
42
|
+
SUMMARY_T0="$(now_ms)"; export SUMMARY_T0
|
|
43
|
+
|
|
44
|
+
parse_verb_globals "$@"
|
|
45
|
+
|
|
46
|
+
resolve_session_storage_state
|
|
47
|
+
|
|
48
|
+
pattern="" action="" status_code="" body_inline="" use_body_stdin=0
|
|
49
|
+
have_body_inline=0
|
|
50
|
+
verb_argv=()
|
|
51
|
+
i=0
|
|
52
|
+
while [ "${i}" -lt "${#REMAINING_ARGV[@]}" ]; do
|
|
53
|
+
case "${REMAINING_ARGV[i]}" in
|
|
54
|
+
--pattern)
|
|
55
|
+
pattern="${REMAINING_ARGV[i+1]:-}"
|
|
56
|
+
[ -n "${pattern}" ] || die "${EXIT_USAGE_ERROR}" "--pattern requires a value"
|
|
57
|
+
verb_argv+=(--pattern "${pattern}")
|
|
58
|
+
i=$((i + 2))
|
|
59
|
+
;;
|
|
60
|
+
--action)
|
|
61
|
+
action="${REMAINING_ARGV[i+1]:-}"
|
|
62
|
+
[ -n "${action}" ] || die "${EXIT_USAGE_ERROR}" "--action requires a value"
|
|
63
|
+
case "${action}" in
|
|
64
|
+
allow|block|fulfill) ;;
|
|
65
|
+
*) die "${EXIT_USAGE_ERROR}" "--action must be one of {allow, block, fulfill} (got: ${action})" ;;
|
|
66
|
+
esac
|
|
67
|
+
verb_argv+=(--action "${action}")
|
|
68
|
+
i=$((i + 2))
|
|
69
|
+
;;
|
|
70
|
+
--status)
|
|
71
|
+
status_code="${REMAINING_ARGV[i+1]:-}"
|
|
72
|
+
[ -n "${status_code}" ] || die "${EXIT_USAGE_ERROR}" "--status requires a value"
|
|
73
|
+
verb_argv+=(--status "${status_code}")
|
|
74
|
+
i=$((i + 2))
|
|
75
|
+
;;
|
|
76
|
+
--body)
|
|
77
|
+
body_inline="${REMAINING_ARGV[i+1]:-}"
|
|
78
|
+
have_body_inline=1
|
|
79
|
+
verb_argv+=(--body "${body_inline}")
|
|
80
|
+
i=$((i + 2))
|
|
81
|
+
;;
|
|
82
|
+
--body-stdin)
|
|
83
|
+
use_body_stdin=1
|
|
84
|
+
verb_argv+=(--body-stdin)
|
|
85
|
+
i=$((i + 1))
|
|
86
|
+
;;
|
|
87
|
+
*)
|
|
88
|
+
verb_argv+=("${REMAINING_ARGV[i]}")
|
|
89
|
+
i=$((i + 1))
|
|
90
|
+
;;
|
|
91
|
+
esac
|
|
92
|
+
done
|
|
93
|
+
|
|
94
|
+
[ -n "${pattern}" ] || die "${EXIT_USAGE_ERROR}" "route requires --pattern URL_PATTERN"
|
|
95
|
+
[ -n "${action}" ] || die "${EXIT_USAGE_ERROR}" "route requires --action (allow|block|fulfill)"
|
|
96
|
+
|
|
97
|
+
# 7-ii validation: fulfill-only flags are rejected when paired with block/allow.
|
|
98
|
+
if [ "${action}" != "fulfill" ]; then
|
|
99
|
+
[ -z "${status_code}" ] || die "${EXIT_USAGE_ERROR}" "--status is only valid with --action fulfill"
|
|
100
|
+
[ "${have_body_inline}" = "0" ] || die "${EXIT_USAGE_ERROR}" "--body is only valid with --action fulfill"
|
|
101
|
+
[ "${use_body_stdin}" = "0" ] || die "${EXIT_USAGE_ERROR}" "--body-stdin is only valid with --action fulfill"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# 7-ii: fulfill requires status + (body | body-stdin); body / body-stdin mutex.
|
|
105
|
+
body_bytes=0
|
|
106
|
+
if [ "${action}" = "fulfill" ]; then
|
|
107
|
+
[ -n "${status_code}" ] || die "${EXIT_USAGE_ERROR}" "fulfill requires --status N (HTTP code in 100-599)"
|
|
108
|
+
case "${status_code}" in
|
|
109
|
+
''|*[!0-9]*) die "${EXIT_USAGE_ERROR}" "--status must be an integer (got: ${status_code})" ;;
|
|
110
|
+
esac
|
|
111
|
+
if [ "${status_code}" -lt 100 ] || [ "${status_code}" -gt 599 ]; then
|
|
112
|
+
die "${EXIT_USAGE_ERROR}" "--status must be in 100-599 (got: ${status_code})"
|
|
113
|
+
fi
|
|
114
|
+
if [ "${have_body_inline}" = "1" ] && [ "${use_body_stdin}" = "1" ]; then
|
|
115
|
+
die "${EXIT_USAGE_ERROR}" "--body and --body-stdin are mutually exclusive"
|
|
116
|
+
fi
|
|
117
|
+
if [ "${have_body_inline}" = "0" ] && [ "${use_body_stdin}" = "0" ]; then
|
|
118
|
+
die "${EXIT_USAGE_ERROR}" "fulfill requires --body STR or --body-stdin"
|
|
119
|
+
fi
|
|
120
|
+
if [ "${have_body_inline}" = "1" ]; then
|
|
121
|
+
body_bytes="${#body_inline}"
|
|
122
|
+
fi
|
|
123
|
+
# body_bytes for --body-stdin is a daemon-side concern; bash doesn't peek at
|
|
124
|
+
# stdin (would consume it before the bridge subprocess can read).
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
if [ "${ARG_DRY_RUN:-0}" = "1" ]; then
|
|
128
|
+
if [ "${action}" = "fulfill" ]; then
|
|
129
|
+
ok "dry-run: would register fulfill rule pattern=${pattern} status=${status_code}"
|
|
130
|
+
if [ "${use_body_stdin}" = "1" ]; then
|
|
131
|
+
# Don't consume stdin in dry-run — body_bytes unknown until daemon reads it.
|
|
132
|
+
emit_summary verb=route tool=none why=dry-run status=ok \
|
|
133
|
+
pattern="${pattern}" action="${action}" \
|
|
134
|
+
fulfill_status="${status_code}" body_source=stdin dry_run=true
|
|
135
|
+
else
|
|
136
|
+
emit_summary verb=route tool=none why=dry-run status=ok \
|
|
137
|
+
pattern="${pattern}" action="${action}" \
|
|
138
|
+
fulfill_status="${status_code}" body_bytes="${body_bytes}" dry_run=true
|
|
139
|
+
fi
|
|
140
|
+
else
|
|
141
|
+
ok "dry-run: would register route rule pattern=${pattern} action=${action}"
|
|
142
|
+
emit_summary verb=route tool=none why=dry-run status=ok \
|
|
143
|
+
pattern="${pattern}" action="${action}" dry_run=true
|
|
144
|
+
fi
|
|
145
|
+
exit 0
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
picked="$(pick_tool route "${verb_argv[@]}")"
|
|
149
|
+
tool_name="${picked%%$'\t'*}"
|
|
150
|
+
why="${picked#*$'\t'}"
|
|
151
|
+
|
|
152
|
+
source_picked_adapter "${tool_name}"
|
|
153
|
+
|
|
154
|
+
set +e
|
|
155
|
+
adapter_out="$(invoke_with_retry route "${verb_argv[@]}")"
|
|
156
|
+
adapter_rc=$?
|
|
157
|
+
set -e
|
|
158
|
+
|
|
159
|
+
[ -n "${adapter_out}" ] && printf '%s\n' "${adapter_out}"
|
|
160
|
+
|
|
161
|
+
if [ "${adapter_rc}" -eq 0 ]; then
|
|
162
|
+
if [ "${action}" = "fulfill" ]; then
|
|
163
|
+
emit_summary verb=route tool="${tool_name}" why="${why}" status=ok \
|
|
164
|
+
pattern="${pattern}" action="${action}" \
|
|
165
|
+
fulfill_status="${status_code}"
|
|
166
|
+
else
|
|
167
|
+
emit_summary verb=route tool="${tool_name}" why="${why}" status=ok \
|
|
168
|
+
pattern="${pattern}" action="${action}"
|
|
169
|
+
fi
|
|
170
|
+
exit 0
|
|
171
|
+
fi
|
|
172
|
+
emit_summary verb=route tool="${tool_name}" why="${why}" status=error \
|
|
173
|
+
pattern="${pattern}" action="${action}"
|
|
174
|
+
exit "${adapter_rc}"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/browser-select.sh — pick an option from a <select> element by ref or selector.
|
|
3
|
+
# Usage: bash scripts/browser-select.sh [--site NAME] [--tool NAME]
|
|
4
|
+
# [--dry-run] [--raw]
|
|
5
|
+
# (--ref eN | --selector CSS)
|
|
6
|
+
# (--value V | --label L | --index N)
|
|
7
|
+
#
|
|
8
|
+
# Routes to chrome-devtools-mcp by default (Phase 6 part 2). Stateful —
|
|
9
|
+
# requires a running daemon (refMap precondition; same shape as click/fill).
|
|
10
|
+
# --selector path enables Phase 11 cache dispatch (cache stores selectors,
|
|
11
|
+
# not snapshot-relative refs). Mirrors browser-click/fill/hover precedent.
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
IFS=$'\n\t'
|
|
15
|
+
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
# shellcheck source=lib/common.sh
|
|
18
|
+
# shellcheck disable=SC1091
|
|
19
|
+
source "${SCRIPT_DIR}/lib/common.sh"
|
|
20
|
+
# shellcheck source=lib/output.sh
|
|
21
|
+
# shellcheck disable=SC1091
|
|
22
|
+
source "${SCRIPT_DIR}/lib/output.sh"
|
|
23
|
+
# shellcheck source=lib/router.sh
|
|
24
|
+
# shellcheck disable=SC1091
|
|
25
|
+
source "${SCRIPT_DIR}/lib/router.sh"
|
|
26
|
+
# shellcheck source=lib/verb_helpers.sh
|
|
27
|
+
# shellcheck disable=SC1091
|
|
28
|
+
source "${SCRIPT_DIR}/lib/verb_helpers.sh"
|
|
29
|
+
|
|
30
|
+
init_paths
|
|
31
|
+
|
|
32
|
+
SUMMARY_T0="$(now_ms)"; export SUMMARY_T0
|
|
33
|
+
|
|
34
|
+
parse_verb_globals "$@"
|
|
35
|
+
|
|
36
|
+
resolve_session_storage_state
|
|
37
|
+
|
|
38
|
+
ref="" selector="" value="" label="" index=""
|
|
39
|
+
mode_count=0
|
|
40
|
+
verb_argv=()
|
|
41
|
+
i=0
|
|
42
|
+
while [ "${i}" -lt "${#REMAINING_ARGV[@]}" ]; do
|
|
43
|
+
case "${REMAINING_ARGV[i]}" in
|
|
44
|
+
--ref)
|
|
45
|
+
ref="${REMAINING_ARGV[i+1]:-}"
|
|
46
|
+
[ -n "${ref}" ] || die "${EXIT_USAGE_ERROR}" "--ref requires a value"
|
|
47
|
+
verb_argv+=(--ref "${ref}")
|
|
48
|
+
i=$((i + 2))
|
|
49
|
+
;;
|
|
50
|
+
--selector)
|
|
51
|
+
selector="${REMAINING_ARGV[i+1]:-}"
|
|
52
|
+
[ -n "${selector}" ] || die "${EXIT_USAGE_ERROR}" "--selector requires a value"
|
|
53
|
+
verb_argv+=(--selector "${selector}")
|
|
54
|
+
i=$((i + 2))
|
|
55
|
+
;;
|
|
56
|
+
--value)
|
|
57
|
+
value="${REMAINING_ARGV[i+1]:-}"
|
|
58
|
+
[ -n "${value}" ] || die "${EXIT_USAGE_ERROR}" "--value requires a value"
|
|
59
|
+
verb_argv+=(--value "${value}")
|
|
60
|
+
mode_count=$((mode_count + 1))
|
|
61
|
+
i=$((i + 2))
|
|
62
|
+
;;
|
|
63
|
+
--label)
|
|
64
|
+
label="${REMAINING_ARGV[i+1]:-}"
|
|
65
|
+
[ -n "${label}" ] || die "${EXIT_USAGE_ERROR}" "--label requires a value"
|
|
66
|
+
verb_argv+=(--label "${label}")
|
|
67
|
+
mode_count=$((mode_count + 1))
|
|
68
|
+
i=$((i + 2))
|
|
69
|
+
;;
|
|
70
|
+
--index)
|
|
71
|
+
index="${REMAINING_ARGV[i+1]:-}"
|
|
72
|
+
[ -n "${index}" ] || die "${EXIT_USAGE_ERROR}" "--index requires a value"
|
|
73
|
+
verb_argv+=(--index "${index}")
|
|
74
|
+
mode_count=$((mode_count + 1))
|
|
75
|
+
i=$((i + 2))
|
|
76
|
+
;;
|
|
77
|
+
*)
|
|
78
|
+
verb_argv+=("${REMAINING_ARGV[i]}")
|
|
79
|
+
i=$((i + 1))
|
|
80
|
+
;;
|
|
81
|
+
esac
|
|
82
|
+
done
|
|
83
|
+
|
|
84
|
+
if [ -n "${ref}" ] && [ -n "${selector}" ]; then
|
|
85
|
+
die "${EXIT_USAGE_ERROR}" "--ref and --selector are mutually exclusive"
|
|
86
|
+
fi
|
|
87
|
+
if [ -z "${ref}" ] && [ -z "${selector}" ]; then
|
|
88
|
+
die "${EXIT_USAGE_ERROR}" "select requires --ref eN or --selector CSS"
|
|
89
|
+
fi
|
|
90
|
+
if [ "${mode_count}" -eq 0 ]; then
|
|
91
|
+
die "${EXIT_USAGE_ERROR}" "select requires one of --value, --label, or --index"
|
|
92
|
+
fi
|
|
93
|
+
if [ "${mode_count}" -gt 1 ]; then
|
|
94
|
+
die "${EXIT_USAGE_ERROR}" "select: --value / --label / --index are mutually exclusive"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
if [ "${ARG_DRY_RUN:-0}" = "1" ]; then
|
|
98
|
+
ok "dry-run: would select ${ref:-${selector}} (value=${value}, label=${label}, index=${index})"
|
|
99
|
+
emit_summary verb=select tool=none why=dry-run status=ok ref="${ref}" selector="${selector}" \
|
|
100
|
+
value="${value}" label="${label}" index="${index}" dry_run=true
|
|
101
|
+
exit 0
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
picked="$(pick_tool select "${verb_argv[@]}")"
|
|
105
|
+
tool_name="${picked%%$'\t'*}"
|
|
106
|
+
why="${picked#*$'\t'}"
|
|
107
|
+
|
|
108
|
+
source_picked_adapter "${tool_name}"
|
|
109
|
+
|
|
110
|
+
set +e
|
|
111
|
+
adapter_out="$(invoke_with_retry select "${verb_argv[@]}")"
|
|
112
|
+
adapter_rc=$?
|
|
113
|
+
set -e
|
|
114
|
+
|
|
115
|
+
[ -n "${adapter_out}" ] && printf '%s\n' "${adapter_out}"
|
|
116
|
+
|
|
117
|
+
if [ "${adapter_rc}" -eq 0 ]; then
|
|
118
|
+
emit_summary verb=select tool="${tool_name}" why="${why}" status=ok ref="${ref}" selector="${selector}"
|
|
119
|
+
exit 0
|
|
120
|
+
fi
|
|
121
|
+
emit_summary verb=select tool="${tool_name}" why="${why}" status=error ref="${ref}" selector="${selector}"
|
|
122
|
+
exit "${adapter_rc}"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# show-session — emit session metadata only.
|
|
3
|
+
#
|
|
4
|
+
# CRITICAL SECURITY INVARIANT: this verb NEVER emits the storageState contents
|
|
5
|
+
# (cookies, tokens, localStorage). Only the meta sidecar (origin, captured_at,
|
|
6
|
+
# expires_in_hours, source_user_agent) is surfaced. The agent has no business
|
|
7
|
+
# seeing raw session material; the adapter applies it via storageState file
|
|
8
|
+
# path, never via JSON pass-through.
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
IFS=$'\n\t'
|
|
12
|
+
umask 077
|
|
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/session.sh
|
|
19
|
+
# shellcheck disable=SC1091
|
|
20
|
+
source "${SCRIPT_DIR}/lib/session.sh"
|
|
21
|
+
init_paths
|
|
22
|
+
|
|
23
|
+
name=""
|
|
24
|
+
usage() { printf 'Usage: show-session --as NAME\n'; }
|
|
25
|
+
while [ $# -gt 0 ]; do
|
|
26
|
+
case "$1" in
|
|
27
|
+
--as) name="$2"; shift 2 ;;
|
|
28
|
+
-h|--help) usage; exit 0 ;;
|
|
29
|
+
*) die "${EXIT_USAGE_ERROR}" "unknown flag: $1" ;;
|
|
30
|
+
esac
|
|
31
|
+
done
|
|
32
|
+
[ -n "${name}" ] || { usage; die "${EXIT_USAGE_ERROR}" "--as is required"; }
|
|
33
|
+
assert_safe_name "${name}" "session-name"
|
|
34
|
+
|
|
35
|
+
started_at_ms="$(now_ms)"
|
|
36
|
+
|
|
37
|
+
if ! session_exists "${name}"; then
|
|
38
|
+
die "${EXIT_SESSION_EXPIRED}" "session not found: ${name}"
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
meta="$(session_meta_load "${name}")"
|
|
42
|
+
ss_path="${SESSIONS_DIR}/${name}.json"
|
|
43
|
+
file_size="$(wc -c < "${ss_path}" 2>/dev/null | tr -d ' ' || printf '0')"
|
|
44
|
+
duration_ms=$(( $(now_ms) - started_at_ms ))
|
|
45
|
+
|
|
46
|
+
# Derive surface-level info from storageState WITHOUT echoing values: count
|
|
47
|
+
# cookies + count distinct origins. Never reveal cookie names, values, or
|
|
48
|
+
# domains beyond the count.
|
|
49
|
+
counts="$(jq -c '{cookie_count: (.cookies // [] | length),
|
|
50
|
+
origin_count: (.origins // [] | length)}' "${ss_path}" 2>/dev/null || printf '{}')"
|
|
51
|
+
|
|
52
|
+
jq -cn --arg n "${name}" --argjson m "${meta}" --argjson c "${counts}" \
|
|
53
|
+
--argjson s "${file_size}" --argjson d "${duration_ms}" \
|
|
54
|
+
'{verb: "show-session", tool: "none", why: "show", status: "ok",
|
|
55
|
+
session: $n, meta: $m,
|
|
56
|
+
storage_state: ($c + {file_size_bytes: $s}),
|
|
57
|
+
duration_ms: $d}'
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# show-site — emit one site's full profile JSON.
|
|
3
|
+
# (Phase 5 will mask credential-shaped fields if any are added; today the
|
|
4
|
+
# profile contains nothing secret, so this verb has no --reveal flag.)
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
IFS=$'\n\t'
|
|
7
|
+
umask 077
|
|
8
|
+
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
# shellcheck source=lib/common.sh
|
|
11
|
+
# shellcheck disable=SC1091
|
|
12
|
+
source "${SCRIPT_DIR}/lib/common.sh"
|
|
13
|
+
# shellcheck source=lib/site.sh
|
|
14
|
+
# shellcheck disable=SC1091
|
|
15
|
+
source "${SCRIPT_DIR}/lib/site.sh"
|
|
16
|
+
init_paths
|
|
17
|
+
|
|
18
|
+
name=""
|
|
19
|
+
usage() { printf 'Usage: show-site --name NAME\n'; }
|
|
20
|
+
while [ $# -gt 0 ]; do
|
|
21
|
+
case "$1" in
|
|
22
|
+
--name) name="$2"; shift 2 ;;
|
|
23
|
+
-h|--help) usage; exit 0 ;;
|
|
24
|
+
*) die "${EXIT_USAGE_ERROR}" "unknown flag: $1" ;;
|
|
25
|
+
esac
|
|
26
|
+
done
|
|
27
|
+
[ -n "${name}" ] || { usage; die "${EXIT_USAGE_ERROR}" "--name is required"; }
|
|
28
|
+
|
|
29
|
+
started_at_ms="$(now_ms)"
|
|
30
|
+
profile="$(site_load "${name}")"
|
|
31
|
+
meta="$(site_meta_load "${name}")"
|
|
32
|
+
duration_ms=$(( $(now_ms) - started_at_ms ))
|
|
33
|
+
|
|
34
|
+
jq -cn --arg n "${name}" --argjson p "${profile}" --argjson m "${meta}" \
|
|
35
|
+
--argjson d "${duration_ms}" \
|
|
36
|
+
'{verb: "show-site", tool: "none", why: "show", status: "ok",
|
|
37
|
+
site: $n, profile: $p, meta: $m, duration_ms: $d}'
|