@deeeed/metamask-harness 0.79.0 → 0.80.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 (210) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +1 -1
  3. package/adapters/extension/ensure-browser.sh +2 -2
  4. package/adapters/extension/inject.mjs +0 -1
  5. package/adapters/extension/launch-browser.cjs +39 -613
  6. package/adapters/extension/readiness.mjs +2 -5
  7. package/adapters/extension/reattach.sh +2 -2
  8. package/adapters/extension/stamp-runtime-title.cjs +1 -1
  9. package/adapters/manifest.json +2 -34
  10. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +57 -1358
  11. package/adapters/mobile/bridge-runtime/teaching.cjs +20 -0
  12. package/adapters/mobile/inject.sh +16 -0
  13. package/adapters/mobile/launch-console-forwarder.cjs +1 -2
  14. package/adapters/mobile/open-device.sh +2 -2
  15. package/adapters/mobile/reload-app.mjs +4 -4
  16. package/adapters/mobile/reset-app-data.sh +1 -1
  17. package/adapters/mobile/start-console-forwarder.sh +3 -2
  18. package/adapters/mobile/start-metro.sh +17 -12
  19. package/adapters/mobile/wait-for-bridge.cjs +2 -2
  20. package/adapters/shared/harness-source-fingerprint.mjs +13 -8
  21. package/adapters/shared/open-debug.mjs +2 -2
  22. package/adapters/shared/sync-wallet-fixture.sh +8 -5
  23. package/adapters/terminal/cleanup.mjs +2 -1
  24. package/adapters/terminal/inject.mjs +2 -1
  25. package/bin/mm-harness +9 -28
  26. package/dist/adapters/checkout-files.js +16 -0
  27. package/dist/adapters/console-capture.js +14 -1
  28. package/dist/adapters/core/actions.js +4 -3
  29. package/dist/adapters/core/overlay.js +1 -1
  30. package/dist/adapters/core/readiness.js +28 -1
  31. package/dist/adapters/core/run.js +2 -2
  32. package/dist/adapters/core/surface.js +16 -1
  33. package/dist/adapters/extension/browser-pid.js +56 -0
  34. package/dist/adapters/extension/console-capture.js +1 -1
  35. package/dist/adapters/extension/doctor-checks.js +1 -1
  36. package/dist/adapters/extension/ensure-ready.js +5 -4
  37. package/dist/adapters/extension/extension-id.js +1 -1
  38. package/dist/adapters/extension/feature-flags.js +1 -1
  39. package/dist/adapters/extension/fixtures.js +1 -2
  40. package/dist/adapters/extension/launch.js +4 -5
  41. package/dist/adapters/extension/launched-browser.js +1 -2
  42. package/dist/adapters/extension/network-observer.js +1 -1
  43. package/dist/adapters/extension/overlay.js +1 -1
  44. package/dist/adapters/extension/product-config.js +1 -1
  45. package/dist/adapters/extension/readiness.js +1 -1
  46. package/dist/adapters/extension/reload.js +1 -1
  47. package/dist/adapters/extension/run.js +24 -10
  48. package/dist/adapters/extension/runtime-decision.js +2 -1
  49. package/dist/adapters/extension/runtime.js +9 -5
  50. package/dist/adapters/extension/surface.js +15 -2
  51. package/dist/adapters/extension/verify.js +2 -2
  52. package/dist/adapters/harness-freshness.js +2 -1
  53. package/dist/adapters/mobile/android-gfxinfo-performance.js +1 -3
  54. package/dist/adapters/mobile/app-identity.js +10 -0
  55. package/dist/adapters/mobile/doctor-checks.js +1 -1
  56. package/dist/adapters/mobile/fixtures.js +1 -1
  57. package/dist/adapters/mobile/launch.js +27 -28
  58. package/dist/adapters/mobile/lifecycle.js +1 -1
  59. package/dist/adapters/mobile/metro-env.js +9 -59
  60. package/dist/adapters/mobile/performance-observer.js +1 -1
  61. package/dist/adapters/mobile/prepare-target.js +1 -1
  62. package/dist/adapters/mobile/prepare.js +3 -4
  63. package/dist/adapters/mobile/provision.js +1 -1
  64. package/dist/adapters/mobile/readiness.js +1 -1
  65. package/dist/adapters/mobile/recording-target.js +15 -0
  66. package/dist/adapters/mobile/release-artifact-state.js +1 -1
  67. package/dist/adapters/mobile/reload.js +1 -1
  68. package/dist/adapters/mobile/run.js +8 -7
  69. package/dist/adapters/mobile/runtime-decision.js +2 -4
  70. package/dist/adapters/mobile/source-freshness.js +22 -126
  71. package/dist/adapters/mobile/surface.js +26 -3
  72. package/dist/adapters/mobile/ui.js +16 -5
  73. package/dist/adapters/mobile/verify.js +2 -3
  74. package/dist/adapters/performance/cdp-trace.js +1 -1
  75. package/dist/adapters/performance/device-platform.js +1 -1
  76. package/dist/adapters/slot-ports.js +1 -1
  77. package/dist/adapters/surface.js +1 -0
  78. package/dist/adapters/terminal/console-capture.js +1 -1
  79. package/dist/adapters/terminal/launch.js +1 -1
  80. package/dist/adapters/terminal/run.js +25 -2
  81. package/dist/adapters/terminal/surface.js +7 -1
  82. package/dist/adapters.js +8 -4
  83. package/dist/advisor-policy.js +1 -1
  84. package/dist/checkout-view.js +1 -2
  85. package/dist/cli.js +28 -15
  86. package/dist/command-contract.js +1 -26
  87. package/dist/commands/call.js +10 -24
  88. package/dist/commands/check.js +3 -4
  89. package/dist/commands/checklist.js +1 -1
  90. package/dist/commands/completion-candidates.js +2 -10
  91. package/dist/commands/config.js +2 -2
  92. package/dist/commands/core-readiness.js +12 -7
  93. package/dist/commands/debug.js +3 -3
  94. package/dist/commands/device-target.js +5 -5
  95. package/dist/commands/doctor.js +2 -14
  96. package/dist/commands/domain.js +2 -2
  97. package/dist/commands/ensure-ready.js +1 -7
  98. package/dist/commands/fixtures.js +5 -6
  99. package/dist/commands/help.js +3 -4
  100. package/dist/commands/launch/device.js +10 -17
  101. package/dist/commands/launch/extension.js +2 -8
  102. package/dist/commands/launch/mobile.js +1 -1
  103. package/dist/commands/list-executables.js +1 -3
  104. package/dist/commands/logs.js +3 -3
  105. package/dist/commands/manifest.js +1 -12
  106. package/dist/commands/mobile-device-view.js +4 -2
  107. package/dist/commands/overlay.js +110 -0
  108. package/dist/commands/pin.js +1 -1
  109. package/dist/commands/pr-body.js +2 -2
  110. package/dist/commands/prepare.js +4 -9
  111. package/dist/commands/provision.js +3 -12
  112. package/dist/commands/recipe-advice.js +1 -2
  113. package/dist/commands/recipe-quality.js +2 -3
  114. package/dist/commands/resolve-extension.js +1 -7
  115. package/dist/commands/review.js +4 -4
  116. package/dist/commands/run-engine.js +12 -51
  117. package/dist/commands/run.js +4 -25
  118. package/dist/commands/runtime-decision.js +1 -6
  119. package/dist/commands/runtime-health.js +1 -7
  120. package/dist/commands/runtime-launch.js +1 -13
  121. package/dist/commands/self-test.js +2 -6
  122. package/dist/commands/status-watch.js +1 -2
  123. package/dist/commands/status.js +2 -5
  124. package/dist/commands/task-init.js +2 -3
  125. package/dist/commands/tutorial.js +2 -2
  126. package/dist/commands/update.js +1 -1
  127. package/dist/completions-cache.js +1 -1
  128. package/dist/console-classifier.js +19 -0
  129. package/dist/doctor.js +2 -3
  130. package/dist/funding-execution-context.js +15 -1
  131. package/dist/host.js +32 -0
  132. package/dist/index.js +1 -6
  133. package/dist/live-tsx.js +49 -0
  134. package/dist/mm-harness-cli.js +2 -4
  135. package/dist/network-observation.js +4 -4
  136. package/dist/paths.js +34 -64
  137. package/dist/performance-observation.js +2 -2
  138. package/dist/progress.js +1 -1
  139. package/dist/recipe-assistance-context.js +1 -3
  140. package/dist/recipe-security.js +2 -59
  141. package/dist/review/knowledge.js +2 -2
  142. package/dist/runner.js +5 -3
  143. package/dist/runtime-context.js +1 -1
  144. package/dist/task-view.js +2 -3
  145. package/docs/CONTRIBUTING.md +1 -1
  146. package/docs/cli-contract.md +2 -2
  147. package/docs/feature-guard.md +90 -0
  148. package/library/README.md +3 -1
  149. package/library/actions/core/perps/_controller.mjs +153 -130
  150. package/library/actions/core/perps/quote_fees.mjs +58 -0
  151. package/library/actions/core/wallet/_fixture-accounts.mjs +69 -0
  152. package/library/actions/core/wallet/list_accounts.mjs +14 -57
  153. package/library/actions/extension/feature_flags/state.mjs +1 -1
  154. package/library/actions/extension/platform/cdp.mjs +2 -8
  155. package/library/actions/harness-exports.mjs +1 -6
  156. package/library/actions/mobile/app/network-control.mjs +6 -8
  157. package/library/actions/mobile/perps/prepare_local_snapshot_endpoint.mjs +1 -1
  158. package/library/actions/mobile/platform/bridge.mjs +8 -8
  159. package/library/actions/mobile/platform/native-session-name.mjs +21 -1
  160. package/library/actions/mobile/platform/native-session.mjs +3 -4
  161. package/library/actions/mobile/platform/observe-ui.mjs +12 -6
  162. package/library/actions/mobile/ui/native-navigation.mjs +4 -3
  163. package/library/actions/mobile/wallet/native-ui.mjs +3 -2
  164. package/library/actions/shared/analytics/_adapter.mjs +1 -1
  165. package/library/actions/shared/console-findings.mjs +1 -1
  166. package/library/actions/terminal/platform/runtime.mjs +2 -1
  167. package/library/manifests/core.action-manifest.json +83 -13
  168. package/package.json +8 -9
  169. package/adapters/extension/lib/slot-title.cjs +0 -292
  170. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +0 -472
  171. package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +0 -125
  172. package/adapters/mobile/bridge-runtime/lib/cdp-broker.cjs +0 -1055
  173. package/adapters/mobile/bridge-runtime/lib/cdp-eval.cjs +0 -110
  174. package/adapters/mobile/bridge-runtime/lib/config.cjs +0 -61
  175. package/adapters/mobile/bridge-runtime/lib/console-format.cjs +0 -53
  176. package/adapters/mobile/bridge-runtime/lib/devtools-proxy.cjs +0 -177
  177. package/adapters/mobile/bridge-runtime/lib/issue-capture.cjs +0 -446
  178. package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +0 -84
  179. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +0 -403
  180. package/adapters/mobile/bridge-runtime/lib/ws-client.cjs +0 -116
  181. package/adapters/mobile/coalesce-metro-log.cjs +0 -24
  182. package/adapters/mobile/launch-metro.cjs +0 -77
  183. package/adapters/mobile/metro-config.cjs +0 -98
  184. package/adapters/mobile/metro-log-generation.cjs +0 -106
  185. package/dist/adapters/mobile/frame-metrics.js +0 -118
  186. package/dist/adapters/mobile/video-recorder.js +0 -289
  187. package/dist/artifact-files.js +0 -92
  188. package/dist/checkout-lock.js +0 -149
  189. package/dist/cli-color.js +0 -98
  190. package/dist/command-journal.js +0 -308
  191. package/dist/commands/last.js +0 -60
  192. package/dist/commands/launch/index.js +0 -109
  193. package/dist/commands/parse-args.js +0 -279
  194. package/dist/commands/reload.js +0 -41
  195. package/dist/commands/run-report.js +0 -241
  196. package/dist/commands/shared.js +0 -323
  197. package/dist/commands/stop.js +0 -60
  198. package/dist/devices.js +0 -100
  199. package/dist/execution-provenance.js +0 -332
  200. package/dist/harness.js +0 -451
  201. package/dist/heal-bounds.js +0 -137
  202. package/dist/json-stream.js +0 -60
  203. package/dist/leaf-invoke.js +0 -19
  204. package/dist/library-provenance.js +0 -33
  205. package/dist/live-adapter-contract.js +0 -380
  206. package/dist/recording-target.js +0 -120
  207. package/dist/run-diagnostics.js +0 -364
  208. package/dist/run-recording.js +0 -481
  209. package/dist/runtime-proof.js +0 -90
  210. package/library/actions/mobile/platform/tool-paths.mjs +0 -123
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { recipeRuntimeDir } from "../../paths.js";
3
+ import { recipeRuntimeDir } from "@farmslot/recipe-cli/harness";
4
4
  function readRuntimeContextTarget(target) {
5
5
  const file = path.join(target, recipeRuntimeDir(), "agentic-runtime.json");
6
6
  let parsed;
@@ -1,9 +1,8 @@
1
+ import { EXIT, spawnScriptStreaming, shellQuote } from "@farmslot/recipe-cli/harness";
1
2
  import { execFileSync } from "node:child_process";
2
3
  import path from "node:path";
3
4
  import { recordDepsBaseline } from "@farmslot/recipe-runner/runtime/deps-readiness";
4
- import { resolveAndExportMobileToolPaths } from "../../../library/actions/mobile/platform/tool-paths.mjs";
5
- import { EXIT, spawnScriptStreaming } from "../../commands/shared.js";
6
- import { shellQuote } from "../../commands/parse-args.js";
5
+ import { resolveAndExportMobileToolPaths } from "@farmslot/adapter-rn";
7
6
  import { runnerDir } from "../../paths.js";
8
7
  import {
9
8
  decideMobileReadiness,
@@ -401,7 +400,7 @@ async function dispatchAction(action, target, platform, json, preflightMode = "f
401
400
  leaf,
402
401
  withWatcherPort(["--target", cwd, ...extra], watcherPort),
403
402
  target,
404
- { MM_HARNESS_METRO_ENV_FINGERPRINT: metroEnvFingerprint ?? mobileMetroEnvCheck(path.resolve(target)).fingerprint }
403
+ { RECIPE_RN_METRO_ENV_FINGERPRINT: metroEnvFingerprint ?? mobileMetroEnvCheck(path.resolve(target)).fingerprint }
405
404
  );
406
405
  }
407
406
  case "start-console-forwarder": {
@@ -1,9 +1,9 @@
1
+ import { recipeRuntimeDir } from "@farmslot/recipe-cli/harness";
1
2
  import { execFileSync } from "node:child_process";
2
3
  import crypto from "node:crypto";
3
4
  import fs from "node:fs";
4
5
  import os from "node:os";
5
6
  import path from "node:path";
6
- import { recipeRuntimeDir } from "../../paths.js";
7
7
  import { resolveSlotPortsByRepo } from "../../../adapters/shared/resolve-slot-ports-core.mjs";
8
8
  const RUNWAY_IOS_METADATA = {
9
9
  artifactName: "ios-app-main-dev-expo",
@@ -1,4 +1,4 @@
1
- import { optionFlag, optionString, parsePort } from "../../commands/parse-args.js";
1
+ import { optionFlag, optionString, parsePort } from "@farmslot/recipe-cli/harness";
2
2
  import { detectOrphanMetros, mobileToolDoctorChecks } from "./doctor-checks.js";
3
3
  import { ensureMobilePerpsEnvironment, mobilePerpsEnvironment } from "./perps-env.js";
4
4
  const mobileReadiness = {
@@ -0,0 +1,15 @@
1
+ function resolveAndroidDeviceSerial(env = process.env) {
2
+ return env.ADB_SERIAL?.trim() || process.env.ADB_SERIAL?.trim() || void 0;
3
+ }
4
+ function resolveMobileIosSimulatorName(env = process.env) {
5
+ const explicit = env.IOS_SIMULATOR?.trim() || env.SIM_UDID?.trim();
6
+ if (explicit) return explicit;
7
+ const slot = env.FARMSLOT_SLOT_ID?.trim() || env.SLOT_ID?.trim();
8
+ if (!slot) return void 0;
9
+ const suffix = slot.includes("-") ? slot.slice(slot.lastIndexOf("-") + 1) : slot;
10
+ return suffix || void 0;
11
+ }
12
+ export {
13
+ resolveAndroidDeviceSerial,
14
+ resolveMobileIosSimulatorName
15
+ };
@@ -1,7 +1,7 @@
1
+ import { recipeRuntimePath } from "@farmslot/recipe-cli/harness";
1
2
  import { randomBytes } from "node:crypto";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
4
- import { recipeRuntimePath } from "../../paths.js";
5
5
  import {
6
6
  assertAndroidReleaseArtifactRuntime
7
7
  } from "./release-artifact.js";
@@ -1,5 +1,5 @@
1
+ import { EXIT, spawnScript } from "@farmslot/recipe-cli/harness";
1
2
  import path from "node:path";
2
- import { EXIT, spawnScript } from "../../commands/shared.js";
3
3
  import { runnerDir } from "../../paths.js";
4
4
  function reloadMobile(target, json) {
5
5
  const script = path.join(runnerDir, "adapters/mobile/reload-app.mjs");
@@ -1,17 +1,18 @@
1
+ import { isRecord } from "@farmslot/recipe-cli/harness";
1
2
  import { spawnSync } from "node:child_process";
2
3
  import path from "node:path";
3
- import { isRecord } from "../../commands/parse-args.js";
4
+ import { IOS_SIMULATOR_RECORDING_TARGET_PREFIX } from "@farmslot/adapter-rn";
4
5
  import { runnerDir } from "../../paths.js";
5
6
  import { devicePlatformFromEnv } from "../performance/device-platform.js";
6
7
  import {
7
- IOS_SIMULATOR_RECORDING_TARGET_PREFIX,
8
8
  resolveAndroidDeviceSerial,
9
9
  resolveMobileIosSimulatorName
10
- } from "../../recording-target.js";
10
+ } from "./recording-target.js";
11
+ import { mobileAppIdentityEnv } from "./app-identity.js";
11
12
  import { requireCurrentMobileReleaseArtifactState } from "./release-artifact-state.js";
12
13
  const EXPLICIT_DEVICE_KEYS = [
13
14
  "PLATFORM",
14
- "MM_HARNESS_EXPLICIT_PLATFORM",
15
+ "RECIPE_RN_EXPLICIT_PLATFORM",
15
16
  "IOS_SIMULATOR",
16
17
  "SIM_UDID",
17
18
  "ADB_SERIAL",
@@ -21,7 +22,7 @@ const EXPLICIT_DEVICE_KEYS = [
21
22
  ];
22
23
  const mobileRun = {
23
24
  pinnedEnv() {
24
- if (!process.env.MM_HARNESS_EXPLICIT_PLATFORM) return void 0;
25
+ if (!process.env.RECIPE_RN_EXPLICIT_PLATFORM) return void 0;
25
26
  return Object.fromEntries(EXPLICIT_DEVICE_KEYS.map((key) => [key, process.env[key]]));
26
27
  },
27
28
  envKeys: ["METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME", "ANDROID_PACKAGE_ID", "ADB_SERIAL", "ANDROID_SERIAL"],
@@ -49,7 +50,7 @@ const mobileRun = {
49
50
  ANDROID_TARGET_DEVICE_NAME: process.env.ANDROID_TARGET_DEVICE_NAME,
50
51
  ADB_SERIAL: process.env.ADB_SERIAL,
51
52
  ANDROID_SERIAL: process.env.ANDROID_SERIAL,
52
- ANDROID_PACKAGE_ID: process.env.ANDROID_PACKAGE_ID,
53
+ ...mobileAppIdentityEnv(),
53
54
  METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME: process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME
54
55
  }),
55
56
  // An installed release artifact is opaque: no harness HUD inside it.
@@ -78,7 +79,7 @@ const mobileRecording = {
78
79
  const androidSerial = resolveAndroidDeviceSerial();
79
80
  const iosSimulator = androidSerial ? void 0 : resolveMobileIosSimulatorName();
80
81
  if (!androidSerial && !iosSimulator) return void 0;
81
- const { createAndroidVideoRecorder, createIosSimulatorVideoRecorder } = await import("./video-recorder.js");
82
+ const { createAndroidVideoRecorder, createIosSimulatorVideoRecorder } = await import("@farmslot/adapter-rn");
82
83
  if (androidSerial) {
83
84
  const { createAndroidMirrorVideoRecorder } = await import("@farmslot/recipe-runner");
84
85
  return createAndroidMirrorVideoRecorder({ serial: androidSerial, fallback: createAndroidVideoRecorder(androidSerial) });
@@ -11,10 +11,8 @@ import {
11
11
  supersededErrorCapture
12
12
  } from "@farmslot/recipe-runner/runtime/log-analysis";
13
13
  import { probeMetroPackager } from "@farmslot/recipe-runner/runtime/metro-probe";
14
- import { color } from "../../cli-color.js";
15
- import { shellQuote } from "../../commands/parse-args.js";
16
- import { recipeRuntimePath } from "../../paths.js";
17
- import { resolveMobileToolPath } from "../../../library/actions/mobile/platform/tool-paths.mjs";
14
+ import { color, recipeRuntimePath, shellQuote } from "@farmslot/recipe-cli/harness";
15
+ import { resolveMobileToolPath } from "@farmslot/adapter-rn";
18
16
  import { mobileProductMarkers } from "./deps-markers.js";
19
17
  import { mobileMetroEnvCheck } from "./metro-env.js";
20
18
  const BUNDLE_ERR = /Bundling failed|Unable to resolve /u;
@@ -1,135 +1,31 @@
1
- import { execFileSync } from "node:child_process";
2
- import { createHash } from "node:crypto";
3
- import fs from "node:fs";
4
- import path from "node:path";
5
- import { recipeRuntimePath } from "../../paths.js";
6
- const SOURCE_PATHS = [
7
- "app",
8
- "locales",
9
- "index.js",
10
- "shim.js",
11
- "shimPerf.js",
12
- "ReactotronConfig.js",
13
- "wdyr.js",
14
- "app.config.js",
15
- "babel.config.js",
16
- "metro.config.js",
17
- "metro.transform.js",
18
- "package.json"
19
- ];
20
- const ENV_INPUTS = [".js.env", ".env", ".env.local"];
1
+ import { recordSourceBaseline, sourceCheck, sourceFingerprint } from "@farmslot/adapter-rn";
2
+ import { recipeRuntimePath } from "@farmslot/recipe-cli/harness";
3
+ const MOBILE_SOURCE_INPUTS = {
4
+ paths: [
5
+ "app",
6
+ "locales",
7
+ "index.js",
8
+ "shim.js",
9
+ "shimPerf.js",
10
+ "ReactotronConfig.js",
11
+ "wdyr.js",
12
+ "app.config.js",
13
+ "babel.config.js",
14
+ "metro.config.js",
15
+ "metro.transform.js",
16
+ "package.json"
17
+ ],
18
+ envFiles: [".js.env", ".env", ".env.local"]
19
+ };
21
20
  const BASELINE_FILE = "mobile-source-baseline.json";
22
21
  function mobileSourceFingerprint(target) {
23
- const hash = createHash("sha256");
24
- const head = tryGit(target, ["rev-parse", "HEAD"]);
25
- if (head === null) {
26
- hash.update("non-git\0");
27
- for (const relative of SOURCE_PATHS) {
28
- hashPath(hash, target, relative);
29
- }
30
- } else {
31
- hash.update(head);
32
- hash.update(git(target, ["diff", "--no-ext-diff", "--binary", "HEAD", "--", ...SOURCE_PATHS]));
33
- const untracked = git(target, [
34
- "ls-files",
35
- "--others",
36
- "--exclude-standard",
37
- "-z",
38
- "--",
39
- ...SOURCE_PATHS
40
- ]).toString("utf8").split("\0").filter(Boolean).sort();
41
- for (const relative of untracked) {
42
- hash.update(`untracked\0${relative}\0`);
43
- hash.update(fs.readFileSync(path.join(target, relative)));
44
- hash.update("\0");
45
- }
46
- }
47
- for (const relative of ENV_INPUTS) {
48
- const absolute = path.join(target, relative);
49
- hash.update(`env\0${relative}\0`);
50
- hash.update(fs.existsSync(absolute) ? fs.readFileSync(absolute) : "<absent>");
51
- hash.update("\0");
52
- }
53
- return hash.digest("hex");
22
+ return sourceFingerprint(target, MOBILE_SOURCE_INPUTS);
54
23
  }
55
24
  function mobileSourceCheck(target) {
56
- const fingerprint = mobileSourceFingerprint(target);
57
- const baseline = readBaseline(target);
58
- return {
59
- fingerprint,
60
- status: baseline === null ? "missing" : baseline.fingerprint === fingerprint ? "current" : "changed"
61
- };
25
+ return sourceCheck(target, MOBILE_SOURCE_INPUTS, recipeRuntimePath(target, BASELINE_FILE));
62
26
  }
63
27
  function recordMobileSourceBaseline(target, expectedFingerprint) {
64
- if (mobileSourceFingerprint(target) !== expectedFingerprint) return false;
65
- const marker = recipeRuntimePath(target, BASELINE_FILE);
66
- const temporary = `${marker}.${process.pid}.${Date.now()}.tmp`;
67
- const baseline = {
68
- schemaVersion: 1,
69
- fingerprint: expectedFingerprint,
70
- recordedAt: (/* @__PURE__ */ new Date()).toISOString()
71
- };
72
- fs.mkdirSync(path.dirname(marker), { recursive: true });
73
- try {
74
- fs.writeFileSync(temporary, `${JSON.stringify(baseline, null, 2)}
75
- `, { mode: 384 });
76
- if (mobileSourceFingerprint(target) !== expectedFingerprint) return false;
77
- fs.renameSync(temporary, marker);
78
- if (mobileSourceFingerprint(target) !== expectedFingerprint) {
79
- if (readBaseline(target)?.fingerprint === expectedFingerprint) fs.rmSync(marker, { force: true });
80
- return false;
81
- }
82
- } finally {
83
- fs.rmSync(temporary, { force: true });
84
- }
85
- return true;
86
- }
87
- function git(target, args) {
88
- return execFileSync("git", args, {
89
- cwd: target,
90
- encoding: "buffer",
91
- maxBuffer: 128 * 1024 * 1024,
92
- stdio: ["ignore", "pipe", "ignore"]
93
- });
94
- }
95
- function tryGit(target, args) {
96
- try {
97
- return git(target, args);
98
- } catch {
99
- return null;
100
- }
101
- }
102
- function hashPath(hash, root, relative) {
103
- const absolute = path.join(root, relative);
104
- if (!fs.existsSync(absolute)) {
105
- hash.update(`absent\0${relative}\0`);
106
- return;
107
- }
108
- const stat = fs.lstatSync(absolute);
109
- if (stat.isDirectory()) {
110
- hash.update(`directory\0${relative}\0`);
111
- for (const child of fs.readdirSync(absolute).sort()) {
112
- hashPath(hash, root, path.join(relative, child));
113
- }
114
- return;
115
- }
116
- if (stat.isSymbolicLink()) {
117
- hash.update(`symlink\0${relative}\0${fs.readlinkSync(absolute)}\0`);
118
- return;
119
- }
120
- hash.update(`file\0${relative}\0`);
121
- hash.update(fs.readFileSync(absolute));
122
- hash.update("\0");
123
- }
124
- function readBaseline(target) {
125
- try {
126
- const parsed = JSON.parse(
127
- fs.readFileSync(recipeRuntimePath(target, BASELINE_FILE), "utf8")
128
- );
129
- return parsed.schemaVersion === 1 && typeof parsed.fingerprint === "string" ? parsed : null;
130
- } catch {
131
- return null;
132
- }
28
+ return recordSourceBaseline(target, MOBILE_SOURCE_INPUTS, recipeRuntimePath(target, BASELINE_FILE), expectedFingerprint);
133
29
  }
134
30
  export {
135
31
  mobileSourceCheck,
@@ -1,19 +1,19 @@
1
+ import { recipeRuntimePath, shellQuote, writeInteractiveProgress } from "@farmslot/recipe-cli/harness";
1
2
  import { spawnSync } from "node:child_process";
2
3
  import path from "node:path";
3
4
  import { defineAdapter } from "@farmslot/adapter-sdk";
4
- import { recipeRuntimePath, runnerDir } from "../../paths.js";
5
- import { shellQuote } from "../../commands/parse-args.js";
5
+ import { runnerDir } from "../../paths.js";
6
6
  import { resolveMobileSlotPorts } from "../slot-ports.js";
7
7
  import { mobileRuntimeStatus } from "./prepare.js";
8
8
  import { hasRunwayProvisionBaseline, provisionRunwayMobile } from "./provision.js";
9
9
  import { adapterRegistry } from "../registry.js";
10
- import { writeInteractiveProgress } from "../../commands/shared.js";
11
10
  import { mobileActions } from "./actions.js";
12
11
  import { mobileHarness, mobilePrepare, mobileRuntimeContext } from "./overlay.js";
13
12
  import { mobileReadiness, mobileRuntimeDecision } from "./readiness.js";
14
13
  import { clearMobileReleaseArtifactState } from "./release-artifact-state.js";
15
14
  import { mobileDiagnostics, mobileObservation, mobileRecording, mobileRun } from "./run.js";
16
15
  import { mobileSourceFingerprint } from "./source-freshness.js";
16
+ import { checkoutHas, checkoutHasDir } from "../checkout-files.js";
17
17
  function mobileRuntimeNextAction(reasonCode, target, platform) {
18
18
  const resolved = shellQuote(path.resolve(target));
19
19
  return reasonCode === "deps-missing" || reasonCode === "deps-partial" || reasonCode === "deps-stale" ? `cd ${resolved} && yarn setup` : `mm-harness launch ${platform} --adapter mobile --target ${resolved}`;
@@ -53,6 +53,9 @@ const mobileSurface = defineAdapter({
53
53
  devServer: {
54
54
  label: "metro",
55
55
  describe: () => "Metro dev server",
56
+ // Metro's leaves read the port as METRO_PORT; run and call take it as --metro-port.
57
+ portEnv: ["METRO_PORT"],
58
+ portFlags: ["metroPort"],
56
59
  stop(target) {
57
60
  const leaf = path.join(runnerDir, "adapters", "mobile", "stop-metro.sh");
58
61
  const args = ["--target", target];
@@ -70,6 +73,26 @@ const mobileSurface = defineAdapter({
70
73
  appLogSource(target) {
71
74
  return { label: "app", path: recipeRuntimePath(target, "app-console.log") };
72
75
  },
76
+ detect: {
77
+ remote: (url) => url.includes("metamask-mobile"),
78
+ files: (target) => checkoutHas(target, "scripts/skills-sync.mts") || checkoutHasDir(target, "ios") && checkoutHasDir(target, "android") && checkoutHasDir(target, "app/core")
79
+ },
80
+ // `launch ios|android`, and --platform ios|android, select Mobile.
81
+ targets: ["ios", "android"],
82
+ flags: {
83
+ launch: ["build", "watch", "verify", "runway", "clearMetro"],
84
+ commands: ["clearMetro", "resolveOnly"]
85
+ },
86
+ failurePatterns: {
87
+ captureProtected: {
88
+ pattern: /SCREENSHOT_PROTECTED|screenshot is protected by FLAG_SECURE/iu,
89
+ message: "the focused Android window intentionally blocks screenshots; a black capture cannot be used as evidence.",
90
+ userAction: "unlock or navigate to a non-protected screen, then rerun the screenshot action"
91
+ },
92
+ transportFirst: /CDP_TIMEOUT|Mobile CDP bridge command timed out|NO_TARGET|WS_CLOSED/iu,
93
+ walletState: /fixture|keyring|not seeded|\bsrp\b|password|onboard/iu,
94
+ transport: /metro|bridge|bundle|packager|dev client/iu
95
+ },
73
96
  hints: {
74
97
  launch: "mm-harness launch ios",
75
98
  relaunch: "mm-harness launch ios",
@@ -3,9 +3,13 @@ import { promisify } from "node:util";
3
3
  import { bridgeCommand, evalAsync, evalSync, MOBILE_BRIDGE_ERROR_CODES, selectBridgeStatusEntry, simulatorScreenshot } from "../../../library/actions/mobile/platform/bridge.mjs";
4
4
  import { observeNativeUi } from "../../../library/actions/mobile/platform/observe-ui.mjs";
5
5
  import { nativeAgentDeviceStateDir } from "../../../library/actions/mobile/platform/native-session-name.mjs";
6
- import { resolveMobileToolPath } from "../../../library/actions/mobile/platform/tool-paths.mjs";
6
+ import { resolveMobileToolPath } from "@farmslot/adapter-rn";
7
7
  import { isRecord, traceText } from "../../action-values.js";
8
- import { runLiveAdapterScript } from "../../live-adapter-contract.js";
8
+ import { runLiveAdapterScript } from "@farmslot/recipe-cli/harness";
9
+ import { trustedMutationContextExtras } from "../../funding-execution-context.js";
10
+ import { LIBRARY_ACTION_NAMESPACE } from "../../paths.js";
11
+ import { liveTsxCandidates } from "../../live-tsx.js";
12
+ import { mobileAppIdentityEnv } from "./app-identity.js";
9
13
  const execFileAsync = promisify(execFile);
10
14
  function sleep(ms) {
11
15
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -213,7 +217,10 @@ async function sendMobileBridgeCommand(command, context) {
213
217
  if (!handler) {
214
218
  throw new Error(`React Native bridge command ${command.command} is not implemented by the MetaMask runner transport.`);
215
219
  }
216
- return handler(command.payload, context);
220
+ return handler(command.payload, {
221
+ ...context,
222
+ env: { ...context.env, ...mobileAppIdentityEnv(context.env) }
223
+ });
217
224
  }
218
225
  function mobileUiInput(context, command, payload) {
219
226
  return uiInputFor(context, { action: `ui.${command}`, node: payload });
@@ -235,8 +242,11 @@ async function handleMobileNavigate(payload, context) {
235
242
  const live = await runLiveAdapterScript({
236
243
  platform: "mobile",
237
244
  action: "ui.navigate",
245
+ namespace: LIBRARY_ACTION_NAMESPACE,
238
246
  node: payload,
239
- context
247
+ context,
248
+ contextExtras: trustedMutationContextExtras(context),
249
+ tsxCandidates: liveTsxCandidates()
240
250
  });
241
251
  if (!live) throw new Error("ui.navigate requires library/actions/mobile/ui/navigate.mjs.");
242
252
  return isRecord(live.result) ? { ...live.result, liveAdapter: live.script } : live.result;
@@ -586,7 +596,8 @@ async function executeNativeProviderAction(action, node, context, createTranspor
586
596
  if (platform === "android" && action === "ui.swipe" && isCoordinateTarget(node.target)) {
587
597
  return executeAndroidCoordinateSwipe(node, String(device));
588
598
  }
589
- const app = platform === "android" ? context.env.ANDROID_PACKAGE_ID ?? process.env.ANDROID_PACKAGE_ID ?? "io.metamask" : context.env.IOS_BUNDLE_ID ?? process.env.IOS_BUNDLE_ID ?? "io.metamask.MetaMask";
599
+ const identity = mobileAppIdentityEnv(context.env);
600
+ const app = platform === "android" ? identity.ANDROID_PACKAGE_ID : identity.IOS_BUNDLE_ID;
590
601
  const session = (platform === "ios" ? `mm-harness-${process.pid}-${device}` : `mm-harness-${process.pid}-${context.nodeId}`).replace(/[^a-zA-Z0-9._-]/gu, "-");
591
602
  const stateDir = nativeAgentDeviceStateDir(context.env ?? {}, context.projectRoot, String(device));
592
603
  let transport;
@@ -1,10 +1,9 @@
1
+ import { recipeHarnessPath, recipeHarnessRoot, recipeRuntimeDir, spawnScriptStreaming, readRuntimeContextField } from "@farmslot/recipe-cli/harness";
1
2
  import { execFileSync } from "node:child_process";
2
3
  import crypto from "node:crypto";
3
4
  import fs from "node:fs";
4
5
  import path from "node:path";
5
- import { spawnScriptStreaming } from "../../commands/shared.js";
6
- import { readRuntimeContextField } from "../../harness.js";
7
- import { recipeHarnessPath, recipeHarnessRoot, recipeRuntimeDir, runnerDir } from "../../paths.js";
6
+ import { runnerDir } from "../../paths.js";
8
7
  const USAGE = "Usage: mm-harness verify --adapter mobile -- [--target <metamask-mobile>] [--artifacts-dir <dir>] [--static-only] [--platform ios|android] [--preflight-mode fast|auto|default|rebuild-native|clean] [--no-auto-start]";
9
8
  const ENV_FILES = [".js.env", ".env", ".env.local"];
10
9
  const PREFLIGHT_MODES = ["fast", "auto", "default", "rebuild-native", "clean"];
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  summarizeFrames
3
- } from "../mobile/frame-metrics.js";
3
+ } from "@farmslot/adapter-rn";
4
4
  import { withTimeout } from "../../async.js";
5
5
  import {
6
6
  summarizeJavaScriptTasks
@@ -1,6 +1,6 @@
1
1
  function devicePlatformFromEnv(env) {
2
2
  const explicit = String(
3
- env.MM_HARNESS_EXPLICIT_PLATFORM ?? env.RECIPE_HARNESS_PLATFORM ?? ""
3
+ env.RECIPE_RN_EXPLICIT_PLATFORM ?? env.RECIPE_HARNESS_PLATFORM ?? ""
4
4
  ).toLowerCase();
5
5
  if (explicit === "android" || explicit === "ios") return explicit;
6
6
  return env.ADB_SERIAL || env.ANDROID_SERIAL || env.ANDROID_DEVICE ? "android" : "ios";
@@ -1,8 +1,8 @@
1
+ import { recipeRuntimeDir } from "@farmslot/recipe-cli/harness";
1
2
  import { execFileSync } from "node:child_process";
2
3
  import fs from "node:fs";
3
4
  import os from "node:os";
4
5
  import path from "node:path";
5
- import { recipeRuntimeDir } from "../paths.js";
6
6
  import {
7
7
  formatExtensionIdentityPorts,
8
8
  resolveDefaultExtensionPorts,
@@ -1,3 +1,4 @@
1
+ import "../host.js";
1
2
  import { adapterRegistry } from "./registry.js";
2
3
  import "./mobile/surface.js";
3
4
  import "./extension/surface.js";
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { recipeRuntimePath } from "@farmslot/recipe-cli/harness";
3
4
  import { verifyCapture } from "../../../library/actions/shared/console-collector.mjs";
4
- import { recipeRuntimePath } from "../../paths.js";
5
5
  import { ensureConsoleCollector } from "../console-capture.js";
6
6
  const TERMINAL_CONSOLE_FILES = Object.freeze({
7
7
  app: "app-console.log",
@@ -1,6 +1,6 @@
1
+ import { flag, str } from "@farmslot/recipe-cli/harness";
1
2
  import { execFileSync } from "node:child_process";
2
3
  import path from "node:path";
3
- import { flag, str } from "../../commands/shared.js";
4
4
  import { runnerDir } from "../../paths.js";
5
5
  const TERMINAL_ONLY_LAUNCH_OPTIONS = Object.freeze({
6
6
  signer: "--signer",
@@ -1,3 +1,5 @@
1
+ import { isRecord } from "@farmslot/recipe-cli/harness";
2
+ import { verifyOwnedCollectorControl } from "../console-capture.js";
1
3
  import { devicePlatformFromEnv } from "../performance/device-platform.js";
2
4
  import {
3
5
  ensureTerminalConsoleCapture,
@@ -11,10 +13,31 @@ const terminalDiagnostics = {
11
13
  start: (projectRoot) => ensureTerminalConsoleCapture(projectRoot),
12
14
  files: (projectRoot) => terminalCaptureFiles(projectRoot),
13
15
  cdpPort: () => terminalCdpPort(),
14
- verifyControl: (capture) => verifyTerminalConsoleControl(capture)
16
+ verifyControl: (capture) => verifyOwnedCollectorControl(capture, () => verifyTerminalConsoleControl(capture))
15
17
  },
16
- walletLog: (projectRoot) => terminalRuntimeFile(projectRoot, "wallet-requests.jsonl")
18
+ requestLog: {
19
+ path: (projectRoot) => terminalRuntimeFile(projectRoot, "wallet-requests.jsonl"),
20
+ findings: (lines) => walletRequestFindings(lines)
21
+ }
17
22
  };
23
+ function walletRequestFindings(lines) {
24
+ const findings = [];
25
+ for (const line of lines) {
26
+ let entry;
27
+ try {
28
+ entry = JSON.parse(line);
29
+ } catch {
30
+ continue;
31
+ }
32
+ if (!isRecord(entry)) continue;
33
+ let text = null;
34
+ if (entry.kind === "blocked-mainnet" && entry.probe !== true) text = `blocked-mainnet: ${String(entry.transport ?? "request")} to ${String(entry.url ?? "?")}`;
35
+ else if (entry.kind === "refused-mainnet") text = `refused-mainnet: ${String(entry.method ?? "?")} (${String(entry.reason ?? "?")})`;
36
+ else if (entry.kind === "unattributed") text = `unattributed wallet ${entry.binding === "request" ? "request" : "log entry"}${typeof entry.method === "string" ? ` (${entry.method})` : ""}`;
37
+ if (text) findings.push({ level: "error", source: "wallet", text });
38
+ }
39
+ return findings;
40
+ }
18
41
  const terminalObservation = {
19
42
  network: {
20
43
  backend: async (_target, env, artifactsDir) => (await import("../../network-observation.js")).createExtensionNetworkBackend(env, artifactsDir)
@@ -1,6 +1,6 @@
1
+ import { shellQuote } from "@farmslot/recipe-cli/harness";
1
2
  import path from "node:path";
2
3
  import { defineAdapter } from "@farmslot/adapter-sdk";
3
- import { shellQuote } from "../../commands/parse-args.js";
4
4
  import fs from "node:fs";
5
5
  import { TERMINAL_CONSOLE_FILES, terminalRuntimeFile } from "./console-capture.js";
6
6
  import { stopTerminalBrowserSync } from "./stop.js";
@@ -9,6 +9,7 @@ import { terminalActions } from "./actions.js";
9
9
  import { terminalHarness, terminalRuntimeContext } from "./overlay.js";
10
10
  import { terminalReadiness } from "./readiness.js";
11
11
  import { terminalDiagnostics, terminalObservation } from "./run.js";
12
+ import { checkoutHas, checkoutHasDir } from "../checkout-files.js";
12
13
  function portFromEnv(...names) {
13
14
  for (const name of names) {
14
15
  const raw = process.env[name];
@@ -98,6 +99,11 @@ const terminalSurface = defineAdapter({
98
99
  appLogSource(target) {
99
100
  return { label: "app-console", path: terminalRuntimeFile(target, TERMINAL_CONSOLE_FILES.app) };
100
101
  },
102
+ detect: {
103
+ remote: (url) => url.includes("va-mmcx-terminal"),
104
+ files: (target) => checkoutHasDir(target, "src/features/perpetuals") && ["next.config.ts", "next.config.mjs", "next.config.js"].some((file) => checkoutHas(target, file))
105
+ },
106
+ flags: { launch: ["freshProfile", "headless", "headful"] },
101
107
  hints: {
102
108
  launch: "mm-harness launch --adapter terminal --cdp-port <port> --watcher-port <app port>",
103
109
  relaunch: "mm-harness launch --adapter terminal --fresh-profile",
package/dist/adapters.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import http from "node:http";
2
2
  import { createHash } from "node:crypto";
3
3
  import { compatibilityMode, fixtureSummary, repoShape } from "./doctor.js";
4
- import {
5
- runLiveAdapterScript
6
- } from "./live-adapter-contract.js";
4
+ import { runLiveAdapterScript } from "@farmslot/recipe-cli/harness";
5
+ import { trustedMutationContextExtras } from "./funding-execution-context.js";
6
+ import { LIBRARY_ACTION_NAMESPACE } from "./paths.js";
7
+ import { liveTsxCandidates } from "./live-tsx.js";
7
8
  import { resolveWalletImportCredentials, validateWalletImportOptions } from "../library/actions/shared/wallet/import-source.mjs";
8
9
  import { isRecord, traceText } from "./action-values.js";
9
10
  import { getAdapterSurface } from "./adapters/surface.js";
@@ -109,9 +110,12 @@ async function runLiveFirst(platform, action, node, context, forceLive = false,
109
110
  const live = await runLiveAdapterScript({
110
111
  platform,
111
112
  action,
113
+ namespace: LIBRARY_ACTION_NAMESPACE,
112
114
  node,
113
115
  context,
114
- prepared: preparedLiveAdapters?.get(action)
116
+ prepared: preparedLiveAdapters?.get(action),
117
+ contextExtras: trustedMutationContextExtras(context),
118
+ tsxCandidates: liveTsxCandidates()
115
119
  });
116
120
  if (!live) return null;
117
121
  const liveResult = isRecord(live.result) ? live.result : { result: live.result };
@@ -1,5 +1,5 @@
1
+ import { optionString, usageError } from "@farmslot/recipe-cli/harness";
1
2
  import { assessmentProvider } from "./assessment-providers.js";
2
- import { optionString, usageError } from "./commands/parse-args.js";
3
3
  import { configPath, parseAdvisorSettings, readConfig } from "./review/knowledge.js";
4
4
  function resolveAdvisorPolicy(options, env = process.env, config) {
5
5
  const saved = (config ?? readConfig(configPath(env))).advisor ?? {};
@@ -1,8 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { color } from "./cli-color.js";
3
+ import { color, recipeRuntimeDir } from "@farmslot/recipe-cli/harness";
4
4
  import { renderRuntimeContext, runtimeContextSummary } from "./doctor.js";
5
- import { recipeRuntimeDir } from "./paths.js";
6
5
  import {
7
6
  configPath,
8
7
  declaredLibraryPathEnv,