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,390 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# scripts/lib/migrate.sh
|
|
3
|
+
# Phase 10 part 1-i — schema migration foundation.
|
|
4
|
+
# Per-schema versions in versions.json; per-schema atomic-swap with backup;
|
|
5
|
+
# manual rollback. Pure read/write API; no verb integration (10-1-ii) and no
|
|
6
|
+
# real migrators registered (10-1-iii).
|
|
7
|
+
#
|
|
8
|
+
# Per design doc 2026-05-11-phase-10-schema-migration-design.md:
|
|
9
|
+
# MIG1 — per-schema versions (not global)
|
|
10
|
+
# MIG2 — registry directory: scripts/lib/migrators/<schema>/v<from>_to_<to>.sh
|
|
11
|
+
# BROWSER_SKILL_MIGRATORS_DIR env override = test-only seam
|
|
12
|
+
# MIG3 — atomic write + backup; jq -e validation; refuse swap on bad output
|
|
13
|
+
# MIG5 — pure bash + jq; no Node
|
|
14
|
+
#
|
|
15
|
+
# Requires lib/common.sh sourced first (uses BROWSER_SKILL_HOME, file_mode,
|
|
16
|
+
# now_iso, assert_safe_name, die, EXIT_*).
|
|
17
|
+
|
|
18
|
+
[ -n "${BROWSER_SKILL_MIGRATE_LOADED:-}" ] && return 0
|
|
19
|
+
readonly BROWSER_SKILL_MIGRATE_LOADED=1
|
|
20
|
+
|
|
21
|
+
# --- Constants ---
|
|
22
|
+
|
|
23
|
+
# Known schemas; versions.json is initialized with all of these at v1 by
|
|
24
|
+
# migrate_init when no prior state exists. Adding a new schema = append here +
|
|
25
|
+
# add the corresponding entry to versions.json on next migrate_init call.
|
|
26
|
+
# Phase 12 part 2: `stats` schema registered for per-action telemetry log
|
|
27
|
+
# (memory/stats.jsonl + stats.db). v1 only; v2+ migrators land at
|
|
28
|
+
# scripts/lib/migrators/stats/v<from>_to_<to>.sh when shape evolves.
|
|
29
|
+
readonly _MIGRATE_KNOWN_SCHEMAS=(sites sessions credentials captures baselines memory config stats)
|
|
30
|
+
|
|
31
|
+
# Default backup retention.
|
|
32
|
+
readonly _MIGRATE_DEFAULT_KEEP=5
|
|
33
|
+
|
|
34
|
+
# --- Internal path helpers ---
|
|
35
|
+
|
|
36
|
+
_migrate_versions_path() {
|
|
37
|
+
printf '%s/versions.json' "${BROWSER_SKILL_HOME}"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_migrate_legacy_version_path() {
|
|
41
|
+
printf '%s/version' "${BROWSER_SKILL_HOME}"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_migrate_backups_dir() {
|
|
45
|
+
printf '%s/backups' "${BROWSER_SKILL_HOME}"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_migrate_schema_backups_dir() {
|
|
49
|
+
printf '%s/%s' "$(_migrate_backups_dir)" "$1"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_migrate_migrators_dir() {
|
|
53
|
+
if [ -n "${BROWSER_SKILL_MIGRATORS_DIR:-}" ]; then
|
|
54
|
+
printf '%s' "${BROWSER_SKILL_MIGRATORS_DIR}"
|
|
55
|
+
return 0
|
|
56
|
+
fi
|
|
57
|
+
local lib_dir
|
|
58
|
+
lib_dir="$(dirname "${BASH_SOURCE[0]}")"
|
|
59
|
+
printf '%s/migrators' "${lib_dir}"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# --- Init ---
|
|
63
|
+
|
|
64
|
+
# migrate_init
|
|
65
|
+
# Lazy-creates versions.json (mode 0600) + backups/ (mode 0700) at the
|
|
66
|
+
# canonical state-home paths. Idempotent. If a legacy `version` file exists
|
|
67
|
+
# (single integer), reads it and seeds the new versions.json with all known
|
|
68
|
+
# schemas at that version (typically 1).
|
|
69
|
+
migrate_init() {
|
|
70
|
+
local backups_dir versions_path
|
|
71
|
+
backups_dir="$(_migrate_backups_dir)"
|
|
72
|
+
versions_path="$(_migrate_versions_path)"
|
|
73
|
+
|
|
74
|
+
mkdir -p "${backups_dir}"
|
|
75
|
+
chmod 700 "${backups_dir}"
|
|
76
|
+
|
|
77
|
+
if [ ! -f "${versions_path}" ]; then
|
|
78
|
+
local default_v=1
|
|
79
|
+
local legacy="$(_migrate_legacy_version_path)"
|
|
80
|
+
if [ -f "${legacy}" ]; then
|
|
81
|
+
default_v="$(tr -d '[:space:]' < "${legacy}" 2>/dev/null || printf '1')"
|
|
82
|
+
[[ "${default_v}" =~ ^[0-9]+$ ]] || default_v=1
|
|
83
|
+
fi
|
|
84
|
+
local schemas_obj
|
|
85
|
+
schemas_obj="$(_migrate_build_default_schemas_object "${default_v}")"
|
|
86
|
+
_migrate_atomic_versions_write "$(jq -nc \
|
|
87
|
+
--argjson v "${default_v}" \
|
|
88
|
+
--argjson schemas "${schemas_obj}" \
|
|
89
|
+
--arg sk "v0.56.0" \
|
|
90
|
+
'{schema_version:1, schema_versions:$schemas, skill_version:$sk}')"
|
|
91
|
+
fi
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# Internal: build a JSON object {sites:N, sessions:N, ...} for the known schemas.
|
|
95
|
+
_migrate_build_default_schemas_object() {
|
|
96
|
+
local v="$1"
|
|
97
|
+
local args=()
|
|
98
|
+
local s
|
|
99
|
+
for s in "${_MIGRATE_KNOWN_SCHEMAS[@]}"; do
|
|
100
|
+
args+=(--argjson "${s}" "${v}")
|
|
101
|
+
done
|
|
102
|
+
local filter='. = {}'
|
|
103
|
+
for s in "${_MIGRATE_KNOWN_SCHEMAS[@]}"; do
|
|
104
|
+
filter="${filter} | .${s} = \$${s}"
|
|
105
|
+
done
|
|
106
|
+
jq -nc "${args[@]}" "${filter}"
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# --- Versions read/write ---
|
|
110
|
+
|
|
111
|
+
# migrate_get_version SCHEMA
|
|
112
|
+
# Echoes current schema_version for SCHEMA. Returns 1 (default) if SCHEMA is
|
|
113
|
+
# absent from versions.json or if versions.json itself is missing.
|
|
114
|
+
migrate_get_version() {
|
|
115
|
+
local schema="$1"
|
|
116
|
+
assert_safe_name "${schema}" "schema-name"
|
|
117
|
+
local versions_path
|
|
118
|
+
versions_path="$(_migrate_versions_path)"
|
|
119
|
+
if [ ! -f "${versions_path}" ]; then
|
|
120
|
+
printf '1\n'
|
|
121
|
+
return 0
|
|
122
|
+
fi
|
|
123
|
+
jq -r --arg s "${schema}" '.schema_versions[$s] // 1' "${versions_path}"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# migrate_set_version SCHEMA N
|
|
127
|
+
# Atomic-write SCHEMA → N in versions.json. Auto-init if versions.json missing.
|
|
128
|
+
migrate_set_version() {
|
|
129
|
+
local schema="$1" n="$2"
|
|
130
|
+
assert_safe_name "${schema}" "schema-name"
|
|
131
|
+
[[ "${n}" =~ ^[0-9]+$ ]] || die "${EXIT_USAGE_ERROR}" "migrate_set_version: N must be integer (got: ${n})"
|
|
132
|
+
migrate_init
|
|
133
|
+
local versions_path current updated
|
|
134
|
+
versions_path="$(_migrate_versions_path)"
|
|
135
|
+
current="$(cat "${versions_path}")"
|
|
136
|
+
updated="$(printf '%s' "${current}" | jq -c --arg s "${schema}" --argjson n "${n}" \
|
|
137
|
+
'.schema_versions[$s] = $n')"
|
|
138
|
+
_migrate_atomic_versions_write "${updated}"
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# Internal: atomic-write versions.json. Validates JSON before swap.
|
|
142
|
+
_migrate_atomic_versions_write() {
|
|
143
|
+
local json="$1"
|
|
144
|
+
if ! printf '%s' "${json}" | jq -e . >/dev/null 2>&1; then
|
|
145
|
+
die "${EXIT_GENERIC_ERROR}" "migrate: refused to write invalid versions.json"
|
|
146
|
+
fi
|
|
147
|
+
local path tmp
|
|
148
|
+
path="$(_migrate_versions_path)"
|
|
149
|
+
tmp="${path}.tmp.$$"
|
|
150
|
+
( umask 077; printf '%s\n' "${json}" | jq . > "${tmp}" )
|
|
151
|
+
chmod 600 "${tmp}"
|
|
152
|
+
mv "${tmp}" "${path}"
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# --- Registry ---
|
|
156
|
+
|
|
157
|
+
# Internal in-process registry table. Keys: "<schema>:<from>:<to>". Values: fn name.
|
|
158
|
+
declare -gA _MIGRATE_REGISTRY=()
|
|
159
|
+
|
|
160
|
+
# _migrate_register SCHEMA FROM TO FN
|
|
161
|
+
# Adds FN as the migrator for SCHEMA going from version FROM to version TO.
|
|
162
|
+
_migrate_register() {
|
|
163
|
+
local schema="$1" from="$2" to="$3" fn="$4"
|
|
164
|
+
_MIGRATE_REGISTRY["${schema}:${from}:${to}"]="${fn}"
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
# _migrate_load_registry
|
|
168
|
+
# Sources every scripts/lib/migrators/*/v*_to_v*.sh under the configured
|
|
169
|
+
# migrators dir. Each loaded file is expected to call _migrate_register itself
|
|
170
|
+
# OR to define a fn that follows the convention `migrate_<schema>_v<F>_to_v<T>`
|
|
171
|
+
# which we auto-register based on the file path.
|
|
172
|
+
_migrate_load_registry() {
|
|
173
|
+
local migrators_dir
|
|
174
|
+
migrators_dir="$(_migrate_migrators_dir)"
|
|
175
|
+
[ -d "${migrators_dir}" ] || return 0
|
|
176
|
+
local schema_dir schema migrator_file from to fn
|
|
177
|
+
for schema_dir in "${migrators_dir}"/*; do
|
|
178
|
+
[ -d "${schema_dir}" ] || continue
|
|
179
|
+
schema="$(basename "${schema_dir}")"
|
|
180
|
+
for migrator_file in "${schema_dir}"/v*_to_v*.sh; do
|
|
181
|
+
[ -f "${migrator_file}" ] || continue
|
|
182
|
+
# Parse "v<F>_to_v<T>.sh" out of the basename.
|
|
183
|
+
local base="$(basename "${migrator_file}" .sh)"
|
|
184
|
+
if [[ "${base}" =~ ^v([0-9]+)_to_v([0-9]+)$ ]]; then
|
|
185
|
+
from="${BASH_REMATCH[1]}"
|
|
186
|
+
to="${BASH_REMATCH[2]}"
|
|
187
|
+
# shellcheck disable=SC1090
|
|
188
|
+
source "${migrator_file}"
|
|
189
|
+
fn="migrate_${schema}_v${from}_to_v${to}"
|
|
190
|
+
_migrate_register "${schema}" "${from}" "${to}" "${fn}"
|
|
191
|
+
fi
|
|
192
|
+
done
|
|
193
|
+
done
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# --- Check ---
|
|
197
|
+
|
|
198
|
+
# migrate_check
|
|
199
|
+
# Walks the registry; for each (schema, from, to) entry where current schema
|
|
200
|
+
# version == from, emits a `_kind:migration_needed` event. Always exits 0.
|
|
201
|
+
migrate_check() {
|
|
202
|
+
local t0=$(now_ms)
|
|
203
|
+
migrate_init
|
|
204
|
+
_migrate_load_registry
|
|
205
|
+
|
|
206
|
+
local pending=0
|
|
207
|
+
local key
|
|
208
|
+
for key in "${!_MIGRATE_REGISTRY[@]}"; do
|
|
209
|
+
local schema="${key%%:*}"
|
|
210
|
+
local rest="${key#*:}"
|
|
211
|
+
local from="${rest%:*}"
|
|
212
|
+
local to="${rest#*:}"
|
|
213
|
+
local current
|
|
214
|
+
current="$(migrate_get_version "${schema}")"
|
|
215
|
+
if [ "${current}" = "${from}" ]; then
|
|
216
|
+
printf '%s\n' "$(jq -nc \
|
|
217
|
+
--arg schema "${schema}" --argjson from "${from}" --argjson to "${to}" \
|
|
218
|
+
'{_kind:"migration_needed", schema:$schema, from:$from, to:$to}')"
|
|
219
|
+
pending=$(( pending + 1 ))
|
|
220
|
+
fi
|
|
221
|
+
done
|
|
222
|
+
|
|
223
|
+
local duration_ms=$(( $(now_ms) - t0 ))
|
|
224
|
+
summary_json verb=migrate mode=check pending="${pending}" \
|
|
225
|
+
duration_ms="${duration_ms}" status=ok
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# --- Run ---
|
|
229
|
+
|
|
230
|
+
# migrate_run [SCHEMA]
|
|
231
|
+
# For each registered migrator (optionally filtered to SCHEMA), if the current
|
|
232
|
+
# version matches the migrator's `from`, run it against every file under
|
|
233
|
+
# ${BROWSER_SKILL_HOME}/<schema>/ that exists. Validates output via jq -e;
|
|
234
|
+
# refuses to atomic-swap on validation failure (version stays at `from`).
|
|
235
|
+
# Backs up each file to backups/<schema>/<basename>.bak.v<from> before swap.
|
|
236
|
+
migrate_run() {
|
|
237
|
+
local t0=$(now_ms)
|
|
238
|
+
local filter_schema="${1:-}"
|
|
239
|
+
migrate_init
|
|
240
|
+
_migrate_load_registry
|
|
241
|
+
|
|
242
|
+
local migrated=0 failed=0
|
|
243
|
+
local key
|
|
244
|
+
for key in "${!_MIGRATE_REGISTRY[@]}"; do
|
|
245
|
+
local schema="${key%%:*}"
|
|
246
|
+
[ -n "${filter_schema}" ] && [ "${schema}" != "${filter_schema}" ] && continue
|
|
247
|
+
local rest="${key#*:}"
|
|
248
|
+
local from="${rest%:*}"
|
|
249
|
+
local to="${rest#*:}"
|
|
250
|
+
local current
|
|
251
|
+
current="$(migrate_get_version "${schema}")"
|
|
252
|
+
[ "${current}" = "${from}" ] || continue
|
|
253
|
+
|
|
254
|
+
local fn="${_MIGRATE_REGISTRY[$key]}"
|
|
255
|
+
local schema_state_dir="${BROWSER_SKILL_HOME}/${schema}"
|
|
256
|
+
[ -d "${schema_state_dir}" ] || continue
|
|
257
|
+
|
|
258
|
+
# Run migrator against every file in the schema's state dir.
|
|
259
|
+
local file ok=1
|
|
260
|
+
while IFS= read -r -d '' file; do
|
|
261
|
+
_migrate_backup "${schema}" "${file}"
|
|
262
|
+
"${fn}" "${file}" || { ok=0; break; }
|
|
263
|
+
# Validate post-migration JSON.
|
|
264
|
+
if ! jq -e . "${file}" >/dev/null 2>&1; then
|
|
265
|
+
warn "migrate_run: ${schema} migrator produced invalid JSON for ${file}; rolling back this file"
|
|
266
|
+
local bk="$(_migrate_schema_backups_dir "${schema}")/$(basename "${file}").bak.v${from}"
|
|
267
|
+
if [ -f "${bk}" ]; then
|
|
268
|
+
cp "${bk}" "${file}"
|
|
269
|
+
fi
|
|
270
|
+
ok=0
|
|
271
|
+
break
|
|
272
|
+
fi
|
|
273
|
+
done < <(find "${schema_state_dir}" -maxdepth 4 -type f -name '*.json' -print0 2>/dev/null)
|
|
274
|
+
|
|
275
|
+
if [ "${ok}" = "1" ]; then
|
|
276
|
+
migrate_set_version "${schema}" "${to}"
|
|
277
|
+
migrated=$(( migrated + 1 ))
|
|
278
|
+
printf '%s\n' "$(jq -nc \
|
|
279
|
+
--arg schema "${schema}" --argjson from "${from}" --argjson to "${to}" \
|
|
280
|
+
'{_kind:"migration_applied", schema:$schema, from:$from, to:$to}')"
|
|
281
|
+
else
|
|
282
|
+
failed=$(( failed + 1 ))
|
|
283
|
+
fi
|
|
284
|
+
done
|
|
285
|
+
|
|
286
|
+
local duration_ms=$(( $(now_ms) - t0 ))
|
|
287
|
+
summary_json verb=migrate mode=run migrated="${migrated}" failed="${failed}" \
|
|
288
|
+
duration_ms="${duration_ms}" status=ok
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
# Internal: backup FILE under backups/<schema>/<basename>.bak.v<current_version>.
|
|
292
|
+
_migrate_backup() {
|
|
293
|
+
local schema="$1" file="$2"
|
|
294
|
+
local current bk_dir bk
|
|
295
|
+
current="$(migrate_get_version "${schema}")"
|
|
296
|
+
bk_dir="$(_migrate_schema_backups_dir "${schema}")"
|
|
297
|
+
mkdir -p "${bk_dir}"
|
|
298
|
+
chmod 700 "${bk_dir}"
|
|
299
|
+
bk="${bk_dir}/$(basename "${file}").bak.v${current}"
|
|
300
|
+
cp "${file}" "${bk}"
|
|
301
|
+
chmod 600 "${bk}"
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
# --- Rollback ---
|
|
305
|
+
|
|
306
|
+
# migrate_rollback SCHEMA
|
|
307
|
+
# Restores the latest backup for SCHEMA's state-dir files. Single-step only;
|
|
308
|
+
# multi-version chains require multiple invocations.
|
|
309
|
+
migrate_rollback() {
|
|
310
|
+
local schema="$1"
|
|
311
|
+
assert_safe_name "${schema}" "schema-name"
|
|
312
|
+
migrate_init
|
|
313
|
+
|
|
314
|
+
local current bk_dir
|
|
315
|
+
current="$(migrate_get_version "${schema}")"
|
|
316
|
+
bk_dir="$(_migrate_schema_backups_dir "${schema}")"
|
|
317
|
+
[ -d "${bk_dir}" ] || die "${EXIT_USAGE_ERROR}" "migrate_rollback: no backups for schema '${schema}'"
|
|
318
|
+
|
|
319
|
+
# Find latest backup version present.
|
|
320
|
+
local latest=0 file v
|
|
321
|
+
while IFS= read -r file; do
|
|
322
|
+
base="$(basename "${file}")"
|
|
323
|
+
v="${base##*.v}"
|
|
324
|
+
[[ "${v}" =~ ^[0-9]+$ ]] || continue
|
|
325
|
+
[ "${v}" -gt "${latest}" ] && latest="${v}"
|
|
326
|
+
done < <(find "${bk_dir}" -maxdepth 1 -type f -name '*.bak.v*' 2>/dev/null)
|
|
327
|
+
|
|
328
|
+
[ "${latest}" -gt 0 ] || die "${EXIT_USAGE_ERROR}" "migrate_rollback: no v* backups found for '${schema}'"
|
|
329
|
+
|
|
330
|
+
# Restore every file at that version.
|
|
331
|
+
local schema_state_dir="${BROWSER_SKILL_HOME}/${schema}"
|
|
332
|
+
while IFS= read -r file; do
|
|
333
|
+
local target_basename="${file##*/}"
|
|
334
|
+
target_basename="${target_basename%.bak.v*}"
|
|
335
|
+
cp "${file}" "${schema_state_dir}/${target_basename}"
|
|
336
|
+
chmod 600 "${schema_state_dir}/${target_basename}"
|
|
337
|
+
done < <(find "${bk_dir}" -maxdepth 1 -type f -name "*.bak.v${latest}" 2>/dev/null)
|
|
338
|
+
|
|
339
|
+
migrate_set_version "${schema}" "${latest}"
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
# --- Status ---
|
|
343
|
+
|
|
344
|
+
# migrate_status
|
|
345
|
+
# Echoes versions.json (after init).
|
|
346
|
+
migrate_status() {
|
|
347
|
+
migrate_init
|
|
348
|
+
cat "$(_migrate_versions_path)"
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
# --- Clean backups ---
|
|
352
|
+
|
|
353
|
+
# migrate_clean_backups [N]
|
|
354
|
+
# Discards backups beyond the newest N versions per schema. Default N=5.
|
|
355
|
+
#
|
|
356
|
+
# Implementation note: uses newline-separated find pipeline (rather than
|
|
357
|
+
# space-joined values in an associative array) because callers may run with
|
|
358
|
+
# IFS=$'\n\t' set globally — that breaks word-splitting on space inside
|
|
359
|
+
# `printf '%s\n' ${array_value}`. Newlines are robust under all IFS settings.
|
|
360
|
+
migrate_clean_backups() {
|
|
361
|
+
local keep="${1:-${_MIGRATE_DEFAULT_KEEP}}"
|
|
362
|
+
[[ "${keep}" =~ ^[0-9]+$ ]] || die "${EXIT_USAGE_ERROR}" "migrate_clean_backups: N must be integer (got: ${keep})"
|
|
363
|
+
local backups_dir
|
|
364
|
+
backups_dir="$(_migrate_backups_dir)"
|
|
365
|
+
[ -d "${backups_dir}" ] || return 0
|
|
366
|
+
|
|
367
|
+
local schema_dir
|
|
368
|
+
for schema_dir in "${backups_dir}"/*; do
|
|
369
|
+
[ -d "${schema_dir}" ] || continue
|
|
370
|
+
|
|
371
|
+
# Discover unique basename-without-version keys via find + sed + sort -u.
|
|
372
|
+
local basenames
|
|
373
|
+
basenames="$(find "${schema_dir}" -maxdepth 1 -type f -name '*.bak.v*' -exec basename {} \; 2>/dev/null \
|
|
374
|
+
| sed -E 's/\.bak\.v[0-9]+$//' | sort -u)"
|
|
375
|
+
[ -z "${basenames}" ] && continue
|
|
376
|
+
|
|
377
|
+
local basename_key versions v
|
|
378
|
+
while IFS= read -r basename_key; do
|
|
379
|
+
[ -z "${basename_key}" ] && continue
|
|
380
|
+
# Per-basename: list version numbers desc; drop top N; rm the rest.
|
|
381
|
+
versions="$(find "${schema_dir}" -maxdepth 1 -type f -name "${basename_key}.bak.v*" \
|
|
382
|
+
-exec basename {} \; 2>/dev/null \
|
|
383
|
+
| sed -E "s/^.*\.bak\.v//" | sort -rn | tail -n +$(( keep + 1 )))"
|
|
384
|
+
while IFS= read -r v; do
|
|
385
|
+
[ -z "${v}" ] && continue
|
|
386
|
+
rm -f "${schema_dir}/${basename_key}.bak.v${v}"
|
|
387
|
+
done <<<"${versions}"
|
|
388
|
+
done <<<"${basenames}"
|
|
389
|
+
done
|
|
390
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Phase 10 — Schema migrators
|
|
2
|
+
|
|
3
|
+
This directory is intentionally empty in 10-1-i (foundation only).
|
|
4
|
+
|
|
5
|
+
Real migrators land in 10-1-iii (no-op `v1_to_v2` for memory archetype JSONs) and case-by-case PRs thereafter.
|
|
6
|
+
|
|
7
|
+
## Convention
|
|
8
|
+
|
|
9
|
+
Each migrator is a bash file at `<schema>/v<from>_to_<to>.sh`. It must define a function named `migrate_<schema>_v<from>_to_v<to>` that takes a file path argument and mutates the file in-place (atomic-swap via tmp+mv).
|
|
10
|
+
|
|
11
|
+
Example (post-10-1-iii):
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
scripts/lib/migrators/
|
|
15
|
+
└── memory/
|
|
16
|
+
└── v1_to_v2.sh # defines: migrate_memory_v1_to_v2()
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The registry auto-loader (`_migrate_load_registry` in `lib/migrate.sh`) sources every `v*_to_v*.sh` file under this dir, parses the version pair from the filename, and registers the fn.
|
|
20
|
+
|
|
21
|
+
## Test-only seam
|
|
22
|
+
|
|
23
|
+
Tests override via `BROWSER_SKILL_MIGRATORS_DIR=/tmp/fixture-migrators` env var. Production code never sets this.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# scripts/lib/migrators/memory/v1_to_v2.sh
|
|
2
|
+
# Phase 10 part 1-iii: first real migrator. No-op identity for memory schema.
|
|
3
|
+
# Validates the registry + dispatch end-to-end without risking data corruption.
|
|
4
|
+
#
|
|
5
|
+
# Migrates every JSON file under ${BROWSER_SKILL_HOME}/memory/ (both
|
|
6
|
+
# patterns.json + archetype JSONs). The change is purely the schema_version
|
|
7
|
+
# bump from 1 to 2; data shape is unchanged.
|
|
8
|
+
#
|
|
9
|
+
# Future migrators (v2_to_v3 and beyond) ship per actual schema-shape changes.
|
|
10
|
+
|
|
11
|
+
migrate_memory_v1_to_v2() {
|
|
12
|
+
local file_path="$1"
|
|
13
|
+
jq '.schema_version = 2' "${file_path}" > "${file_path}.tmp"
|
|
14
|
+
mv "${file_path}.tmp" "${file_path}"
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# lib/migrators/recent_urls/
|
|
2
|
+
|
|
3
|
+
Schema migrator directory for the `recent_urls.jsonl` observation log
|
|
4
|
+
(Phase 11 v2 Pick A6, PR introducing schema_version:1).
|
|
5
|
+
|
|
6
|
+
**Currently empty.** The log ships at `schema_version:1` from inception; no
|
|
7
|
+
migrator is needed until the line shape changes. Future shape bumps land
|
|
8
|
+
a `v1_to_v2.sh` here following the precedent in `lib/migrators/memory/v1_to_v2.sh`
|
|
9
|
+
(PR #111).
|
|
10
|
+
|
|
11
|
+
The directory exists so the migrate-registry mechanism (`lib/migrate.sh`)
|
|
12
|
+
walks the `recent_urls` schema namespace alongside other schemas — even with
|
|
13
|
+
no migrators registered, the schema's `versions.json` slot is tracked.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Migrators — `stats` schema (Phase 12 part 2)
|
|
2
|
+
|
|
3
|
+
Per-action telemetry log under `${BROWSER_SKILL_HOME}/memory/`:
|
|
4
|
+
- `stats.jsonl` — append-only JSONL, one event per adapter call (source of truth)
|
|
5
|
+
- `stats.db` — lazy-built SQLite mirror, rebuilt from JSONL on `browser-stats rebuild`
|
|
6
|
+
|
|
7
|
+
Schema starts at v1. **No migrator needed until shape changes** (mirrors the
|
|
8
|
+
`recent_urls/` precedent — registry tolerates a schema with no `v*_to_v*.sh`
|
|
9
|
+
file as long as `versions.json` keeps it pinned at v1).
|
|
10
|
+
|
|
11
|
+
## When a v2 ships
|
|
12
|
+
|
|
13
|
+
1. Drop `v1_to_v2.sh` here defining `migrate_stats_v1_to_v2(file_path)`.
|
|
14
|
+
2. Bump the JSONL writer's `STATS_SCHEMA_VERSION` constant in
|
|
15
|
+
`scripts/lib/stats.sh`.
|
|
16
|
+
3. SQLite mirror rebuilds itself on next `browser-stats rebuild` from the
|
|
17
|
+
(now-migrated) JSONL — no SQL DDL migration needed because
|
|
18
|
+
`audit_events.raw_json` carries the source event verbatim.
|
|
19
|
+
|
|
20
|
+
## Event schema reference
|
|
21
|
+
|
|
22
|
+
Canonical event shape: [`references/stats-schema.json`](../../../../references/stats-schema.json).
|
|
23
|
+
Field naming follows OpenInference + OTel GenAI v1.40 conventions
|
|
24
|
+
(snake_case dot-name flattening).
|