@deeeed/metamask-harness 0.2.0 → 0.3.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 (109) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +20 -17
  3. package/{orchestration → adapters}/core/cleanup.sh +2 -2
  4. package/{orchestration → adapters}/core/inject.sh +8 -8
  5. package/{orchestration → adapters}/extension/cleanup.mjs +1 -1
  6. package/{orchestration → adapters}/extension/console-tail.mjs +3 -0
  7. package/{orchestration → adapters}/extension/ensure-browser.sh +4 -4
  8. package/{orchestration → adapters}/extension/inject.mjs +27 -31
  9. package/{orchestration → adapters}/extension/launch.sh +4 -4
  10. package/{orchestration → adapters}/extension/live.sh +5 -5
  11. package/{orchestration → adapters}/extension/readiness.mjs +12 -2
  12. package/{orchestration → adapters}/extension/refresh-build.sh +1 -1
  13. package/{orchestration → adapters}/extension/seed-fixture.sh +1 -1
  14. package/{orchestration → adapters}/extension/start-watch.sh +7 -7
  15. package/{runner → adapters}/extension/verify.sh +11 -11
  16. package/{orchestration → adapters}/extension/wallet-fixture-state.cjs +2 -2
  17. package/{orchestration → adapters}/manifest.json +96 -96
  18. package/{library/actions → adapters}/mobile/bridge-runtime/cdp-bridge.cjs +5 -5
  19. package/{orchestration → adapters}/mobile/cleanup.sh +3 -3
  20. package/{orchestration → adapters}/mobile/inject.sh +22 -24
  21. package/adapters/mobile/open-device.sh +250 -0
  22. package/{orchestration/compat-overlays → adapters/mobile/overlay/compat}/README.md +13 -4
  23. package/adapters/mobile/prewarm-bundle.sh +95 -0
  24. package/adapters/mobile/start-metro.sh +228 -0
  25. package/{runner → adapters}/mobile/verify.sh +6 -6
  26. package/adapters/mobile/wait-for-bridge.sh +108 -0
  27. package/adapters/mobile/yarn-setup.sh +20 -0
  28. package/{orchestration/lib → adapters/shared}/cli-ux.sh +5 -9
  29. package/{orchestration/lib → adapters/shared}/log-tui.mjs +57 -1
  30. package/{orchestration/lib → adapters/shared}/open-debug.mjs +32 -10
  31. package/{orchestration/lib → adapters/shared}/resolve-farmslot-ports.sh +47 -0
  32. package/adapters/shared/sync-wallet-fixture.sh +78 -0
  33. package/bin/mm-harness +5 -6
  34. package/docs/CLI-SPEC.md +41 -2
  35. package/docs/architecture.md +69 -38
  36. package/docs/runtime-file-conventions.md +3 -3
  37. package/library/actions/core/perps/_controller.mjs +1 -1
  38. package/library/actions/extension/platform/cdp.mjs +2 -2
  39. package/library/actions/extension/wallet/ensure_unlocked.mjs +1 -1
  40. package/library/actions/mobile/platform/bridge.mjs +1 -1
  41. package/library/actions/mobile/wallet/ensure_unlocked.mjs +1 -1
  42. package/library/actions/mobile/wallet/setup.mjs +2 -2
  43. package/library/library.json +1 -1
  44. package/package.json +8 -8
  45. package/{orchestration → src/adapters}/extension/runtime-decision.ts +5 -5
  46. package/{orchestration → src/adapters}/extension/runtime.ts +1 -1
  47. package/src/adapters/mobile/prepare.ts +210 -0
  48. package/{orchestration → src/adapters}/mobile/runtime-decision.ts +11 -3
  49. package/{runner/src → src}/adapters.ts +3 -3
  50. package/{orchestration/lib/cli-color.mjs → src/cli-color.ts} +21 -13
  51. package/{orchestration/lib/cli-commands.mjs → src/cli-commands.ts} +52 -45
  52. package/{orchestration/lib/cli-version.mjs → src/cli-version.ts} +35 -17
  53. package/{runner/src → src}/cli.ts +25 -18
  54. package/src/commands/debug.ts +61 -0
  55. package/src/commands/fixtures.ts +196 -0
  56. package/{runner/src → src}/commands/launch.ts +185 -41
  57. package/src/commands/logs.ts +86 -0
  58. package/{runner/src → src}/commands/shared.ts +26 -26
  59. package/{runner/src → src}/harness.ts +97 -10
  60. package/src/index.ts +15 -0
  61. package/{runner/src → src}/live-adapter-contract.ts +1 -1
  62. package/{runner/src → src}/mm-harness-cli.ts +19 -3
  63. package/{runner/src → src}/paths.ts +2 -2
  64. package/{orchestration/lib/progress.mjs → src/progress.ts} +31 -21
  65. package/docs/extension-runtime-commands.md +0 -60
  66. package/docs/harness-cli.md +0 -43
  67. package/orchestration/compat-overlays/mobile/README.md +0 -13
  68. package/orchestration/doctor.mjs +0 -72
  69. package/orchestration/lib/cli-home.mjs +0 -354
  70. package/orchestration/lib/recipe-paths.mjs +0 -26
  71. package/orchestration/mobile/launch.sh +0 -137
  72. package/orchestration/mobile/live.sh +0 -125
  73. package/orchestration/porcelain/metamask-recipe +0 -99
  74. package/orchestration/porcelain/mm-recipe +0 -1591
  75. package/orchestration/porcelain/mme-recipe +0 -1181
  76. package/runner/src/commands/debug.ts +0 -44
  77. package/runner/src/commands/fixtures.ts +0 -99
  78. package/runner/src/commands/logs.ts +0 -60
  79. package/runner/src/index.ts +0 -6
  80. /package/{orchestration → adapters}/extension/launch-browser.cjs +0 -0
  81. /package/{orchestration → adapters}/extension/pin-remote-flags.cjs +0 -0
  82. /package/{orchestration → adapters}/extension/sidepanel-toggle.sh +0 -0
  83. /package/{orchestration → adapters}/extension/snapshot-dist.sh +0 -0
  84. /package/{library/actions → adapters}/mobile/bridge-runtime/lib/cdp-eval.cjs +0 -0
  85. /package/{library/actions → adapters}/mobile/bridge-runtime/lib/config.cjs +0 -0
  86. /package/{library/actions → adapters}/mobile/bridge-runtime/lib/issue-capture.cjs +0 -0
  87. /package/{library/actions → adapters}/mobile/bridge-runtime/lib/target-discovery.cjs +0 -0
  88. /package/{library/actions → adapters}/mobile/bridge-runtime/lib/ws-client.cjs +0 -0
  89. /package/{library/actions → adapters}/mobile/bridge-runtime/setup-wallet.sh +0 -0
  90. /package/{library/actions/mobile/app-overlay → adapters/mobile/overlay}/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -0
  91. /package/{library/actions/mobile/app-overlay → adapters/mobile/overlay}/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -0
  92. /package/{orchestration/compat-overlays/mobile → adapters/mobile/overlay/compat}/rn81-message-event-source.patch +0 -0
  93. /package/{orchestration/lib → adapters/shared}/activate-repo-node.sh +0 -0
  94. /package/{orchestration/lib → adapters/shared}/ensure-runner-deps.sh +0 -0
  95. /package/{orchestration/lib → adapters/shared}/harness-path.sh +0 -0
  96. /package/{orchestration/lib → adapters/shared}/hash-helpers.sh +0 -0
  97. /package/{orchestration/lib → adapters/shared}/json-field.sh +0 -0
  98. /package/{orchestration/lib → adapters/shared}/path-defaults.json +0 -0
  99. /package/{orchestration → src/adapters}/extension/ensure-ready.ts +0 -0
  100. /package/{orchestration → src/adapters}/extension/extension-id.ts +0 -0
  101. /package/{orchestration → src/adapters}/mobile/deps-markers.ts +0 -0
  102. /package/{runner/src → src}/completions-cache.ts +0 -0
  103. /package/{runner/src → src}/doctor.ts +0 -0
  104. /package/{runner/src → src}/heal-bounds.ts +0 -0
  105. /package/{runner/src → src}/manifest.ts +0 -0
  106. /package/{runner/src → src}/recording-target.ts +0 -0
  107. /package/{runner/src → src}/run-recording.ts +0 -0
  108. /package/{runner/src → src}/runner.ts +0 -0
  109. /package/{runner/src → src}/types.ts +0 -0
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env bash
2
+ # open-device — open the MetaMask Mobile dev client on a simulator or device.
3
+ #
4
+ # iOS: boots simulator if needed, writes EXDevMenuIsOnboardingFinished default,
5
+ # then opens via the expo-development-client deep link (disableOnboarding=1).
6
+ # Android: sets adb reverse for Metro port reachability, then opens via deep link.
7
+ # Preflight-mode guard: fast (teaching error if not installed), auto (install if missing),
8
+ # rebuild-native / clean (always rebuild), default (same as auto).
9
+ #
10
+ # Inputs:
11
+ # --platform ios|android (default ios)
12
+ # --target <metamask-mobile dir> (default $PWD)
13
+ # --simulator <udid|name|"booted"> iOS simulator (default: IOS_SIMULATOR env, else "booted")
14
+ # --adb-serial <serial> Android device serial (default: ADB_SERIAL env)
15
+ # --preflight-mode fast|auto|rebuild-native|clean (default: fast; env MOBILE_PREFLIGHT_MODE)
16
+ # Outputs:
17
+ # Progress on stderr; exit 0 pass; 1 open failed; 2 bad args.
18
+ #
19
+ # Single device op: open the dev client. Does not start Metro or install node_modules.
20
+ set -uo pipefail
21
+
22
+ PLATFORM="ios"
23
+ TARGET="$PWD"
24
+ SIMULATOR="${IOS_SIMULATOR:-${SIM_UDID:-booted}}"
25
+ ADB_SERIAL_ARG="${ADB_SERIAL:-${ANDROID_SERIAL:-}}"
26
+ PREFLIGHT_MODE="${MOBILE_PREFLIGHT_MODE:-fast}"
27
+ PORT="${WATCHER_PORT:-${METRO_PORT:-8081}}"
28
+
29
+ while [ "$#" -gt 0 ]; do
30
+ case "$1" in
31
+ --platform) PLATFORM="$2"; shift 2 ;;
32
+ --target) TARGET="$2"; shift 2 ;;
33
+ --simulator) SIMULATOR="$2"; shift 2 ;;
34
+ --adb-serial) ADB_SERIAL_ARG="$2"; shift 2 ;;
35
+ --preflight-mode) PREFLIGHT_MODE="$2"; shift 2 ;;
36
+ --port) PORT="$2"; shift 2 ;;
37
+ -h|--help)
38
+ printf 'Usage: open-device.sh [--platform ios|android] [--target <dir>]\n'
39
+ printf ' [--simulator <udid|name|booted>] [--adb-serial <serial>]\n'
40
+ printf ' [--preflight-mode fast|auto|rebuild-native|clean] [--port <n>]\n'
41
+ exit 0
42
+ ;;
43
+ *) printf 'open-device: unknown arg: %s\n' "$1" >&2; exit 2 ;;
44
+ esac
45
+ done
46
+
47
+ case "$PLATFORM" in
48
+ ios|android) ;;
49
+ *) printf 'open-device: --platform must be ios or android (got: %s)\n' "$PLATFORM" >&2; exit 2 ;;
50
+ esac
51
+
52
+ TARGET="$(cd "$TARGET" && pwd)"
53
+
54
+ # --- iOS helpers --------------------------------------------------------------
55
+
56
+ ios_app_installed() {
57
+ local sim_target="$1" bundle_id="$2"
58
+ xcrun simctl get_app_container "$sim_target" "$bundle_id" app >/dev/null 2>&1
59
+ }
60
+
61
+ run_ios_native_build() {
62
+ local sim_target="$1" reason="$2"
63
+ printf '%s; installing with yarn start:ios (WATCHER_PORT=%s)\n' "$reason" "$PORT" >&2
64
+ (cd "$TARGET" && WATCHER_PORT="$PORT" METRO_PORT="$PORT" IOS_SIMULATOR="$sim_target" EXPO_NO_TYPESCRIPT_SETUP=1 yarn start:ios 2>&1)
65
+ }
66
+
67
+ ensure_ios_app_for_mode() {
68
+ local sim_target="$1" bundle_id="$2" mode="$3"
69
+ case "$mode" in
70
+ fast)
71
+ if ios_app_installed "$sim_target" "$bundle_id"; then return 0; fi
72
+ printf >&2 'open-device: fast mode requires an installed iOS dev client (%s) on %s.\n' \
73
+ "$bundle_id" "$sim_target"
74
+ printf >&2 'Fast mode only prewarms the bundle and opens the existing client.\n'
75
+ printf >&2 'Next: use --preflight-mode auto to install if missing.\n'
76
+ return 1
77
+ ;;
78
+ auto|default)
79
+ if ios_app_installed "$sim_target" "$bundle_id"; then return 0; fi
80
+ run_ios_native_build "$sim_target" "iOS dev client ${bundle_id} not installed on ${sim_target}"
81
+ ;;
82
+ rebuild-native|clean)
83
+ run_ios_native_build "$sim_target" "Rebuilding iOS dev client ${bundle_id} (--preflight-mode ${mode})"
84
+ ;;
85
+ *)
86
+ printf 'open-device: unknown --preflight-mode: %s\n' "$mode" >&2; return 2 ;;
87
+ esac
88
+ }
89
+
90
+ sim_udid_for_target() {
91
+ local target="$1"
92
+ # Already a UDID
93
+ if printf '%s' "$target" | grep -Eq '^[0-9A-Fa-f-]{36}$'; then
94
+ printf '%s\n' "$target"; return 0
95
+ fi
96
+ xcrun simctl list devices available 2>/dev/null \
97
+ | sed -n "s/.*${target} (\([0-9A-Fa-f-]*\)) (.*/\1/p" | head -1
98
+ }
99
+
100
+ boot_simulator_if_needed() {
101
+ local target="$1" state=""
102
+ [ "$target" != "booted" ] || return 0
103
+ local line=""
104
+ line="$(xcrun simctl list devices available 2>/dev/null | grep -F "${target} (" | head -1 || true)"
105
+ case "$line" in *"(Booted)"*) state="Booted" ;; *"(Shutdown)"*) state="Shutdown" ;; esac
106
+ [ "$state" = "Booted" ] && return 0
107
+ printf 'Booting iOS simulator %s%s\n' "$target" "${state:+ ($state)}" >&2
108
+ xcrun simctl boot "$target" 2>/dev/null || true
109
+ xcrun simctl bootstatus "$target" -b >/dev/null
110
+ }
111
+
112
+ show_simulator() {
113
+ local target="$1" udid=""
114
+ if [ "$target" = "booted" ]; then
115
+ printf 'Opening Simulator UI\n' >&2
116
+ open -a Simulator >/dev/null 2>&1 || true
117
+ else
118
+ udid="$(sim_udid_for_target "$target")"
119
+ if [ -n "$udid" ]; then
120
+ open -a Simulator --args -CurrentDeviceUDID "$udid" >/dev/null 2>&1 || true
121
+ else
122
+ open -a Simulator >/dev/null 2>&1 || true
123
+ fi
124
+ fi
125
+ osascript -e 'tell application "Simulator" to activate' >/dev/null 2>&1 || true
126
+ }
127
+
128
+ # --- Android helpers ----------------------------------------------------------
129
+
130
+ android_app_installed() {
131
+ local pkg="$1"
132
+ shift
133
+ adb "$@" shell pm list packages 2>/dev/null | grep -q "package:${pkg}"
134
+ }
135
+
136
+ run_android_native_build() {
137
+ local reason="$1"
138
+ printf '%s; installing with yarn start:android (WATCHER_PORT=%s)\n' "$reason" "$PORT" >&2
139
+ (cd "$TARGET" && WATCHER_PORT="$PORT" yarn start:android 2>&1)
140
+ }
141
+
142
+ ensure_android_app_for_mode() {
143
+ local pkg="$1" mode="$2"
144
+ shift 2
145
+ case "$mode" in
146
+ fast)
147
+ if android_app_installed "$pkg" "$@"; then return 0; fi
148
+ printf >&2 'open-device: fast mode requires an installed Android dev client (%s).\n' "$pkg"
149
+ printf >&2 'Next: use --preflight-mode auto to install if missing.\n'
150
+ return 1
151
+ ;;
152
+ auto|default)
153
+ if android_app_installed "$pkg" "$@"; then return 0; fi
154
+ run_android_native_build "Android dev client ${pkg} not installed"
155
+ ;;
156
+ rebuild-native|clean)
157
+ run_android_native_build "Rebuilding Android dev client ${pkg} (--preflight-mode ${mode})"
158
+ ;;
159
+ *)
160
+ printf 'open-device: unknown --preflight-mode: %s\n' "$mode" >&2; return 2 ;;
161
+ esac
162
+ }
163
+
164
+ # URL-encode using python3 (same as baseline).
165
+ urlencode() {
166
+ python3 - "$1" <<'PY'
167
+ import sys, urllib.parse
168
+ print(urllib.parse.quote(sys.argv[1], safe=''))
169
+ PY
170
+ }
171
+
172
+ # --- dispatch -----------------------------------------------------------------
173
+
174
+ BUNDLE_IDS=("${IOS_BUNDLE_ID:-io.metamask.MetaMask}" "io.metamask" "io.metamask.flask" "io.metamask.qa")
175
+ ANDROID_PKGS=("${ANDROID_PACKAGE_ID:-io.metamask}" "io.metamask.flask" "io.metamask.qa")
176
+
177
+ if [ "$PLATFORM" = "ios" ]; then
178
+ SIM_TARGET="$SIMULATOR"
179
+
180
+ boot_simulator_if_needed "$SIM_TARGET"
181
+ show_simulator "$SIM_TARGET"
182
+
183
+ LAUNCHED=false
184
+ for BUNDLE_ID in "${BUNDLE_IDS[@]}"; do
185
+ ensure_ios_app_for_mode "$SIM_TARGET" "$BUNDLE_ID" "$PREFLIGHT_MODE" || continue
186
+
187
+ # Dismiss Expo dev-menu onboarding for clean headless runs.
188
+ xcrun simctl spawn "$SIM_TARGET" defaults write "$BUNDLE_ID" \
189
+ EXDevMenuIsOnboardingFinished -bool YES 2>/dev/null || true
190
+
191
+ # Open via expo-development-client deep link so the app connects to our Metro.
192
+ ENCODED_URL="$(urlencode "http://localhost:${PORT}?disableOnboarding=1" 2>/dev/null \
193
+ || printf 'http%%3A%%2F%%2Flocalhost%%3A%s%%3FdisableOnboarding%%3D1' "$PORT")"
194
+ DEEP_LINK="expo-metamask://expo-development-client/?url=${ENCODED_URL}"
195
+
196
+ printf 'Launching iOS dev client %s on %s\n' "$BUNDLE_ID" "$SIM_TARGET" >&2
197
+ if xcrun simctl openurl "$SIM_TARGET" "$DEEP_LINK" 2>/dev/null; then
198
+ LAUNCHED=true
199
+ printf 'open-device: iOS dev client opened (%s on %s)\n' "$BUNDLE_ID" "$SIM_TARGET" >&2
200
+ break
201
+ fi
202
+ # Fallback: plain launch (older Expo / non-development-client build).
203
+ if xcrun simctl launch "$SIM_TARGET" "$BUNDLE_ID" 2>/dev/null; then
204
+ LAUNCHED=true
205
+ printf 'open-device: iOS dev client launched (fallback) (%s on %s)\n' "$BUNDLE_ID" "$SIM_TARGET" >&2
206
+ break
207
+ fi
208
+ done
209
+
210
+ if [ "$LAUNCHED" = false ]; then
211
+ printf 'open-device: no MetaMask bundle found on simulator %s\n' "$SIM_TARGET" >&2
212
+ printf ' tried: %s\n' "${BUNDLE_IDS[*]}" >&2
213
+ exit 1
214
+ fi
215
+
216
+ else
217
+ ADB_ARGS=()
218
+ [ -n "$ADB_SERIAL_ARG" ] && ADB_ARGS+=("-s" "$ADB_SERIAL_ARG")
219
+
220
+ # Reverse-tunnel Metro port so the device can reach localhost:PORT.
221
+ adb "${ADB_ARGS[@]}" reverse "tcp:${PORT}" "tcp:${PORT}" >/dev/null 2>&1 || \
222
+ printf 'open-device: adb reverse failed (non-fatal — device may already be reachable)\n' >&2
223
+
224
+ LAUNCHED=false
225
+ for PKG in "${ANDROID_PKGS[@]}"; do
226
+ ensure_android_app_for_mode "$PKG" "$PREFLIGHT_MODE" "${ADB_ARGS[@]}" || continue
227
+
228
+ ENCODED_URL="$(urlencode "http://localhost:${PORT}?disableOnboarding=1" 2>/dev/null \
229
+ || printf 'http%%3A%%2F%%2Flocalhost%%3A%s%%3FdisableOnboarding%%3D1' "$PORT")"
230
+ DEEP_LINK="expo-metamask://expo-development-client/?url=${ENCODED_URL}"
231
+
232
+ printf 'Launching Android dev client %s\n' "$PKG" >&2
233
+ if adb "${ADB_ARGS[@]}" shell am start \
234
+ -a android.intent.action.VIEW \
235
+ -d "$DEEP_LINK" >/dev/null 2>&1; then
236
+ LAUNCHED=true
237
+ printf 'open-device: Android dev client opened (%s)\n' "$PKG" >&2
238
+ break
239
+ fi
240
+ done
241
+
242
+ if [ "$LAUNCHED" = false ]; then
243
+ printf 'open-device: no MetaMask package found on Android device %s\n' \
244
+ "${ADB_SERIAL_ARG:-<default>}" >&2
245
+ printf ' tried: %s\n' "${ANDROID_PKGS[*]}" >&2
246
+ exit 1
247
+ fi
248
+ fi
249
+
250
+ printf ' Next: mm-harness logs # tail Metro\n' >&2
@@ -7,13 +7,22 @@ up empty here by design: the feature's purpose is non-current checkouts.
7
7
 
8
8
  - Purpose: make historical product checkouts bootable/bridgeable under the
9
9
  current local toolchain so Recipe v1 evals can replay against them.
10
- - When it applies: mobile checkouts predating React Native 0.81 polyfill
11
- fixes (see mobile/README.md for the per-patch details).
10
+ - When it applies: mobile checkouts predating React Native 0.81 polyfill fixes.
12
11
  - How it is applied: MANUALLY (no automatic apply logic in this repo).
13
12
  The operator or the `/recipe-harness` skill applies the patch reversibly
14
13
  before a historical rebuild (`git apply <patch>` in the product checkout,
15
14
  `git apply -R` to remove) and records the overlay path in validation
16
- evidence. `orchestration/mobile/inject.sh` does NOT apply these.
15
+ evidence. `adapters/mobile/inject.sh` does NOT apply these.
17
16
 
18
- Listed in orchestration/manifest.json (kind: compat-overlay) so the doctor
17
+ Kept in the external runner repository, not Farmslot root scripts, so a
18
+ `/recipe-harness` skill can apply the same reversible overlay before a
19
+ historical rebuild and record the overlay path in validation evidence.
20
+
21
+ Listed in adapters/manifest.json (kind: compat-overlay) so the doctor
19
22
  verifies the surface and this directory is never flagged as unused again.
23
+
24
+ ## Patches
25
+
26
+ - `rn81-message-event-source.patch`: adds a read-only `MessageEvent.prototype.source`
27
+ getter for React Native 0.81 historical checkouts whose bundled polyfill omits
28
+ it while CDP websocket clients expect browser-compatible `MessageEvent` shape.
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env bash
2
+ # prewarm-bundle — curl the Metro bundle URL before the dev client opens.
3
+ #
4
+ # Triggers Metro to compile and cache the bundle so the app connects instantly.
5
+ # Streams progress from the Metro log every 30s until the bundle curl finishes
6
+ # or MOBILE_BUNDLE_PREWARM_TIMEOUT seconds elapse.
7
+ # Set MOBILE_BUNDLE_PREWARM=0 to skip.
8
+ #
9
+ # Inputs:
10
+ # --platform ios|android (default ios)
11
+ # --target <metamask-mobile dir> (default $PWD)
12
+ # --port <number> (default WATCHER_PORT env, else METRO_PORT, else 8081)
13
+ # Outputs:
14
+ # Progress on stderr; exit 0 bundle ready; 1 timeout or curl error; 2 bad args.
15
+ #
16
+ # Single device op: prewarm the bundle. Does not start Metro or open the dev client.
17
+ set -uo pipefail
18
+
19
+ PLATFORM="ios"
20
+ TARGET="$PWD"
21
+ PORT="${WATCHER_PORT:-${METRO_PORT:-8081}}"
22
+
23
+ while [ "$#" -gt 0 ]; do
24
+ case "$1" in
25
+ --platform) PLATFORM="$2"; shift 2 ;;
26
+ --target) TARGET="$2"; shift 2 ;;
27
+ --port) PORT="$2"; shift 2 ;;
28
+ -h|--help)
29
+ printf 'Usage: prewarm-bundle.sh [--platform ios|android] [--target <dir>] [--port <n>]\n'
30
+ exit 0
31
+ ;;
32
+ *) printf 'prewarm-bundle: unknown arg: %s\n' "$1" >&2; exit 2 ;;
33
+ esac
34
+ done
35
+
36
+ case "$PLATFORM" in
37
+ ios|android) ;;
38
+ *) printf 'prewarm-bundle: --platform must be ios or android (got: %s)\n' "$PLATFORM" >&2; exit 2 ;;
39
+ esac
40
+
41
+ TARGET="$(cd "$TARGET" && pwd)"
42
+ LOG_DIR="$TARGET/temp/recipe/runtime"
43
+ mkdir -p "$LOG_DIR"
44
+ METRO_LOG="$LOG_DIR/metro.log"
45
+
46
+ if [ "${MOBILE_BUNDLE_PREWARM:-1}" = "0" ]; then
47
+ printf 'Skipping %s bundle prewarm (MOBILE_BUNDLE_PREWARM=0)\n' "$PLATFORM" >&2
48
+ exit 0
49
+ fi
50
+
51
+ TIMEOUT="${MOBILE_BUNDLE_PREWARM_TIMEOUT:-900}"
52
+
53
+ BUNDLE_URL="http://localhost:${PORT}/index.bundle?platform=${PLATFORM}&dev=true&hot=false&lazy=true&transform.engine=hermes&transform.bytecode=1&transform.routerRoot=app&unstable_transformProfile=hermes-stable"
54
+
55
+ printf 'Prewarming %s bundle before launching dev client (set MOBILE_BUNDLE_PREWARM=0 to skip)\n' "$PLATFORM" >&2
56
+ printf ' url: %s\n' "$BUNDLE_URL" >&2
57
+
58
+ curl -sfL --connect-timeout 5 --max-time "$TIMEOUT" -o /dev/null "$BUNDLE_URL" &
59
+ BUNDLE_PID=$!
60
+
61
+ ELAPSED=0
62
+ LAST_PROGRESS=""
63
+ while kill -0 "$BUNDLE_PID" 2>/dev/null; do
64
+ PROGRESS=""
65
+ if [ -f "$METRO_LOG" ]; then
66
+ PROGRESS="$(grep -E '^[[:space:]]*(iOS|Android).*index\.(js|tsx?).*%|Bundl(ed|ing)|Finished' "$METRO_LOG" 2>/dev/null | tail -1 | tr -d '\r' || true)"
67
+ fi
68
+ if [ -n "$PROGRESS" ] && [ "$PROGRESS" != "$LAST_PROGRESS" ]; then
69
+ printf 'Bundle progress: %s\n' "$PROGRESS" >&2
70
+ LAST_PROGRESS="$PROGRESS"
71
+ elif [ $((ELAPSED % 30)) -eq 0 ] && [ "$ELAPSED" -gt 0 ]; then
72
+ printf 'Waiting for %s bundle (%ss/%ss)%s\n' "$PLATFORM" "$ELAPSED" "$TIMEOUT" \
73
+ "${LAST_PROGRESS:+ — $LAST_PROGRESS}" >&2
74
+ fi
75
+ if [ "$ELAPSED" -ge "$TIMEOUT" ]; then
76
+ kill "$BUNDLE_PID" 2>/dev/null || true
77
+ wait "$BUNDLE_PID" 2>/dev/null || true
78
+ printf 'prewarm-bundle: %s bundle did not become ready before timeout (%ss)\n' "$PLATFORM" "$TIMEOUT" >&2
79
+ [ -f "$METRO_LOG" ] && tail -80 "$METRO_LOG" >&2 || true
80
+ printf ' Next: check %s for errors or reduce MOBILE_BUNDLE_PREWARM_TIMEOUT\n' "$METRO_LOG" >&2
81
+ exit 1
82
+ fi
83
+ sleep 3
84
+ ELAPSED=$((ELAPSED + 3))
85
+ done
86
+
87
+ if wait "$BUNDLE_PID"; then
88
+ printf '%s bundle ready\n' "$PLATFORM" >&2
89
+ exit 0
90
+ fi
91
+
92
+ printf 'prewarm-bundle: %s bundle curl failed\n' "$PLATFORM" >&2
93
+ [ -f "$METRO_LOG" ] && tail -80 "$METRO_LOG" >&2 || true
94
+ printf ' Next: check %s for Metro errors\n' "$METRO_LOG" >&2
95
+ exit 1
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env bash
2
+ # start-metro — ensure the Metro bundler is running for this MetaMask Mobile checkout.
3
+ #
4
+ # Checks if Metro is already running and owned by this checkout with required env vars.
5
+ # Clears stale or foreign listeners before starting. Uses yarn expo start --port (not
6
+ # yarn start), sources .js.env/.env/.env.local, sets EXPO_NO_TYPESCRIPT_SETUP=1, and
7
+ # caps METRO_MAX_WORKERS to 6 (or the physical CPU count).
8
+ #
9
+ # Inputs:
10
+ # --target <metamask-mobile dir> (default $PWD)
11
+ # --port <number> (default: WATCHER_PORT env, else METRO_PORT, else 8081)
12
+ # --clear reset Metro bundler cache before starting
13
+ # Outputs:
14
+ # Progress on stderr; exit 0 Metro ready; 1 failed to start; 2 bad args.
15
+ #
16
+ # Single device op: ensure Metro is running. Does not open the dev client or install deps.
17
+ set -uo pipefail
18
+
19
+ TARGET="$PWD"
20
+ PORT="${WATCHER_PORT:-${METRO_PORT:-8081}}"
21
+ CLEAR=false
22
+
23
+ while [ "$#" -gt 0 ]; do
24
+ case "$1" in
25
+ --target) TARGET="$2"; shift 2 ;;
26
+ --port) PORT="$2"; shift 2 ;;
27
+ --clear) CLEAR=true; shift ;;
28
+ -h|--help)
29
+ printf 'Usage: start-metro.sh [--target <dir>] [--port <port>] [--clear]\n'
30
+ printf ' --target MetaMask Mobile checkout directory\n'
31
+ printf ' --port Metro port (default: WATCHER_PORT env or 8081)\n'
32
+ printf ' --clear restart with cleared cache\n'
33
+ exit 0
34
+ ;;
35
+ *) printf 'start-metro: unknown arg: %s\n' "$1" >&2; exit 2 ;;
36
+ esac
37
+ done
38
+
39
+ TARGET="$(cd "$TARGET" && pwd)"
40
+ LOG_DIR="$TARGET/temp/recipe/runtime"
41
+ mkdir -p "$LOG_DIR"
42
+ LOG_FILE="$LOG_DIR/metro.log"
43
+ PID_FILE="$LOG_DIR/metro.pid"
44
+
45
+ # --- helpers ------------------------------------------------------------------
46
+
47
+ metro_ready() {
48
+ curl -sf --max-time 2 "http://localhost:${PORT}/status" 2>/dev/null | grep -q 'packager-status:running'
49
+ }
50
+
51
+ metro_listener_pids() {
52
+ lsof -nP -iTCP:"${PORT}" -sTCP:LISTEN -t 2>/dev/null | tr '\n' ' ' || true
53
+ }
54
+
55
+ metro_listener_valid() {
56
+ local pids pid command rv
57
+ pids="$(metro_listener_pids)"
58
+ [ -n "$(printf '%s' "$pids" | tr -d ' ')" ] || return 1
59
+ rv=0
60
+ for pid in $pids; do
61
+ command="$(ps eww -p "$pid" -o command= 2>/dev/null || true)"
62
+ case "$command" in
63
+ *"$TARGET"*) ;;
64
+ *)
65
+ printf 'Metro on port %s not owned by this checkout (pid %s); restarting\n' "$PORT" "$pid" >&2
66
+ rv=1; break
67
+ ;;
68
+ esac
69
+ for required_var in ${MOBILE_METRO_REQUIRED_ENV:-MM_INFURA_PROJECT_ID}; do
70
+ if ! printf '%s\n' "$command" | grep -Eq "(^|[[:space:]])${required_var}="; then
71
+ printf 'Metro on port %s missing required env %s (pid %s); restarting\n' "$PORT" "$required_var" "$pid" >&2
72
+ rv=1; break 2
73
+ fi
74
+ done
75
+ done
76
+ return "$rv"
77
+ }
78
+
79
+ stop_metro_listener() {
80
+ local pids i
81
+ pids="$(metro_listener_pids)"
82
+ [ -n "$(printf '%s' "$pids" | tr -d ' ')" ] || return 0
83
+ printf 'Stopping Metro listener on port %s: %s\n' "$PORT" "$pids" >&2
84
+ # shellcheck disable=SC2086
85
+ kill $pids 2>/dev/null || true
86
+ for i in $(seq 1 20); do
87
+ pids="$(metro_listener_pids)"
88
+ [ -z "$(printf '%s' "$pids" | tr -d ' ')" ] && return 0
89
+ sleep 0.25
90
+ done
91
+ printf 'Metro listener still held; force-killing: %s\n' "$pids" >&2
92
+ # shellcheck disable=SC2086
93
+ kill -KILL $pids 2>/dev/null || true
94
+ for i in $(seq 1 20); do
95
+ pids="$(metro_listener_pids)"
96
+ [ -z "$(printf '%s' "$pids" | tr -d ' ')" ] && return 0
97
+ sleep 0.25
98
+ done
99
+ printf 'start-metro: port %s still occupied after force-kill: %s\n' "$PORT" "$pids" >&2
100
+ return 1
101
+ }
102
+
103
+ default_metro_workers() {
104
+ local cores=""
105
+ if command -v sysctl >/dev/null 2>&1; then
106
+ cores="$(sysctl -n hw.perflevel0.physicalcpu 2>/dev/null || sysctl -n hw.physicalcpu 2>/dev/null || true)"
107
+ fi
108
+ if printf '%s' "${cores:-}" | grep -Eq '^[0-9]+$' && [ "${cores:-0}" -gt 0 ]; then
109
+ [ "${cores:-4}" -gt 6 ] && cores=6
110
+ printf '%s\n' "$cores"
111
+ else
112
+ printf '4\n'
113
+ fi
114
+ }
115
+
116
+ start_viewer_window() {
117
+ local metro_log="$1"
118
+ command -v tmux >/dev/null 2>&1 || return 0
119
+ local window="metro-${PORT}"
120
+ local metro_tmux="$LOG_DIR/metro.tmux"
121
+ local session=""
122
+ if [ -n "${slot:-}" ] && tmux has-session -t "=mm-${slot}" 2>/dev/null; then
123
+ session="mm-${slot}"
124
+ fi
125
+ if [ -z "$session" ]; then
126
+ session="$(tmux display-message -p '#S' 2>/dev/null || true)"
127
+ fi
128
+ [ -n "$session" ] || return 0
129
+ tmux kill-window -t "${session}:${window}" >/dev/null 2>&1 || true
130
+ tmux new-window -d -t "$session" -n "$window" "exec tail -n +1 -F $(printf '%q' "$metro_log")"
131
+ printf '%s:%s\n' "$session" "$window" > "$metro_tmux"
132
+ printf 'Metro log viewer → tmux %s:%s\n' "$session" "$window" >&2
133
+ }
134
+
135
+ # --- main ---------------------------------------------------------------------
136
+
137
+ # Decide whether to (re)start or skip.
138
+ if metro_ready; then
139
+ if [ "$CLEAR" = true ]; then
140
+ printf 'Restarting Metro on port %s with cleared cache\n' "$PORT" >&2
141
+ stop_metro_listener || exit 1
142
+ rm -f "$PID_FILE"
143
+ elif metro_listener_valid; then
144
+ printf 'Metro already running and valid on port %s\n' "$PORT" >&2
145
+ exit 0
146
+ else
147
+ stop_metro_listener || exit 1
148
+ rm -f "$PID_FILE"
149
+ fi
150
+ fi
151
+
152
+ stop_metro_listener || exit 1
153
+
154
+ METRO_WORKERS="${METRO_MAX_WORKERS:-$(default_metro_workers)}"
155
+ CLEAR_SUFFIX=""
156
+ [ "$CLEAR" = true ] && CLEAR_SUFFIX=" --clear"
157
+
158
+ printf 'Starting Metro on port %s (workers=%s%s)\n' \
159
+ "$PORT" "$METRO_WORKERS" "${CLEAR:+, clear}" >&2
160
+ printf '(Full log: %s — recipe logs | recipe logs --full)\n' "$LOG_FILE" >&2
161
+
162
+ # Metro runs detached, writing to the log. The tmux window is a read-only tail.
163
+ (
164
+ cd "$TARGET"
165
+ : > "$LOG_FILE"
166
+ # Source product env files (provides MM_INFURA_PROJECT_ID and friends).
167
+ set -a
168
+ # shellcheck disable=SC1091
169
+ [ ! -f .js.env ] || . ./.js.env
170
+ # shellcheck disable=SC1091
171
+ [ ! -f .env ] || . ./.env
172
+ # shellcheck disable=SC1091
173
+ [ ! -f .env.local ] || . ./.env.local
174
+ set +a
175
+ export EXPO_NO_TYPESCRIPT_SETUP=1
176
+ export WATCHER_PORT="${PORT}" METRO_PORT="${PORT}"
177
+ export METRO_MAX_WORKERS="${METRO_WORKERS}"
178
+ # shellcheck disable=SC2094
179
+ bash -c "exec yarn expo start --port ${PORT}${CLEAR_SUFFIX}" \
180
+ </dev/null >> "$LOG_FILE" 2>&1 &
181
+ echo "$!" > "$PID_FILE"
182
+ )
183
+ # Disown the background process so it outlives this script.
184
+ disown "$(cat "$PID_FILE" 2>/dev/null || true)" 2>/dev/null || true
185
+
186
+ start_viewer_window "$LOG_FILE"
187
+
188
+ # Attach a log-tui watcher for compact event display (best-effort; non-blocking).
189
+ LOG_TUI="$(dirname "$0")/../shared/log-tui.mjs"
190
+ TAIL_PID=""
191
+ if [ -f "$LOG_TUI" ] && command -v node >/dev/null 2>&1 \
192
+ && [ "${METAMASK_RECIPE_AUTO_HUD:-1}" != "0" ]; then
193
+ node "$LOG_TUI" watch \
194
+ --log "$LOG_FILE" \
195
+ --events "${RECIPE_LOG_EVENTS:-12}" \
196
+ --mode compact \
197
+ --label "Metro bundler" \
198
+ --success-pattern 'Dev server ready' &
199
+ TAIL_PID=$!
200
+ fi
201
+
202
+ # Ready-poll: 90 × 1s = 90 s timeout.
203
+ READY=false
204
+ for _i in $(seq 1 90); do
205
+ if metro_ready; then
206
+ READY=true
207
+ break
208
+ fi
209
+ sleep 1
210
+ done
211
+
212
+ [ -n "$TAIL_PID" ] && { kill "$TAIL_PID" 2>/dev/null; wait "$TAIL_PID" 2>/dev/null || true; }
213
+
214
+ if [ "$READY" = true ]; then
215
+ printf 'Metro ready on port %s\n' "$PORT" >&2
216
+ printf ' Next: mm-harness logs (tail Metro)\n' >&2
217
+ exit 0
218
+ fi
219
+
220
+ # Failure: dump log events for diagnosis.
221
+ if [ -f "$LOG_TUI" ] && command -v node >/dev/null 2>&1; then
222
+ node "$LOG_TUI" events --log "$LOG_FILE" --events 20 >&2 || true
223
+ else
224
+ tail -40 "$LOG_FILE" >&2 || true
225
+ fi
226
+ printf 'start-metro: Metro did not become ready on port %s within 90s\n' "$PORT" >&2
227
+ printf ' Next: check %s for errors, or run mm-harness logs\n' "$LOG_FILE" >&2
228
+ exit 1
@@ -58,12 +58,12 @@ esac
58
58
 
59
59
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
60
60
  # shellcheck disable=SC1091
61
- for _hp in "$SCRIPT_DIR/lib/harness-path.sh" "$SCRIPT_DIR/../../orchestration/lib/harness-path.sh" "$SCRIPT_DIR/../lib/harness-path.sh"; do
61
+ for _hp in "$SCRIPT_DIR/lib/harness-path.sh" "$SCRIPT_DIR/../../adapters/shared/harness-path.sh" "$SCRIPT_DIR/../shared/harness-path.sh"; do
62
62
  [ -f "$_hp" ] && { . "$_hp"; break; }
63
63
  done
64
64
  unset _hp
65
65
  if ! command -v harness_root >/dev/null 2>&1; then
66
- echo "mobile verify: shared lib orchestration/lib/harness-path.sh not found; reinstall the runner." >&2
66
+ echo "mobile verify: shared lib adapters/shared/harness-path.sh not found; reinstall the runner." >&2
67
67
  exit 1
68
68
  fi
69
69
  TARGET="$(cd "$TARGET" && pwd)"
@@ -71,7 +71,7 @@ RUNTIME_DIR="$(recipe_runtime_dir)"
71
71
  HARNESS_ROOT="$(harness_root)"
72
72
  HARNESS_REL="$HARNESS_ROOT/mobile"
73
73
  HARNESS_DIR="$(harness_dir "$TARGET" mobile)"
74
- RUNNER_BIN="$HARNESS_DIR/runner/bin/metamask-recipe"
74
+ RUNNER_BIN="$HARNESS_DIR/runner/bin/mm-harness"
75
75
  ARTIFACTS="${ARTIFACTS:-$HARNESS_DIR/verify/$(date -u +%Y%m%dT%H%M%SZ)}"
76
76
  mkdir -p "$ARTIFACTS/logs"
77
77
 
@@ -221,7 +221,7 @@ check_file() {
221
221
 
222
222
  check_file "$HARNESS_REL/manifest.json"
223
223
  check_file "$HARNESS_REL/action-manifest.json"
224
- check_file "$HARNESS_REL/runner/bin/metamask-recipe"
224
+ check_file "$HARNESS_REL/runner/bin/mm-harness"
225
225
  check_file "package.json"
226
226
  check_file "app/dev-tools/AgenticService/AgenticService.ts"
227
227
 
@@ -263,7 +263,7 @@ if (!runnerDir) {
263
263
  process.stdout.write(JSON.stringify({ name, status: 'warn', detail: 'runner source unavailable; overlay freshness compare skipped' }));
264
264
  process.exit(0);
265
265
  }
266
- const overlayDir = path.join(runnerDir, 'library', 'actions', 'mobile', 'app-overlay', 'app', 'core', 'AgenticService');
266
+ const overlayDir = path.join(runnerDir, 'adapters', 'mobile', 'overlay', 'app', 'core', 'AgenticService');
267
267
  let checked = 0;
268
268
  const drifted = [];
269
269
  const missing = [];
@@ -289,7 +289,7 @@ if (checked === 0) {
289
289
  const parts = [];
290
290
  if (drifted.length) parts.push('behind runner: ' + drifted.join(', '));
291
291
  if (missing.length) parts.push('absent in repo: ' + missing.join(', '));
292
- process.stdout.write(JSON.stringify({ name, status: 'warn', detail: parts.join('; ') + ' — rerun metamask-recipe install --force-overlay or recipe sync to refresh the in-repo HUD/AgenticService from the runner' }));
292
+ process.stdout.write(JSON.stringify({ name, status: 'warn', detail: parts.join('; ') + ' — rerun mm-harness install --force-overlay or recipe sync to refresh the in-repo HUD/AgenticService from the runner' }));
293
293
  } else {
294
294
  process.stdout.write(JSON.stringify({ name, status: 'pass' }));
295
295
  }