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