@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,137 @@
1
+ #!/usr/bin/env bash
2
+ # launch.sh — mobile instance identity intake + prepare/preflight launch
3
+ #
4
+ # Purpose:
5
+ # Resolves the per-instance tuple (port, simulator/adb-serial, runtime dir,
6
+ # platform, preflight mode) and runs the installed runner's mobile prepare.
7
+ #
8
+ # Inputs (flags / env precedence):
9
+ # --target <metamask-mobile> (default $PWD)
10
+ # --platform ios|android (default ios)
11
+ # --preflight-mode|--mode fast|auto|default|rebuild-native|clean
12
+ # (env RECIPE_HARNESS_MOBILE_PREFLIGHT_MODE, default fast)
13
+ # --port|--watcher-port|--cdp-port <port> (env WATCHER_PORT > METRO_PORT > CDP_PORT)
14
+ # --simulator <sim> (env IOS_SIMULATOR > SIM_UDID)
15
+ # --adb-serial <serial> (env ADB_SERIAL > ANDROID_SERIAL)
16
+ # --runtime-dir <dir> (env RECIPE_RUNTIME_DIR)
17
+ # --wallet-setup / --no-wallet-setup, --wallet-fixture <json>
18
+ # --artifacts-dir <dir>
19
+ #
20
+ # Outputs:
21
+ # <artifacts>/summary.json (adapter/action/status/runtime/generatedAt),
22
+ # <artifacts>/runtime-status.json, <artifacts>/logs/prepare.log
23
+ # Exit 0 — launch pass; 1 — runner missing or prepare failed; 2 — bad args.
24
+ #
25
+ # Never touches: product source files; anything outside the target's harness
26
+ # dir and the artifacts dir.
27
+ set -euo pipefail
28
+
29
+ TARGET="$PWD"
30
+ PLATFORM="ios"
31
+ PREFLIGHT_MODE="${RECIPE_HARNESS_MOBILE_PREFLIGHT_MODE:-fast}"
32
+ ARTIFACTS=""
33
+ PORT="${WATCHER_PORT:-${METRO_PORT:-${CDP_PORT:-}}}"
34
+ SIMULATOR="${IOS_SIMULATOR:-${SIM_UDID:-}}"
35
+ ADB_SERIAL_ARG="${ADB_SERIAL:-${ANDROID_SERIAL:-}}"
36
+ RUNTIME_DIR_ARG="${RECIPE_RUNTIME_DIR:-}"
37
+ WALLET_SETUP=false
38
+ WALLET_FIXTURE_ARG=""
39
+ require_value() { [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; }
40
+ while [ "$#" -gt 0 ]; do
41
+ case "$1" in
42
+ --target) require_value "$@"; TARGET="$2"; shift 2 ;;
43
+ --platform) require_value "$@"; PLATFORM="$2"; shift 2 ;;
44
+ --preflight-mode|--mode) require_value "$@"; PREFLIGHT_MODE="$2"; shift 2 ;;
45
+ --artifacts-dir) require_value "$@"; ARTIFACTS="$2"; shift 2 ;;
46
+ --port|--watcher-port|--cdp-port) require_value "$@"; PORT="$2"; shift 2 ;;
47
+ --simulator) require_value "$@"; SIMULATOR="$2"; shift 2 ;;
48
+ --adb-serial) require_value "$@"; ADB_SERIAL_ARG="$2"; shift 2 ;;
49
+ --runtime-dir) require_value "$@"; RUNTIME_DIR_ARG="$2"; shift 2 ;;
50
+ --wallet-setup) WALLET_SETUP=true; shift ;;
51
+ --no-wallet-setup) WALLET_SETUP=false; shift ;;
52
+ --wallet-fixture) require_value "$@"; WALLET_FIXTURE_ARG="$2"; shift 2 ;;
53
+ -h|--help)
54
+ echo "Usage: launch.sh [--target <metamask-mobile>] [--platform ios|android] [--preflight-mode fast|auto|default|rebuild-native|clean] [--port <port>] [--simulator <sim>] [--runtime-dir <dir>] [--wallet-setup] [--wallet-fixture <json>]"
55
+ exit 0
56
+ ;;
57
+ *) echo "Unknown arg: $1" >&2; exit 2 ;;
58
+ esac
59
+ done
60
+
61
+ case "$PLATFORM" in ios|android) ;; *) echo "Unknown --platform: $PLATFORM" >&2; exit 2 ;; esac
62
+ case "$PREFLIGHT_MODE" in fast|auto|default|rebuild-native|clean) ;; *) echo "Unknown --preflight-mode: $PREFLIGHT_MODE" >&2; exit 2 ;; esac
63
+
64
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
65
+ # shellcheck disable=SC1091
66
+ for _hp in "$SCRIPT_DIR/lib/harness-path.sh" "$SCRIPT_DIR/../lib/harness-path.sh"; do
67
+ [ -f "$_hp" ] && { . "$_hp"; break; }
68
+ done
69
+ unset _hp
70
+ if ! command -v harness_root >/dev/null 2>&1; then
71
+ echo "mm-harness: shared lib orchestration/lib/harness-path.sh not found; reinstall the runner." >&2
72
+ exit 1
73
+ fi
74
+ TARGET="$(cd "$TARGET" && pwd)"
75
+ HARNESS_DIR="$(harness_dir "$TARGET" mobile)"
76
+ RUNNER_BIN="$HARNESS_DIR/runner/bin/metamask-recipe"
77
+ ARTIFACTS="${ARTIFACTS:-$HARNESS_DIR/launch/$(date -u +%Y%m%dT%H%M%SZ)}"
78
+ mkdir -p "$ARTIFACTS/logs"
79
+
80
+ if [ ! -x "$RUNNER_BIN" ]; then
81
+ echo "Mobile recipe runner is not installed in $TARGET. Run metamask-recipe mobile install --target $TARGET first." >&2
82
+ exit 1
83
+ fi
84
+
85
+ prepare_args=(mobile prepare --target "$TARGET" --platform "$PLATFORM" --preflight-mode "$PREFLIGHT_MODE")
86
+ [ -n "$PORT" ] && prepare_args+=(--port "$PORT")
87
+ [ -n "$SIMULATOR" ] && prepare_args+=(--simulator "$SIMULATOR")
88
+ [ -n "$ADB_SERIAL_ARG" ] && prepare_args+=(--adb-serial "$ADB_SERIAL_ARG")
89
+ [ -n "$RUNTIME_DIR_ARG" ] && prepare_args+=(--runtime-dir "$RUNTIME_DIR_ARG")
90
+ $WALLET_SETUP && prepare_args+=(--wallet-setup)
91
+ [ -n "$WALLET_FIXTURE_ARG" ] && prepare_args+=(--wallet-fixture "$WALLET_FIXTURE_ARG")
92
+
93
+ status="pass"
94
+ if (cd "$TARGET" && "$RUNNER_BIN" "${prepare_args[@]}") > "$ARTIFACTS/logs/prepare.log" 2>&1; then
95
+ runtime_status="pass"
96
+ else
97
+ runtime_status="fail"
98
+ status="fail"
99
+ fi
100
+
101
+ status_args=(mobile runtime-status --target "$TARGET" --json)
102
+ [ -n "$PORT" ] && status_args+=(--port "$PORT")
103
+ [ -n "$RUNTIME_DIR_ARG" ] && status_args+=(--runtime-dir "$RUNTIME_DIR_ARG")
104
+ (cd "$TARGET" && "$RUNNER_BIN" "${status_args[@]}") > "$ARTIFACTS/runtime-status.json" 2> "$ARTIFACTS/logs/runtime-status.err" || true
105
+
106
+ TARGET_FOR_SUMMARY="$TARGET" ARTIFACTS_FOR_SUMMARY="$ARTIFACTS" STATUS_FOR_SUMMARY="$status" RUNTIME_STATUS="$runtime_status" PLATFORM_FOR_SUMMARY="$PLATFORM" MODE_FOR_SUMMARY="$PREFLIGHT_MODE" node <<'NODE'
107
+ const fs = require('fs');
108
+ const path = require('path');
109
+ const artifacts = process.env.ARTIFACTS_FOR_SUMMARY;
110
+ let runtime = null;
111
+ try { runtime = JSON.parse(fs.readFileSync(path.join(artifacts, 'runtime-status.json'), 'utf8')); } catch {}
112
+ fs.writeFileSync(path.join(artifacts, 'summary.json'), `${JSON.stringify({
113
+ adapter: 'mobile',
114
+ action: 'launch',
115
+ status: process.env.STATUS_FOR_SUMMARY,
116
+ platform: process.env.PLATFORM_FOR_SUMMARY,
117
+ preflightMode: process.env.MODE_FOR_SUMMARY,
118
+ target: process.env.TARGET_FOR_SUMMARY,
119
+ runtime: {
120
+ status: process.env.RUNTIME_STATUS,
121
+ ready: runtime?.ready === true,
122
+ reason: runtime?.reason || null,
123
+ port: runtime?.metro?.port || null,
124
+ prepareLogPath: path.join(artifacts, 'logs/prepare.log'),
125
+ runtimeStatusPath: path.join(artifacts, 'runtime-status.json'),
126
+ },
127
+ runtimePolicy: {
128
+ nativeBuildPolicy: process.env.MODE_FOR_SUMMARY === 'fast'
129
+ ? 'fast mode never runs a native build; it requires an installed dev client, starts/reuses Metro, prewarms the JS bundle, opens the dev client, and waits for the bridge'
130
+ : 'non-fast modes may install or rebuild the native dev client when needed; then start/reuse Metro, prewarm the JS bundle, open the dev client, and wait for the bridge',
131
+ },
132
+ generatedAt: new Date().toISOString(),
133
+ }, null, 2)}\n`);
134
+ NODE
135
+
136
+ echo "Mobile harness launch $status: $ARTIFACTS/summary.json"
137
+ [ "$status" = "pass" ]
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env bash
2
+ # live.sh — mobile launch-then-verify chaining for one live slot run
3
+ #
4
+ # Purpose:
5
+ # Runs launch.sh then verify.sh (verify with --no-auto-start; launch owns
6
+ # startup) under a timestamped artifact dir and aggregates both results.
7
+ #
8
+ # Inputs (flags / env):
9
+ # --target <metamask-mobile> (default $PWD)
10
+ # --platform ios|android (default ios)
11
+ # --preflight-mode|--mode fast|auto|default|rebuild-native|clean
12
+ # (env RECIPE_HARNESS_MOBILE_PREFLIGHT_MODE, default fast)
13
+ # --artifacts-dir <dir> (default <harness>/live/<UTC>)
14
+ # --wallet-setup|--no-wallet-setup (default auto), --wallet-fixture <json>
15
+ #
16
+ # Outputs:
17
+ # <artifacts>/summary.json (adapter/action/status, launch/verify exit codes
18
+ # + child summary paths, easyCommand), <artifacts>/{launch,verify}/.
19
+ # Exit 0 — launch and verify both passed; 1 — either failed; 2 — bad args.
20
+ #
21
+ # Never touches: product source files; anything outside the target's harness
22
+ # dir and the artifacts dir.
23
+ set -euo pipefail
24
+
25
+ TARGET="$PWD"
26
+ PLATFORM="ios"
27
+ PREFLIGHT_MODE="${RECIPE_HARNESS_MOBILE_PREFLIGHT_MODE:-fast}"
28
+ ARTIFACTS=""
29
+ WALLET_SETUP="auto"
30
+ WALLET_FIXTURE=""
31
+ while [ "$#" -gt 0 ]; do
32
+ case "$1" in
33
+ --target) TARGET="$2"; shift 2 ;;
34
+ --platform) PLATFORM="$2"; shift 2 ;;
35
+ --preflight-mode|--mode) PREFLIGHT_MODE="$2"; shift 2 ;;
36
+ --artifacts-dir) ARTIFACTS="$2"; shift 2 ;;
37
+ --wallet-setup) WALLET_SETUP="true"; shift ;;
38
+ --no-wallet-setup) WALLET_SETUP="false"; shift ;;
39
+ --wallet-fixture) WALLET_FIXTURE="$2"; shift 2 ;;
40
+ -h|--help) echo "Usage: live.sh [--target <metamask-mobile>] [--platform ios|android] [--preflight-mode fast|auto|default|rebuild-native|clean] [--artifacts-dir <dir>] [--wallet-setup|--no-wallet-setup]"; exit 0 ;;
41
+ *) echo "Unknown arg: $1" >&2; exit 2 ;;
42
+ esac
43
+ done
44
+
45
+ case "$PLATFORM" in ios|android) ;; *) echo "Unknown --platform: $PLATFORM" >&2; exit 2 ;; esac
46
+ case "$PREFLIGHT_MODE" in fast|auto|default|rebuild-native|clean) ;; *) echo "Unknown --preflight-mode: $PREFLIGHT_MODE" >&2; exit 2 ;; esac
47
+
48
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
49
+ # shellcheck disable=SC1091
50
+ for _hp in "$SCRIPT_DIR/lib/harness-path.sh" "$SCRIPT_DIR/../lib/harness-path.sh"; do
51
+ [ -f "$_hp" ] && { . "$_hp"; break; }
52
+ done
53
+ unset _hp
54
+ if ! command -v harness_root >/dev/null 2>&1; then
55
+ echo "mm-harness: shared lib orchestration/lib/harness-path.sh not found; reinstall the runner." >&2
56
+ exit 1
57
+ fi
58
+ # verify.sh is a recipe-tree feature: co-located in installed copies, under
59
+ # runner/mobile in a repo checkout.
60
+ VERIFY_SH=""
61
+ for _v in "$SCRIPT_DIR/verify.sh" "$SCRIPT_DIR/../../runner/mobile/verify.sh"; do
62
+ [ -f "$_v" ] && { VERIFY_SH="$_v"; break; }
63
+ done
64
+ unset _v
65
+ if [ -z "$VERIFY_SH" ]; then
66
+ echo "mm-harness: mobile verify.sh not found next to $SCRIPT_DIR; reinstall the runner." >&2
67
+ exit 1
68
+ fi
69
+ TARGET="$(cd "$TARGET" && pwd)"
70
+ WALLET_FIXTURE="${WALLET_FIXTURE:-$(recipe_runtime_dir)/wallet-fixture.json}"
71
+ ARTIFACTS="${ARTIFACTS:-$(harness_dir "$TARGET" mobile)/live/$(date -u +%Y%m%dT%H%M%SZ)}"
72
+ mkdir -p "$ARTIFACTS"
73
+
74
+ launch_args=(--target "$TARGET" --platform "$PLATFORM" --preflight-mode "$PREFLIGHT_MODE" --artifacts-dir "$ARTIFACTS/launch")
75
+ case "$WALLET_SETUP" in
76
+ true) launch_args+=(--wallet-setup) ;;
77
+ false) launch_args+=(--no-wallet-setup) ;;
78
+ esac
79
+ launch_args+=(--wallet-fixture "$WALLET_FIXTURE")
80
+
81
+ echo "Mobile live validation command:"
82
+ echo " mm-harness launch $PLATFORM --verify"
83
+ echo "Launch artifacts: $ARTIFACTS/launch"
84
+ echo "Verify artifacts: $ARTIFACTS/verify"
85
+
86
+ set +e
87
+ "$SCRIPT_DIR/launch.sh" "${launch_args[@]}"
88
+ launch_status=$?
89
+ set -e
90
+
91
+ verify_status=1
92
+ if [ "$launch_status" -eq 0 ]; then
93
+ set +e
94
+ "$VERIFY_SH" --target "$TARGET" --platform "$PLATFORM" --preflight-mode "$PREFLIGHT_MODE" --no-auto-start --artifacts-dir "$ARTIFACTS/verify"
95
+ verify_status=$?
96
+ set -e
97
+ else
98
+ echo "Skipping Mobile live verify because launch failed; see $ARTIFACTS/launch/summary.json" >&2
99
+ fi
100
+
101
+ TARGET_FOR_SUMMARY="$TARGET" ARTIFACTS_FOR_SUMMARY="$ARTIFACTS" PLATFORM_FOR_SUMMARY="$PLATFORM" MODE_FOR_SUMMARY="$PREFLIGHT_MODE" LAUNCH_STATUS="$launch_status" VERIFY_STATUS="$verify_status" node <<'NODE'
102
+ const fs = require('fs');
103
+ const path = require('path');
104
+ const artifacts = process.env.ARTIFACTS_FOR_SUMMARY;
105
+ const launchSummary = path.join(artifacts, 'launch', 'summary.json');
106
+ const verifySummary = path.join(artifacts, 'verify', 'summary.json');
107
+ const launchStatus = Number(process.env.LAUNCH_STATUS);
108
+ const verifyStatus = Number(process.env.VERIFY_STATUS);
109
+ fs.writeFileSync(path.join(artifacts, 'summary.json'), `${JSON.stringify({
110
+ adapter: 'mobile',
111
+ action: 'live',
112
+ status: launchStatus === 0 && verifyStatus === 0 ? 'pass' : 'fail',
113
+ target: process.env.TARGET_FOR_SUMMARY,
114
+ platform: process.env.PLATFORM_FOR_SUMMARY,
115
+ preflightMode: process.env.MODE_FOR_SUMMARY,
116
+ launch: { exitCode: launchStatus, summaryPath: fs.existsSync(launchSummary) ? launchSummary : null },
117
+ verify: { exitCode: verifyStatus, summaryPath: fs.existsSync(verifySummary) ? verifySummary : null },
118
+ easyCommand: `mm-harness launch ${process.env.PLATFORM_FOR_SUMMARY} --verify`,
119
+ note: 'Runs launch then live verify so a developer can validate app startup, runner-owned CDP bridge, screenshot capture, and tiny recipe control from one runner-owned command.',
120
+ generatedAt: new Date().toISOString(),
121
+ }, null, 2)}\n`);
122
+ NODE
123
+
124
+ echo "Mobile live validation summary: $ARTIFACTS/summary.json"
125
+ [ "$launch_status" -eq 0 ] && [ "$verify_status" -eq 0 ]
@@ -0,0 +1,292 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import {
5
+ clearDecisionState,
6
+ depsCheck,
7
+ recordDepsBaseline,
8
+ type DepsCheck,
9
+ } from '@farmslot/recipe-harness/runtime/deps-readiness';
10
+ import {
11
+ analyzeBundleLog,
12
+ evaluatePersistentBundleError,
13
+ supersededErrorCapture,
14
+ type BundleLogAnalysis,
15
+ } from '@farmslot/recipe-harness/runtime/log-analysis';
16
+ import { probeMetroPackager, type MetroReachabilityCheck } from '@farmslot/recipe-harness/runtime/metro-probe';
17
+
18
+ import { mobileProductMarkers } from './deps-markers.ts';
19
+
20
+ /**
21
+ * MetaMask Mobile runtime-readiness adapter.
22
+ *
23
+ * Generic deps/log/metro primitives live in `@farmslot/recipe-harness/runtime/*`.
24
+ * This file owns MetaMask-specific markers, Nitro staleness regex, and action ids.
25
+ */
26
+
27
+ export type MobileReadinessDecision = 'install' | 'launch' | 'ready' | 'blocked' | 'unknown';
28
+
29
+ export interface MobileRuntimeDecisionAction {
30
+ id: string;
31
+ argv?: string[];
32
+ cwd?: string;
33
+ paths?: string[];
34
+ }
35
+
36
+ export type MetroLogCheck = BundleLogAnalysis;
37
+
38
+ export interface MobileRuntimeDecisionReport {
39
+ schemaVersion: 1;
40
+ adapter: 'mobile';
41
+ target: string;
42
+ decision: MobileReadinessDecision;
43
+ reasonCode: string;
44
+ reasons: string[];
45
+ checks: {
46
+ deps: DepsCheck;
47
+ metroLog: MetroLogCheck;
48
+ metro: MetroReachabilityCheck;
49
+ };
50
+ actions: MobileRuntimeDecisionAction[];
51
+ }
52
+
53
+ export interface MobileRuntimeDecisionOptions {
54
+ watcherPort?: number;
55
+ metroLog?: string;
56
+ platform?: string;
57
+ record?: boolean;
58
+ }
59
+
60
+ const BUNDLE_ERR = /Bundling failed|Unable to resolve /u;
61
+ const BUNDLE_OK = /Bundled \d+ms|iOS Bundled|Android Bundled|Finished bundling/u;
62
+ const NATIVE_MODULE_STALE =
63
+ /\[runtime not ready\].*HybridObject "([^"]+)" - It has not yet been registered in the Nitro Modules HybridObjectRegistry/u;
64
+
65
+ function resolveMetroLog(target: string, metroLog?: string): string | null {
66
+ const rel = metroLog ?? 'temp/recipe/runtime/metro.log';
67
+ const abs = path.isAbsolute(rel) ? rel : path.join(target, rel);
68
+ return fs.existsSync(abs) ? abs : null;
69
+ }
70
+
71
+ function metroLogCheck(target: string, metroLog?: string): MetroLogCheck {
72
+ const abs = resolveMetroLog(target, metroLog);
73
+ if (!abs) return { status: 'no-log' };
74
+ const logText = fs.readFileSync(abs, 'utf8');
75
+ return analyzeBundleLog({
76
+ target,
77
+ logText,
78
+ errorPattern: BUNDLE_ERR,
79
+ okPattern: BUNDLE_OK,
80
+ });
81
+ }
82
+
83
+ const installActions = (target: string): MobileRuntimeDecisionAction[] => [
84
+ { id: 'yarn-setup', argv: ['yarn', 'setup'], cwd: target },
85
+ ];
86
+
87
+ const launchActions = (target: string, clearMetro = false): MobileRuntimeDecisionAction[] => {
88
+ const actions: MobileRuntimeDecisionAction[] = [];
89
+ if (clearMetro) {
90
+ actions.push({ id: 'clear-metro-cache', argv: ['yarn', 'expo', 'start', '--clear'], cwd: target });
91
+ }
92
+ actions.push({ id: 'launch-mobile-runtime' });
93
+ return actions;
94
+ };
95
+
96
+ export async function decideMobileReadiness(
97
+ target: string,
98
+ options: MobileRuntimeDecisionOptions = {},
99
+ ): Promise<MobileRuntimeDecisionReport> {
100
+ const resolved = path.resolve(target);
101
+ if (options.record) recordDepsBaseline(resolved);
102
+
103
+ const deps = depsCheck(resolved, {
104
+ productMarkers: mobileProductMarkers(options.platform),
105
+ });
106
+ const metroLog = metroLogCheck(resolved, options.metroLog);
107
+ const metro = options.watcherPort
108
+ ? await probeMetroPackager(options.watcherPort)
109
+ : { status: 'skipped' as const };
110
+
111
+ if (metroLog.status === 'ok') {
112
+ clearDecisionState(resolved, 'bundle-error-state.json');
113
+ }
114
+
115
+ const checks = { deps, metroLog, metro };
116
+
117
+ if (deps.status === 'missing') {
118
+ return {
119
+ schemaVersion: 1,
120
+ adapter: 'mobile',
121
+ target: resolved,
122
+ decision: 'install',
123
+ reasonCode: 'deps-missing',
124
+ reasons: ['Dependencies are not installed (no yarn install-state markers).'],
125
+ checks,
126
+ actions: installActions(resolved),
127
+ };
128
+ }
129
+
130
+ if (deps.status === 'partial') {
131
+ const missing = deps.missingProducts?.join(', ') ?? 'product markers';
132
+ return {
133
+ schemaVersion: 1,
134
+ adapter: 'mobile',
135
+ target: resolved,
136
+ decision: 'install',
137
+ reasonCode: 'deps-partial',
138
+ reasons: [`node_modules tree is incomplete (missing: ${missing}).`],
139
+ checks,
140
+ actions: installActions(resolved),
141
+ };
142
+ }
143
+
144
+ if (deps.status === 'stale') {
145
+ return {
146
+ schemaVersion: 1,
147
+ adapter: 'mobile',
148
+ target: resolved,
149
+ decision: 'install',
150
+ reasonCode: 'deps-stale',
151
+ reasons: ['package.json/yarn.lock changed since the recorded install baseline.'],
152
+ checks,
153
+ actions: installActions(resolved),
154
+ };
155
+ }
156
+
157
+ const metroLogAbs = resolveMetroLog(resolved, options.metroLog);
158
+ const metroLogText = metroLogAbs ? fs.readFileSync(metroLogAbs, 'utf8') : '';
159
+ const staleNativeModule = supersededErrorCapture(metroLogText, {
160
+ errorPattern: NATIVE_MODULE_STALE,
161
+ okPattern: BUNDLE_OK,
162
+ });
163
+ if (staleNativeModule) {
164
+ return {
165
+ schemaVersion: 1,
166
+ adapter: 'mobile',
167
+ target: resolved,
168
+ decision: 'launch',
169
+ reasonCode: 'native-module-stale',
170
+ reasons: [
171
+ `Installed dev client is missing native module "${staleNativeModule}" (JS deps are current). Rebuild the native app (yarn start:ios / yarn start:android or --preflight-mode rebuild-native).`,
172
+ 'Metro may report a successful bundle while the installed dev client binary predates the native Nitro module link.',
173
+ ],
174
+ checks,
175
+ actions: [
176
+ { id: 'rebuild-native-dev-client' },
177
+ ...launchActions(resolved, true),
178
+ ],
179
+ };
180
+ }
181
+
182
+ if (metroLog.status === 'errors') {
183
+ const depsSatisfied = deps.status === 'current';
184
+ if (
185
+ depsSatisfied
186
+ && (metroLog.reason === 'stale-bundle-error'
187
+ || metroLog.reason === 'bundle-error')
188
+ ) {
189
+ const excerpt = metroLog.excerpt ?? '';
190
+ const persistent = evaluatePersistentBundleError(resolved, excerpt);
191
+ if (persistent.blocked) {
192
+ return {
193
+ schemaVersion: 1,
194
+ adapter: 'mobile',
195
+ target: resolved,
196
+ decision: 'blocked',
197
+ reasonCode: 'bundle-error-persistent',
198
+ reasons: [
199
+ 'Metro bundle keeps failing with the same error after a cache-cleared relaunch was already suggested; fix the bundle error in app code before retrying recipe up.',
200
+ ...(excerpt ? [excerpt] : []),
201
+ ],
202
+ checks,
203
+ actions: [],
204
+ };
205
+ }
206
+ return {
207
+ schemaVersion: 1,
208
+ adapter: 'mobile',
209
+ target: resolved,
210
+ decision: 'launch',
211
+ reasonCode: metroLog.reason === 'stale-bundle-error' ? 'metro-stale-log' : 'bundle-errors-recoverable',
212
+ reasons: [
213
+ metroLog.reason === 'stale-bundle-error'
214
+ ? 'Metro log shows prior bundle failures but cited modules are installed; restart Metro with a cleared cache.'
215
+ : 'Metro bundle is failing with current deps; restart Metro (clear cache) before relaunching the dev client.',
216
+ ...(excerpt ? [excerpt] : []),
217
+ ],
218
+ checks,
219
+ actions: launchActions(resolved, true),
220
+ };
221
+ }
222
+ return {
223
+ schemaVersion: 1,
224
+ adapter: 'mobile',
225
+ target: resolved,
226
+ decision: 'install',
227
+ reasonCode: metroLog.reason === 'unresolved-module' ? 'deps-unresolved-module' : 'bundle-errors',
228
+ reasons: [
229
+ metroLog.reason === 'unresolved-module'
230
+ ? 'Metro cannot resolve modules that are absent from node_modules; run yarn setup.'
231
+ : 'Metro bundle is failing; reinstall deps or clear Metro cache before retrying.',
232
+ ...(metroLog.excerpt ? [metroLog.excerpt] : []),
233
+ ],
234
+ checks,
235
+ actions: installActions(resolved),
236
+ };
237
+ }
238
+
239
+ if (metro.status === 'down') {
240
+ return {
241
+ schemaVersion: 1,
242
+ adapter: 'mobile',
243
+ target: resolved,
244
+ decision: 'launch',
245
+ reasonCode: 'metro-down',
246
+ reasons: ['Metro is not reachable; start Metro and launch the dev client.'],
247
+ checks,
248
+ actions: launchActions(resolved),
249
+ };
250
+ }
251
+
252
+ if (metroLog.status === 'bundling' || metroLog.status === 'no-log') {
253
+ return {
254
+ schemaVersion: 1,
255
+ adapter: 'mobile',
256
+ target: resolved,
257
+ decision: 'launch',
258
+ reasonCode: metroLog.status === 'bundling' ? 'bundle-in-progress' : 'runtime-cold',
259
+ reasons: [
260
+ metroLog.status === 'bundling'
261
+ ? 'Metro bundle is still in progress; launch or wait for a successful bundle.'
262
+ : 'No successful Metro bundle recorded; start Metro and launch the dev client.',
263
+ ],
264
+ checks,
265
+ actions: launchActions(resolved),
266
+ };
267
+ }
268
+
269
+ if (metro.status === 'up' && metroLog.status === 'ok') {
270
+ return {
271
+ schemaVersion: 1,
272
+ adapter: 'mobile',
273
+ target: resolved,
274
+ decision: 'ready',
275
+ reasonCode: 'healthy',
276
+ reasons: ['Dependencies are current and Metro reports a successful bundle. Verify bridge before replay.'],
277
+ checks,
278
+ actions: [],
279
+ };
280
+ }
281
+
282
+ return {
283
+ schemaVersion: 1,
284
+ adapter: 'mobile',
285
+ target: resolved,
286
+ decision: 'launch',
287
+ reasonCode: 'runtime-unverified',
288
+ reasons: ['Dependencies are current; start or refresh Metro + dev client before replay.'],
289
+ checks,
290
+ actions: launchActions(resolved),
291
+ };
292
+ }
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ SOURCE="${BASH_SOURCE[0]}"
4
+ while [ -L "$SOURCE" ]; do
5
+ SOURCE_DIR="$(cd "$(dirname "$SOURCE")" && pwd -P)"
6
+ TARGET="$(readlink "$SOURCE")"
7
+ if [[ "$TARGET" == /* ]]; then
8
+ SOURCE="$TARGET"
9
+ else
10
+ SOURCE="$SOURCE_DIR/$TARGET"
11
+ fi
12
+ done
13
+ SCRIPT_DIR="$(cd "$(dirname "$SOURCE")" && pwd -P)"
14
+ # This script lives at <root>/orchestration/porcelain/; the runner root is two up.
15
+ RUNNER_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
16
+
17
+ ENSURE_DEPS="$RUNNER_DIR/orchestration/lib/ensure-runner-deps.sh"
18
+ if [ -f "$ENSURE_DEPS" ]; then
19
+ # shellcheck disable=SC1090
20
+ bash "$ENSURE_DEPS" "$RUNNER_DIR"
21
+ fi
22
+
23
+ if [ "${1:-}" = "mobile" ]; then
24
+ shift
25
+ exec "$RUNNER_DIR/bin/mm-recipe" "$@"
26
+ fi
27
+ if [ "${1:-}" = "extension" ]; then
28
+ shift
29
+ exec "$RUNNER_DIR/bin/mme-recipe" "$@"
30
+ fi
31
+
32
+ find_protocol_root() {
33
+ local start="$1"
34
+ local dir
35
+ dir="$(cd "$start" && pwd -P)"
36
+ while [ "$dir" != "/" ]; do
37
+ if [ -f "$dir/packages/recipe-harness/package.json" ] && [ -f "$dir/packages/protocol/package.json" ]; then
38
+ printf '%s\n' "$dir"
39
+ return 0
40
+ fi
41
+ if [ -f "$dir/farmslot/packages/recipe-harness/package.json" ] && [ -f "$dir/farmslot/packages/protocol/package.json" ]; then
42
+ printf '%s\n' "$dir/farmslot"
43
+ return 0
44
+ fi
45
+ dir="$(dirname "$dir")"
46
+ done
47
+ return 1
48
+ }
49
+
50
+ node_can_run_source_typescript() {
51
+ # Node refuses type-stripping for files under node_modules (ERR_UNSUPPORTED_NODE_
52
+ # MODULES_TYPE_STRIPPING). When this runner is installed as a dependency, cli.ts
53
+ # lives under node_modules and cannot run under plain node — a /tmp probe would be a
54
+ # false positive. Skip the node-source path in that case and fall through to dist/tsx.
55
+ case "$RUNNER_DIR/" in
56
+ */node_modules/*) return 1 ;;
57
+ esac
58
+ local probe_dir probe_file
59
+ probe_dir="$(mktemp -d "${TMPDIR:-/tmp}/metamask-recipe-node-ts.XXXXXX")"
60
+ probe_file="$probe_dir/probe.ts"
61
+ printf 'const answer: number = 42; if (answer !== 42) process.exit(1);\n' > "$probe_file"
62
+ if ! node --no-warnings "$probe_file" >/dev/null 2>&1; then
63
+ rm -rf "$probe_dir"
64
+ return 1
65
+ fi
66
+ rm -rf "$probe_dir"
67
+ }
68
+
69
+ if [ -f "$RUNNER_DIR/dist/cli.js" ]; then
70
+ exec node "$RUNNER_DIR/dist/cli.js" "$@"
71
+ fi
72
+
73
+ TSX_BIN="${TSX_BIN:-$RUNNER_DIR/node_modules/.bin/tsx}"
74
+ if [ ! -x "$TSX_BIN" ] && [ -x "$RUNNER_DIR/../../.bin/tsx" ]; then
75
+ TSX_BIN="$RUNNER_DIR/../../.bin/tsx"
76
+ fi
77
+ if [ ! -x "$TSX_BIN" ]; then
78
+ if [ -z "${FARMSLOT_ROOT:-}" ] && [ -f "$RUNNER_DIR/.farmslot-root" ]; then
79
+ FARMSLOT_ROOT="$(cat "$RUNNER_DIR/.farmslot-root")"
80
+ fi
81
+ if [ -z "${FARMSLOT_ROOT:-}" ]; then
82
+ FARMSLOT_ROOT="$(find_protocol_root "$RUNNER_DIR" || find_protocol_root "$PWD" || true)"
83
+ fi
84
+ if [ -n "${FARMSLOT_ROOT:-}" ]; then
85
+ FARMSLOT_ROOT="$(cd "$FARMSLOT_ROOT" && pwd -P)"
86
+ TSX_BIN="$FARMSLOT_ROOT/node_modules/.bin/tsx"
87
+ fi
88
+ fi
89
+
90
+ if node_can_run_source_typescript; then
91
+ exec node --no-warnings "$RUNNER_DIR/runner/src/cli.ts" "$@"
92
+ fi
93
+
94
+ if [ -x "$TSX_BIN" ]; then
95
+ exec "$TSX_BIN" "$RUNNER_DIR/runner/src/cli.ts" "$@"
96
+ fi
97
+
98
+ echo "metamask-recipe source checkout requires dist/cli.js, Node.js TypeScript type stripping, or a local tsx binary. Install dependencies normally, or set TSX_BIN/FARMSLOT_ROOT for local protocol co-development." >&2
99
+ exit 1