@deeeed/metamask-harness 0.2.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 (158) hide show
  1. package/CHANGELOG.md +161 -0
  2. package/README.md +140 -0
  3. package/bin/mm-harness +99 -0
  4. package/docs/CHEATSHEET.md +61 -0
  5. package/docs/CLI-SPEC.md +915 -0
  6. package/docs/MENTAL-MODEL.md +295 -0
  7. package/docs/architecture.md +367 -0
  8. package/docs/extension-runtime-commands.md +60 -0
  9. package/docs/harness-cli.md +43 -0
  10. package/docs/live-adapter-contract.md +188 -0
  11. package/docs/package-boundaries.md +47 -0
  12. package/docs/perps-flow-catalog.md +235 -0
  13. package/docs/recipe-libraries.md +95 -0
  14. package/docs/runtime-file-conventions.md +36 -0
  15. package/library/actions/core/perps/_controller.mjs +727 -0
  16. package/library/actions/core/perps/assert_orders.mjs +53 -0
  17. package/library/actions/core/perps/assert_positions.mjs +52 -0
  18. package/library/actions/core/perps/close_orders.mjs +97 -0
  19. package/library/actions/core/perps/close_positions.mjs +118 -0
  20. package/library/actions/core/perps/ensure_orders.mjs +40 -0
  21. package/library/actions/core/perps/ensure_positions.mjs +37 -0
  22. package/library/actions/core/perps/place_order.mjs +201 -0
  23. package/library/actions/core/perps/read_account.mjs +30 -0
  24. package/library/actions/core/perps/read_orders.mjs +27 -0
  25. package/library/actions/core/perps/read_positions.mjs +27 -0
  26. package/library/actions/core/perps/start_state.mjs +92 -0
  27. package/library/actions/core/perps/teardown_state.mjs +86 -0
  28. package/library/actions/extension/perps/assert_orders.mjs +11 -0
  29. package/library/actions/extension/perps/assert_positions.mjs +11 -0
  30. package/library/actions/extension/perps/close_orders.mjs +8 -0
  31. package/library/actions/extension/perps/close_positions.mjs +8 -0
  32. package/library/actions/extension/perps/ensure_orders.mjs +4 -0
  33. package/library/actions/extension/perps/ensure_positions.mjs +4 -0
  34. package/library/actions/extension/perps/perps.mjs +730 -0
  35. package/library/actions/extension/perps/place_order.mjs +7 -0
  36. package/library/actions/extension/perps/read_orders.mjs +4 -0
  37. package/library/actions/extension/perps/read_positions.mjs +3 -0
  38. package/library/actions/extension/platform/cdp.mjs +541 -0
  39. package/library/actions/extension/ui/navigate.mjs +44 -0
  40. package/library/actions/extension/wallet/ensure_unlocked.mjs +36 -0
  41. package/library/actions/extension/wallet/read_state.mjs +27 -0
  42. package/library/actions/extension/wallet/select_account.mjs +48 -0
  43. package/library/actions/extension/wallet/setup.mjs +35 -0
  44. package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +185 -0
  45. package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +1662 -0
  46. package/library/actions/mobile/bridge-runtime/cdp-bridge.cjs +686 -0
  47. package/library/actions/mobile/bridge-runtime/lib/cdp-eval.cjs +110 -0
  48. package/library/actions/mobile/bridge-runtime/lib/config.cjs +39 -0
  49. package/library/actions/mobile/bridge-runtime/lib/issue-capture.cjs +446 -0
  50. package/library/actions/mobile/bridge-runtime/lib/target-discovery.cjs +204 -0
  51. package/library/actions/mobile/bridge-runtime/lib/ws-client.cjs +108 -0
  52. package/library/actions/mobile/bridge-runtime/setup-wallet.sh +442 -0
  53. package/library/actions/mobile/perps/assert_orders.mjs +11 -0
  54. package/library/actions/mobile/perps/assert_positions.mjs +11 -0
  55. package/library/actions/mobile/perps/close_orders.mjs +8 -0
  56. package/library/actions/mobile/perps/close_positions.mjs +8 -0
  57. package/library/actions/mobile/perps/ensure_orders.mjs +4 -0
  58. package/library/actions/mobile/perps/ensure_positions.mjs +4 -0
  59. package/library/actions/mobile/perps/perps.mjs +709 -0
  60. package/library/actions/mobile/perps/place_order.mjs +7 -0
  61. package/library/actions/mobile/perps/read_orders.mjs +4 -0
  62. package/library/actions/mobile/perps/read_positions.mjs +3 -0
  63. package/library/actions/mobile/platform/bridge.mjs +283 -0
  64. package/library/actions/mobile/ui/navigate.mjs +38 -0
  65. package/library/actions/mobile/wallet/ensure_unlocked.mjs +107 -0
  66. package/library/actions/mobile/wallet/home.mjs +35 -0
  67. package/library/actions/mobile/wallet/read_state.mjs +40 -0
  68. package/library/actions/mobile/wallet/select_account.mjs +48 -0
  69. package/library/actions/mobile/wallet/setup.mjs +220 -0
  70. package/library/flows/perps.flows.json +64 -0
  71. package/library/library.json +7 -0
  72. package/library/manifests/core.action-manifest.json +1282 -0
  73. package/library/manifests/extension.action-manifest.json +1749 -0
  74. package/library/manifests/mobile.action-manifest.json +1753 -0
  75. package/library/recipes/action-validation.extension.recipe.json +417 -0
  76. package/library/recipes/action-validation.mobile.recipe.json +422 -0
  77. package/library/recipes/order-lifecycle.core.recipe.json +78 -0
  78. package/library/recipes/perps-lifecycle.recipe.json +194 -0
  79. package/library/recipes/read-markets.core.recipe.json +38 -0
  80. package/library/recipes/smoke.extension.recipe.json +31 -0
  81. package/library/recipes/smoke.mobile.recipe.json +31 -0
  82. package/library/recipes/trading-lifecycle.core.recipe.json +76 -0
  83. package/orchestration/compat-overlays/README.md +19 -0
  84. package/orchestration/compat-overlays/mobile/README.md +13 -0
  85. package/orchestration/compat-overlays/mobile/rn81-message-event-source.patch +42 -0
  86. package/orchestration/core/cleanup.sh +37 -0
  87. package/orchestration/core/inject.sh +154 -0
  88. package/orchestration/doctor.mjs +72 -0
  89. package/orchestration/extension/cleanup.mjs +60 -0
  90. package/orchestration/extension/console-tail.mjs +228 -0
  91. package/orchestration/extension/ensure-browser.sh +416 -0
  92. package/orchestration/extension/ensure-ready.ts +185 -0
  93. package/orchestration/extension/extension-id.ts +107 -0
  94. package/orchestration/extension/inject.mjs +266 -0
  95. package/orchestration/extension/launch-browser.cjs +216 -0
  96. package/orchestration/extension/launch.sh +175 -0
  97. package/orchestration/extension/live.sh +320 -0
  98. package/orchestration/extension/pin-remote-flags.cjs +45 -0
  99. package/orchestration/extension/readiness.mjs +414 -0
  100. package/orchestration/extension/refresh-build.sh +190 -0
  101. package/orchestration/extension/runtime-decision.ts +445 -0
  102. package/orchestration/extension/runtime.ts +407 -0
  103. package/orchestration/extension/seed-fixture.sh +177 -0
  104. package/orchestration/extension/sidepanel-toggle.sh +291 -0
  105. package/orchestration/extension/snapshot-dist.sh +84 -0
  106. package/orchestration/extension/start-watch.sh +339 -0
  107. package/orchestration/extension/wallet-fixture-state.cjs +1086 -0
  108. package/orchestration/lib/activate-repo-node.sh +144 -0
  109. package/orchestration/lib/cli-color.mjs +84 -0
  110. package/orchestration/lib/cli-commands.mjs +243 -0
  111. package/orchestration/lib/cli-home.mjs +354 -0
  112. package/orchestration/lib/cli-ux.sh +252 -0
  113. package/orchestration/lib/cli-version.mjs +123 -0
  114. package/orchestration/lib/ensure-runner-deps.sh +56 -0
  115. package/orchestration/lib/harness-path.sh +55 -0
  116. package/orchestration/lib/hash-helpers.sh +44 -0
  117. package/orchestration/lib/json-field.sh +23 -0
  118. package/orchestration/lib/log-tui.mjs +304 -0
  119. package/orchestration/lib/open-debug.mjs +317 -0
  120. package/orchestration/lib/path-defaults.json +4 -0
  121. package/orchestration/lib/progress.mjs +107 -0
  122. package/orchestration/lib/recipe-paths.mjs +26 -0
  123. package/orchestration/lib/resolve-farmslot-ports.sh +144 -0
  124. package/orchestration/manifest.json +358 -0
  125. package/orchestration/mobile/cleanup.sh +192 -0
  126. package/orchestration/mobile/deps-markers.ts +21 -0
  127. package/orchestration/mobile/inject.sh +681 -0
  128. package/orchestration/mobile/launch.sh +137 -0
  129. package/orchestration/mobile/live.sh +125 -0
  130. package/orchestration/mobile/runtime-decision.ts +292 -0
  131. package/orchestration/porcelain/metamask-recipe +99 -0
  132. package/orchestration/porcelain/mm-recipe +1591 -0
  133. package/orchestration/porcelain/mme-recipe +1181 -0
  134. package/package.json +59 -0
  135. package/runner/extension/verify.sh +511 -0
  136. package/runner/mobile/verify.sh +501 -0
  137. package/runner/src/adapters.ts +601 -0
  138. package/runner/src/cli.ts +1820 -0
  139. package/runner/src/commands/debug.ts +44 -0
  140. package/runner/src/commands/fixtures.ts +99 -0
  141. package/runner/src/commands/launch.ts +397 -0
  142. package/runner/src/commands/logs.ts +60 -0
  143. package/runner/src/commands/shared.ts +138 -0
  144. package/runner/src/completions-cache.ts +86 -0
  145. package/runner/src/doctor.ts +203 -0
  146. package/runner/src/harness.ts +516 -0
  147. package/runner/src/heal-bounds.ts +179 -0
  148. package/runner/src/index.ts +6 -0
  149. package/runner/src/live-adapter-contract.ts +274 -0
  150. package/runner/src/manifest.ts +47 -0
  151. package/runner/src/mm-harness-cli.ts +488 -0
  152. package/runner/src/paths.ts +198 -0
  153. package/runner/src/recording-target.ts +147 -0
  154. package/runner/src/run-recording.ts +329 -0
  155. package/runner/src/runner.ts +108 -0
  156. package/runner/src/types.ts +57 -0
  157. package/scripts/completions.sh +125 -0
  158. package/scripts/install-completions.sh +62 -0
@@ -0,0 +1,144 @@
1
+ # activate-repo-node.sh — pin the target repo's declared Node for non-interactive spawns
2
+ #
3
+ # Sourced by orchestration scripts that run yarn/webpack in tmux, nohup, or other
4
+ # environments whose PATH may not match an interactive dev shell (.tool-versions and
5
+ # asdf shims are not applied automatically).
6
+ #
7
+ # On success sets:
8
+ # REPO_NODE_VERSION — e.g. 24.13.0 (no leading v)
9
+ # REPO_NODE_BIN — absolute path to the node binary
10
+ # REPO_NODE_BIN_DIR — dirname of REPO_NODE_BIN
11
+ # REPO_NODE_PATH_PREFIX — "${REPO_NODE_BIN_DIR}:" when resolved, else empty
12
+ #
13
+ # Also prepends REPO_NODE_BIN_DIR to PATH in the current shell when resolved.
14
+ #
15
+ # Usage (after cd to target repo, or pass directory as $1):
16
+ # . /path/to/activate-repo-node.sh
17
+ # activate_repo_node [target-dir]
18
+ #
19
+ # Returns 0 when a declared version exists and a matching node binary was resolved;
20
+ # 1 when no version is declared; 2 when declared but no manager provided it.
21
+
22
+ _repo_node_matches_req() {
23
+ local req="$1" actual="${2:-}"
24
+ [ -n "$actual" ] || actual="$("$REPO_NODE_BIN" -v 2>/dev/null || true)"
25
+ [ -n "$actual" ] || return 1
26
+ actual="${actual#v}"
27
+ [ "$actual" = "$req" ] && return 0
28
+ case "$req" in
29
+ *.*.*) return 1 ;;
30
+ *.*) [[ "$actual" == "$req."* ]] ;;
31
+ *) [[ "$actual" == "$req"* ]] ;;
32
+ esac
33
+ }
34
+
35
+ _repo_node_set_from_bin() {
36
+ local candidate="$1"
37
+ [ -n "$candidate" ] && [ -x "$candidate" ] || return 1
38
+ REPO_NODE_BIN="$candidate"
39
+ REPO_NODE_BIN_DIR="$(cd "$(dirname "$REPO_NODE_BIN")" && pwd)"
40
+ REPO_NODE_PATH_PREFIX="${REPO_NODE_BIN_DIR}:"
41
+ export PATH="${REPO_NODE_BIN_DIR}:${PATH}"
42
+ return 0
43
+ }
44
+
45
+ _repo_node_try_req() {
46
+ local target="$1" req="$2" manager=""
47
+ [ -n "$req" ] || return 1
48
+ REPO_NODE_VERSION="$req"
49
+
50
+ if command -v asdf >/dev/null 2>&1; then
51
+ local asdf_root
52
+ asdf_root="$(asdf where nodejs "$req" 2>/dev/null || true)"
53
+ if [ -n "$asdf_root" ] && _repo_node_set_from_bin "$asdf_root/bin/node" && _repo_node_matches_req "$req"; then
54
+ manager=asdf
55
+ echo "[env] node $("$REPO_NODE_BIN" -v) from .tool-versions/.nvmrc via asdf ($req)" >&2
56
+ return 0
57
+ fi
58
+ fi
59
+
60
+ if command -v fnm >/dev/null 2>&1; then
61
+ local fnm_bin
62
+ fnm_bin="$(fnm exec --using "$req" -- which node 2>/dev/null || true)"
63
+ if _repo_node_set_from_bin "$fnm_bin" && _repo_node_matches_req "$req"; then
64
+ echo "[env] node $("$REPO_NODE_BIN" -v) from .tool-versions/.nvmrc via fnm ($req)" >&2
65
+ return 0
66
+ fi
67
+ fi
68
+
69
+ if command -v mise >/dev/null 2>&1; then
70
+ local mise_bin
71
+ mise_bin="$(mise x -C "$target" -- which node 2>/dev/null || true)"
72
+ if _repo_node_set_from_bin "$mise_bin" && _repo_node_matches_req "$req"; then
73
+ echo "[env] node $("$REPO_NODE_BIN" -v) from .tool-versions/.nvmrc via mise ($req)" >&2
74
+ return 0
75
+ fi
76
+ fi
77
+ return 1
78
+ }
79
+
80
+ activate_repo_node() {
81
+ local target="${1:-.}"
82
+ REPO_NODE_VERSION=""
83
+ REPO_NODE_BIN=""
84
+ REPO_NODE_BIN_DIR=""
85
+ REPO_NODE_PATH_PREFIX=""
86
+
87
+ local declared=false tool_ver="" nvmrc="" req="" nvm_dir path_node
88
+ if [ -f "$target/.tool-versions" ]; then
89
+ declared=true
90
+ tool_ver="$(awk '/^nodejs / {print $2; exit}' "$target/.tool-versions")"
91
+ _repo_node_try_req "$target" "$tool_ver" && return 0
92
+ fi
93
+ if [ -f "$target/.nvmrc" ]; then
94
+ declared=true
95
+ nvmrc="$(tr -d ' \t\r\nv' < "$target/.nvmrc")"
96
+ _repo_node_try_req "$target" "$nvmrc" && return 0
97
+ case "$nvmrc" in
98
+ *.*.*) ;;
99
+ *.*) _repo_node_try_req "$target" "${nvmrc}.0" && return 0 ;;
100
+ esac
101
+ fi
102
+
103
+ if [ "$declared" != true ]; then
104
+ echo "[env] no node version declared in .nvmrc or .tool-versions" >&2
105
+ return 1
106
+ fi
107
+
108
+ req="${tool_ver:-$nvmrc}"
109
+ if [ -z "$req" ]; then
110
+ echo "[env] node version file present but no nodejs entry in .tool-versions / .nvmrc" >&2
111
+ return 1
112
+ fi
113
+ REPO_NODE_VERSION="$req"
114
+ nvm_dir="${NVM_DIR:-$HOME/.nvm}"
115
+ if [ -s "$nvm_dir/nvm.sh" ]; then
116
+ # shellcheck disable=SC1090,SC1091
117
+ . "$nvm_dir/nvm.sh"
118
+ nvm use "$req" >/dev/null 2>&1 || nvm install "$req" >/dev/null 2>&1 || true
119
+ fi
120
+
121
+ path_node="$(command -v node 2>/dev/null || true)"
122
+ if _repo_node_set_from_bin "$path_node" && _repo_node_matches_req "$req"; then
123
+ echo "[env] node $("$REPO_NODE_BIN" -v) already on PATH ($req)" >&2
124
+ return 0
125
+ fi
126
+
127
+ REPO_NODE_BIN=""
128
+ REPO_NODE_BIN_DIR=""
129
+ REPO_NODE_PATH_PREFIX=""
130
+ echo "[env] need node ${req} (from .nvmrc / .tool-versions) but no installed manager provided it" >&2
131
+ return 2
132
+ }
133
+
134
+ require_repo_node() {
135
+ local target="${1:-.}"
136
+ if ! command -v activate_repo_node >/dev/null 2>&1; then
137
+ echo "[env] activate-repo-node.sh is not sourced; reinstall the harness (recipe sync)" >&2
138
+ return 1
139
+ fi
140
+ activate_repo_node "$target" || {
141
+ echo "[env] refusing to run yarn/webpack without a pinned Node for $target" >&2
142
+ return 1
143
+ }
144
+ }
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cli-color.mjs — shared ANSI styling for recipe CLI human output.
4
+ *
5
+ * Honors NO_COLOR, RECIPE_NO_COLOR, FORCE_COLOR, RECIPE_COLOR, and TTY detection.
6
+ */
7
+
8
+ const STYLES = {
9
+ reset: '\x1b[0m',
10
+ bold: '\x1b[1m',
11
+ dim: '\x1b[2m',
12
+ label: '\x1b[1;36m',
13
+ cmd: '\x1b[1m',
14
+ ok: '\x1b[1;32m',
15
+ warn: '\x1b[1;33m',
16
+ err: '\x1b[1;31m',
17
+ info: '\x1b[0;37m',
18
+ accent: '\x1b[1;35m',
19
+ path: '\x1b[2m',
20
+ comment: '\x1b[2m',
21
+ active: '\x1b[1m',
22
+ };
23
+
24
+ export function colorEnabled(stream = process.stderr) {
25
+ if (process.env.NO_COLOR != null && process.env.NO_COLOR !== '' && process.env.NO_COLOR !== '0') {
26
+ return false;
27
+ }
28
+ if (process.env.RECIPE_NO_COLOR === '1') return false;
29
+ if (process.env.RECIPE_COLOR === '1' || process.env.FORCE_COLOR === '1') return true;
30
+ return Boolean(stream?.isTTY);
31
+ }
32
+
33
+ export function stripAnsi(text) {
34
+ return String(text).replace(/\x1b\[[0-9;]*m/gu, '');
35
+ }
36
+
37
+ export function color(style, text, { stream = process.stderr } = {}) {
38
+ const value = String(text);
39
+ if (!colorEnabled(stream)) return value;
40
+ const code = STYLES[style];
41
+ if (!code) return value;
42
+ return `${code}${value}${STYLES.reset}`;
43
+ }
44
+
45
+ export function colorKv(label, value, valueStyle = 'info', { stream = process.stderr } = {}) {
46
+ return `${color('label', `${label}:`, { stream })} ${color(valueStyle, value, { stream })}`;
47
+ }
48
+
49
+ export function colorStatusWord(word, { stream = process.stderr } = {}) {
50
+ const normalized = String(word || '').toLowerCase();
51
+ if (['up', 'ready', 'yes', 'pass', 'running', 'present', 'healthy'].includes(normalized)) {
52
+ return color('ok', word, { stream });
53
+ }
54
+ if (['down', 'stopped', 'no', 'fail', 'failed', 'missing', 'blocked', 'unknown'].includes(normalized)) {
55
+ return color('err', word, { stream });
56
+ }
57
+ if (['compiling', 'bundling', 'starting', 'waiting', 'warn'].includes(normalized)) {
58
+ return color('warn', word, { stream });
59
+ }
60
+ return color('info', word, { stream });
61
+ }
62
+
63
+ export function classifyLogEvent(event) {
64
+ const text = String(event || '');
65
+ if (/Module build failed|^ERROR in |BUILD FAILED|compiled with [1-9][0-9]* error/iu.test(text)) {
66
+ return 'err';
67
+ }
68
+ if (/compiled successfully|compiled with [0-9]+ warning|PASS runtime-launch|launch pass|verify pass|build complete/iu.test(text)) {
69
+ return 'ok';
70
+ }
71
+ if (/phase [0-9]\/[0-9]|prepare pipeline|snapshot|fixture:|dist-freshness|build-health/iu.test(text)) {
72
+ return 'label';
73
+ }
74
+ if (/webpack [0-9]+%|Bundl(ed|ing)|(iOS|Android).*%/iu.test(text)) {
75
+ return 'warn';
76
+ }
77
+ return 'info';
78
+ }
79
+
80
+ export function colorLogEvent(event, { latest = false, stream = process.stderr } = {}) {
81
+ const style = classifyLogEvent(event);
82
+ const painted = color(style === 'label' ? 'label' : style, event, { stream });
83
+ return latest ? color('active', painted, { stream }) : painted;
84
+ }
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cli-commands.mjs — single source of truth for recipe CLI tab completion.
4
+ */
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ /** @typedef {{ name: string, aliases?: string[], desc?: string, flags?: string[], args?: string[] }} CommandSpec */
10
+
11
+ /** @type {{ shared: CommandSpec[], extension: CommandSpec[], mobile: CommandSpec[], shim: CommandSpec[] }} */
12
+ export const SPEC = {
13
+ shim: [
14
+ { name: 'sync', desc: 'Copy fixture, write slot context, install harness', flags: ['--dry-run', '--no-install'] },
15
+ { name: 'help', aliases: ['-h', '--help'], desc: 'Show usage' },
16
+ ],
17
+ shared: [
18
+ { name: 'status', aliases: ['health', 'home'], desc: 'Home status + next commands', flags: ['--json'] },
19
+ { name: 'ports', desc: 'Slot ports and runtime paths', flags: ['--json'] },
20
+ { name: 'up', desc: 'Decide + run minimum work to reach ready', flags: ['--json', '--dry-run'] },
21
+ { name: 'sync', desc: 'Refresh harness + canonicalize wallet fixture', flags: ['--json'] },
22
+ { name: 'logs', aliases: ['tail'], desc: 'Compact build events or full log', flags: ['--full', '-f'] },
23
+ { name: 'debug', aliases: ['devtools', 'inspect'], desc: 'Open DevTools UI', flags: ['--json', '--no-open'] },
24
+ { name: 'actions', desc: 'List runnable recipe actions', flags: ['--json'] },
25
+ { name: 'doctor', desc: 'Check harness/orchestration health', flags: ['--json'] },
26
+ { name: 'run', desc: 'Execute a proof recipe', args: ['recipe.json'] },
27
+ { name: 'interactive', aliases: ['menu'], desc: 'Interactive command menu' },
28
+ { name: 'prepare', desc: 'Install harness (+ optional validate)', flags: ['--target', '--runtime-dir', '--json'] },
29
+ { name: 'runtime-status', desc: 'Structured runtime status JSON', flags: ['--json', '--target', '--cdp-port', '--runtime-dir'] },
30
+ ],
31
+ extension: [
32
+ { name: 'decision', aliases: ['decide'], desc: 'Runtime decision (what next?)' },
33
+ { name: 'update', aliases: ['sync-runtime'], desc: 'Perform recommended runtime action' },
34
+ { name: 'ready', aliases: ['ensure-ready'], desc: 'Ensure one healthy extension home tab' },
35
+ { name: 'watch', aliases: ['start-watch', 'watcher'], desc: 'Start/reuse webpack watcher', flags: ['--full', '-f'] },
36
+ { name: 'stop', aliases: ['stop-watch', 'stop-watcher'], desc: 'Stop this checkout watcher' },
37
+ { name: 'rebuild', aliases: ['reset', 'reopen', 'reload', 'browser', 'launch', 'full-launch', 'runtime-launch'], desc: 'Clean webpack + browser launch', flags: ['--json', '--dry-run', '--full'] },
38
+ { name: 'refresh', aliases: ['build'], desc: 'Fast refresh with active watcher' },
39
+ { name: 'refresh:once', aliases: ['refresh-once', 'build:once', 'build-once'], desc: 'One-shot refresh-build helper' },
40
+ { name: 'sidepanel', desc: 'Side panel helper', args: ['cycle', 'open', 'close', 'toggle', 'status'] },
41
+ { name: 'prepare', flags: ['--target', '--cdp-port', '--runtime-dir', '--validate'] },
42
+ { name: 'debug', args: ['page', 'worker'], flags: ['--json', '--no-open'] },
43
+ ],
44
+ mobile: [
45
+ { name: 'ios', aliases: ['start'], desc: 'Start Metro + launch iOS dev client' },
46
+ { name: 'android', desc: 'Start Metro + launch Android dev client' },
47
+ { name: 'setup:ios', desc: 'iOS launch + wallet fixture setup' },
48
+ { name: 'setup:android', desc: 'Android launch + wallet fixture setup' },
49
+ { name: 'refresh', aliases: ['reload', 'relaunch'], desc: 'Refresh Metro + relaunch dev client' },
50
+ { name: 'stop', desc: 'Stop Metro for this slot' },
51
+ { name: 'app-status', aliases: ['bridge-status'], desc: 'Bridge/runtime status' },
52
+ { name: 'route', aliases: ['get-route'], desc: 'Current navigation route' },
53
+ { name: 'navigate', desc: 'Navigate by route name', args: ['RouteName'] },
54
+ { name: 'back', aliases: ['go-back'], desc: 'Navigate back' },
55
+ { name: 'unlock', desc: 'Unlock with fixture password', args: ['password'] },
56
+ { name: 'setup-wallet', aliases: ['wallet-setup'], desc: 'Apply wallet fixture', args: ['fixture.json'] },
57
+ { name: 'accounts', aliases: ['list-accounts'], desc: 'List accounts' },
58
+ { name: 'select-account', aliases: ['switch-account'], desc: 'Switch account', args: ['address'] },
59
+ { name: 'screenshot', desc: 'Capture simulator/device screenshot', args: ['path'] },
60
+ { name: 'dev-menu', aliases: ['devmenu'], desc: 'Open RN developer menu', flags: ['--json', '--no-open'] },
61
+ { name: 'prepare', flags: ['--target', '--platform', '--preflight-mode', '--port', '--simulator', '--adb-serial', '--runtime-dir', '--wallet-setup', '--wallet-fixture'] },
62
+ { name: 'debug', flags: ['--json', '--no-open', '--action'] },
63
+ ],
64
+ };
65
+
66
+ const GLOBAL_FLAGS = ['--json', '--dry-run', '--full', '-f', '--help', '-h', '--no-open', '--no-color'];
67
+
68
+ function mergeCommands(adapter) {
69
+ const byName = new Map();
70
+ const add = (cmd) => {
71
+ const existing = byName.get(cmd.name);
72
+ if (!existing) {
73
+ byName.set(cmd.name, {
74
+ ...cmd,
75
+ aliases: [...(cmd.aliases || [])],
76
+ flags: [...(cmd.flags || [])],
77
+ args: [...(cmd.args || [])],
78
+ });
79
+ return;
80
+ }
81
+ existing.aliases = [...new Set([...(existing.aliases || []), ...(cmd.aliases || [])])];
82
+ existing.flags = [...new Set([...(existing.flags || []), ...(cmd.flags || [])])];
83
+ existing.args = [...new Set([...(existing.args || []), ...(cmd.args || [])])];
84
+ if (!existing.desc && cmd.desc) existing.desc = cmd.desc;
85
+ };
86
+ for (const cmd of SPEC.shared) add(cmd);
87
+ for (const cmd of SPEC[adapter] || []) add(cmd);
88
+ return [...byName.values()];
89
+ }
90
+
91
+ export function commandTokens(adapter, { includeAliases = true } = {}) {
92
+ const tokens = new Set();
93
+ for (const cmd of mergeCommands(adapter)) {
94
+ tokens.add(cmd.name);
95
+ if (includeAliases) for (const alias of cmd.aliases || []) tokens.add(alias);
96
+ }
97
+ return [...tokens].sort();
98
+ }
99
+
100
+ export function shimCommandTokens() {
101
+ const tokens = new Set();
102
+ for (const cmd of SPEC.shim) {
103
+ tokens.add(cmd.name);
104
+ for (const alias of cmd.aliases || []) tokens.add(alias);
105
+ }
106
+ return [...tokens].sort();
107
+ }
108
+
109
+ export function lookupCommand(adapter, token) {
110
+ for (const cmd of mergeCommands(adapter)) {
111
+ if (cmd.name === token) return cmd;
112
+ if ((cmd.aliases || []).includes(token)) return cmd;
113
+ }
114
+ for (const cmd of SPEC.shim) {
115
+ if (cmd.name === token) return cmd;
116
+ if ((cmd.aliases || []).includes(token)) return cmd;
117
+ }
118
+ return null;
119
+ }
120
+
121
+ export function flagsForPosition(adapter, commandToken) {
122
+ const cmd = lookupCommand(adapter, commandToken);
123
+ const flags = new Set(GLOBAL_FLAGS);
124
+ if (cmd) for (const flag of cmd.flags || []) flags.add(flag);
125
+ return [...flags].sort();
126
+ }
127
+
128
+ export function argsForPosition(adapter, commandToken, argIndex) {
129
+ const cmd = lookupCommand(adapter, commandToken);
130
+ if (!cmd?.args?.length) return [];
131
+ const value = cmd.args[Math.max(0, argIndex)];
132
+ return value ? [value] : cmd.args;
133
+ }
134
+
135
+ const RUN_RECIPE_DIRS = [
136
+ (adapter, root) => path.join(root, 'temp/recipe/harness', adapter, 'runner/recipes'),
137
+ (adapter, root) => path.join(root, 'temp/agentic/recipe-harness', adapter, 'runner/recipes'),
138
+ ];
139
+
140
+ /** Harness runner recipes only — used for `recipe run <tab>`. */
141
+ export function listRunRecipes(adapter, root, cwd = process.cwd()) {
142
+ const byBase = new Map();
143
+ for (const resolveDir of RUN_RECIPE_DIRS) {
144
+ const dir = resolveDir(adapter, root);
145
+ if (!fs.existsSync(dir)) continue;
146
+ for (const name of fs.readdirSync(dir)) {
147
+ if (!name.endsWith('.recipe.json') || byBase.has(name)) continue;
148
+ byBase.set(name, path.join(dir, name));
149
+ }
150
+ }
151
+ return [...byBase.values()]
152
+ .map((abs) => path.relative(cwd, abs))
153
+ .sort();
154
+ }
155
+
156
+ function usage() {
157
+ process.stderr.write(`Usage:
158
+ cli-commands.mjs dump --adapter extension|mobile|shim [--aliases]
159
+ cli-commands.mjs describe --adapter extension|mobile|shim
160
+ cli-commands.mjs flags --adapter <adapter> --command <name>
161
+ cli-commands.mjs args --adapter <adapter> --command <name> [--index <n>]
162
+ cli-commands.mjs run-recipes --adapter extension|mobile --root <repo> [--cwd <dir>]
163
+ cli-commands.mjs global-flags
164
+ `);
165
+ }
166
+
167
+ async function main() {
168
+ const { parseArgs } = await import('node:util');
169
+ const { values, positionals } = parseArgs({
170
+ allowPositionals: true,
171
+ args: process.argv.slice(2),
172
+ options: {
173
+ adapter: { type: 'string' },
174
+ command: { type: 'string' },
175
+ aliases: { type: 'boolean', default: true },
176
+ index: { type: 'string', default: '0' },
177
+ root: { type: 'string' },
178
+ cwd: { type: 'string' },
179
+ },
180
+ });
181
+
182
+ const command = positionals[0];
183
+ if (!command || command === '--help' || command === '-h') {
184
+ usage();
185
+ process.exit(command ? 0 : 2);
186
+ }
187
+
188
+ if (command === 'global-flags') {
189
+ process.stdout.write(`${GLOBAL_FLAGS.join('\n')}\n`);
190
+ return;
191
+ }
192
+
193
+ const adapter = values.adapter;
194
+ if (!adapter && command !== 'global-flags') {
195
+ usage();
196
+ process.exit(2);
197
+ }
198
+
199
+ if (command === 'dump') {
200
+ process.stdout.write(`${commandTokens(adapter, { includeAliases: values.aliases }).join('\n')}\n`);
201
+ return;
202
+ }
203
+
204
+ if (command === 'describe') {
205
+ const rows = adapter === 'shim' ? SPEC.shim : mergeCommands(adapter);
206
+ for (const cmd of rows) {
207
+ const aliases = (cmd.aliases || []).join('|');
208
+ const label = aliases ? `${cmd.name}|${aliases}` : cmd.name;
209
+ process.stdout.write(`${label}\t${cmd.desc || cmd.name}\n`);
210
+ }
211
+ return;
212
+ }
213
+
214
+ if (command === 'flags') {
215
+ process.stdout.write(`${flagsForPosition(adapter, values.command || '').join('\n')}\n`);
216
+ return;
217
+ }
218
+
219
+ if (command === 'args') {
220
+ const index = Number.parseInt(values.index, 10) || 0;
221
+ process.stdout.write(`${argsForPosition(adapter, values.command || '', index).join('\n')}\n`);
222
+ return;
223
+ }
224
+
225
+ if (command === 'run-recipes') {
226
+ const root = values.root ? path.resolve(values.root) : process.cwd();
227
+ const cwd = values.cwd ? path.resolve(values.cwd) : process.cwd();
228
+ process.stdout.write(`${listRunRecipes(adapter, root, cwd).join('\n')}\n`);
229
+ return;
230
+ }
231
+
232
+ usage();
233
+ process.exit(2);
234
+ }
235
+
236
+ const entry = process.argv[1] ? path.resolve(process.argv[1]) : '';
237
+ const self = fileURLToPath(import.meta.url);
238
+ if (entry === self) {
239
+ main().catch((error) => {
240
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
241
+ process.exit(1);
242
+ });
243
+ }