@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,18 +1,15 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { featureFlagReport } from "../feature-flags.js";
4
- import { color } from "../cli-color.js";
4
+ import { color, detectAdapter, adapterDetectNext, EXIT, usageOut, optionFlag, optionString, targetPath } from "@farmslot/recipe-cli/harness";
5
5
  import { collectCheckoutView, renderCheckoutView } from "../checkout-view.js";
6
- import { detectAdapter } from "../harness.js";
7
6
  import { assertAdapter } from "../paths.js";
8
7
  import { getAdapterSurface } from "../adapters/surface.js";
9
8
  import {
10
9
  applyDeviceTargeting
11
10
  } from "./device-target.js";
12
- import { ADAPTER_DETECT_NEXT, EXIT, usageOut } from "./shared.js";
13
11
  import { handleStatusTaskView, renderAcceptanceLines } from "./status-watch.js";
14
12
  import { collectTaskView, findTaskDir } from "../task-view.js";
15
- import { optionFlag, optionString, targetPath } from "./parse-args.js";
16
13
  async function handleStatus({ options }) {
17
14
  const target = targetPath(options);
18
15
  const json = optionFlag(options, "json");
@@ -29,7 +26,7 @@ async function handleStatus({ options }) {
29
26
  const allDevices = optionFlag(options, "allDevices");
30
27
  const adapter = detectAdapter(target);
31
28
  if (!adapter) {
32
- return usageOut(json, "status", `could not detect the MetaMask repo type for ${target}`, ADAPTER_DETECT_NEXT);
29
+ return usageOut(json, "status", `could not detect the MetaMask repo type for ${target}`, adapterDetectNext());
33
30
  }
34
31
  assertAdapter(adapter);
35
32
  const view = collectCheckoutView(target, adapter);
@@ -1,8 +1,7 @@
1
+ import { harnessExecutable, detectAdapter, EXIT } from "@farmslot/recipe-cli/harness";
1
2
  import { spawnSync } from "node:child_process";
2
3
  import { createRequire } from "node:module";
3
- import { detectAdapter } from "../harness.js";
4
- import { harnessExecutable } from "../paths.js";
5
- import { EXIT } from "./shared.js";
4
+ import "../adapters/surface.js";
6
5
  const require2 = createRequire(import.meta.url);
7
6
  const USAGE = "mm-harness task init <task-dir> --flow f --template id --title t [options]";
8
7
  function hasOption(argv, name) {
@@ -1,5 +1,5 @@
1
+ import { parseArgs } from "@farmslot/recipe-cli/harness";
1
2
  import { spawnSync } from "node:child_process";
2
- import { parseArgs } from "./parse-args.js";
3
3
  const PROTECTED_TUTORIAL_URL = "https://glowing-chainsaw-gwzrn8k.pages.github.io/recipes.html#agent-method";
4
4
  function recipeTutorialUrl(runMode = process.env.MM_HARNESS_RUN_MODE) {
5
5
  if (runMode === "src") {
@@ -13,7 +13,7 @@ function openUrl(url) {
13
13
  return result.status === 0;
14
14
  }
15
15
  function handleTutorial(argv, context) {
16
- const parsed = parseArgs(argv, "tutorial");
16
+ const parsed = parseArgs(argv);
17
17
  const url = recipeTutorialUrl();
18
18
  const noOpen = parsed.options.noOpen === true;
19
19
  const opened = noOpen ? false : (context.open ?? openUrl)(url);
@@ -1,9 +1,9 @@
1
+ import { EXIT, flag, parseFlags } from "@farmslot/recipe-cli/harness";
1
2
  import { spawn, spawnSync } 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
6
  import { runnerDir } from "../paths.js";
6
- import { EXIT, flag, parseFlags } from "./shared.js";
7
7
  const PACKAGE_NAME = "@deeeed/metamask-harness";
8
8
  const NUDGE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
9
9
  const NUDGE_FETCH_TIMEOUT_MS = 2e3;
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { recipeRuntimePath } from "./paths.js";
3
+ import { recipeRuntimePath } from "@farmslot/recipe-cli/harness";
4
4
  const COMPLETION_CACHE_VERSION = 2;
5
5
  const COMPLETION_CACHE_TTL_MS = 6e4;
6
6
  function completionCachePath(projectRoot) {
@@ -0,0 +1,19 @@
1
+ import {
2
+ allowlistMatch,
3
+ classifyConsoleRecord,
4
+ consoleRecords,
5
+ libraryRootsFromEnv,
6
+ loadConsoleAllowlist,
7
+ messageSignature
8
+ } from "../library/actions/shared/console-findings.mjs";
9
+ const consoleClassifier = {
10
+ records: (text) => consoleRecords(text),
11
+ classify: (record) => classifyConsoleRecord(record),
12
+ signature: (message) => messageSignature(message),
13
+ loadAllowlist: (libraryRoots) => loadConsoleAllowlist([...libraryRoots]),
14
+ match: (event, allowlist) => allowlistMatch(event, allowlist),
15
+ libraryRoots: () => libraryRootsFromEnv()
16
+ };
17
+ export {
18
+ consoleClassifier
19
+ };
package/dist/doctor.js CHANGED
@@ -1,11 +1,10 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { color } from "./cli-color.js";
3
+ import { color, harnessExecutable, recipeHarnessRoot, recipeRuntimeDir, readRuntimeContextField, resolveRuntimeContextPath } from "@farmslot/recipe-cli/harness";
4
4
  import { normalizeFeatureFlagRecord } from "./feature-flags.js";
5
5
  import { getAdapterSurface } from "./adapters/surface.js";
6
- import { readRuntimeContextField, resolveRuntimeContextPath } from "./harness.js";
7
6
  import { isPin } from "./harness-pin.js";
8
- import { harnessExecutable, readJson, recipeHarnessRoot, recipeRuntimeDir, runnerDir } from "./paths.js";
7
+ import { readJson, runnerDir } from "./paths.js";
9
8
  function requiredDoctorCheckSummary(checks) {
10
9
  const required = checks.filter((check) => check.required);
11
10
  const failed = required.filter((check) => check.status === "fail").map((check) => check.id);
@@ -1,10 +1,10 @@
1
+ import { optionString, recipeRuntimeDir } from "@farmslot/recipe-cli/harness";
1
2
  import { createHash } from "node:crypto";
2
3
  import { constants as fsConstants } from "node:fs";
3
4
  import { chmod, lstat, mkdir, open, readFile, readdir, realpath } from "node:fs/promises";
4
5
  import os from "node:os";
5
6
  import path from "node:path";
6
7
  import { digestRecipeDocument } from "@farmslot/protocol";
7
- import { recipeRuntimeDir } from "./paths.js";
8
8
  const SHA256 = /^[a-f0-9]{64}$/u;
9
9
  const INTEGER = /^(0|[1-9][0-9]{0,39})$/u;
10
10
  const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
@@ -255,6 +255,18 @@ async function trustedMutationForAction(base, recipe, selectedIdentitySha256) {
255
255
  trustedActionContexts.add(trusted);
256
256
  return trusted;
257
257
  }
258
+ function fundingOptionsFromCli(options) {
259
+ return {
260
+ fundingRequest: optionString(options, "fundingRequest"),
261
+ fundingPreflight: optionString(options, "fundingPreflight"),
262
+ fundingReleaseProof: optionString(options, "fundingReleaseProof"),
263
+ fundingBalanceProof: optionString(options, "fundingBalanceProof")
264
+ };
265
+ }
266
+ function trustedMutationContextExtras(context) {
267
+ const trusted = context.trustedMutation;
268
+ return trusted ? { trustedMutation: serializeTrustedMutationContext(trusted) } : void 0;
269
+ }
258
270
  function serializeTrustedMutationContext(value) {
259
271
  if (!isRecord(value) || !trustedActionContexts.has(value)) {
260
272
  throw new Error("Live adapter refused an untrusted mutation context.");
@@ -1431,8 +1443,10 @@ function deepFreeze(value) {
1431
1443
  }
1432
1444
  export {
1433
1445
  authorizeTrustedMutationPlan,
1446
+ fundingOptionsFromCli,
1434
1447
  loadTrustedMutationBase,
1435
1448
  selectedIdentityFromWalletImport,
1436
1449
  serializeTrustedMutationContext,
1450
+ trustedMutationContextExtras,
1437
1451
  trustedMutationForAction
1438
1452
  };
package/dist/host.js ADDED
@@ -0,0 +1,32 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { configureHarnessAdapters, configureHarnessHost } from "@farmslot/recipe-cli/harness";
4
+ import { adapterRegistry } from "./adapters/registry.js";
5
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
6
+ configureHarnessHost({
7
+ name: "mm-harness",
8
+ product: "MetaMask",
9
+ envPrefix: "MM_HARNESS",
10
+ recipeEnvPrefix: "METAMASK_RECIPE",
11
+ packageName: "@deeeed/metamask-harness",
12
+ packageRoot,
13
+ bin: "bin/mm-harness",
14
+ journaledCommands: [
15
+ "prepare",
16
+ "call",
17
+ "check",
18
+ "checklist",
19
+ "cleanup",
20
+ "doctor",
21
+ "fixtures",
22
+ "install",
23
+ "launch",
24
+ "runtime-launch",
25
+ "provision",
26
+ "recipe-quality",
27
+ "run",
28
+ "stop",
29
+ "verify"
30
+ ]
31
+ });
32
+ configureHarnessAdapters(adapterRegistry);
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import "./host.js";
1
2
  import { createDoctorReport } from "./doctor.js";
2
3
  import { loadActionManifest, validateManifest } from "./manifest.js";
3
4
  import { createMetaMaskExtensionRunner, createMetaMaskMobileRunner, createMetaMaskRunner } from "./runner.js";
@@ -5,13 +6,9 @@ import {
5
6
  extensionIdPath,
6
7
  importRecipeHarnessRuntimeBrowserExtension,
7
8
  importRecipeHarnessRuntimeCdp,
8
- recipeHarnessPath,
9
9
  walletFixturePath
10
10
  } from "./paths.js";
11
- import { captureActiveRecipeRecordingSnapshot } from "./run-recording.js";
12
- import { validateRuntimeProof } from "./runtime-proof.js";
13
11
  export {
14
- captureActiveRecipeRecordingSnapshot,
15
12
  createDoctorReport,
16
13
  createMetaMaskExtensionRunner,
17
14
  createMetaMaskMobileRunner,
@@ -20,8 +17,6 @@ export {
20
17
  importRecipeHarnessRuntimeBrowserExtension,
21
18
  importRecipeHarnessRuntimeCdp,
22
19
  loadActionManifest,
23
- recipeHarnessPath,
24
20
  validateManifest,
25
- validateRuntimeProof,
26
21
  walletFixturePath
27
22
  };
@@ -0,0 +1,49 @@
1
+ import { accessSync, constants, existsSync, readFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ const requireFromHarness = createRequire(import.meta.url);
5
+ function harnessTsxBin() {
6
+ try {
7
+ const manifest = requireFromHarness.resolve("tsx/package.json");
8
+ const { bin } = JSON.parse(readFileSync(manifest, "utf8"));
9
+ const relative = typeof bin === "string" ? bin : bin?.tsx;
10
+ return relative ? path.join(path.dirname(manifest), relative) : void 0;
11
+ } catch {
12
+ return void 0;
13
+ }
14
+ }
15
+ function liveTsxCandidates() {
16
+ const bin = harnessTsxBin();
17
+ return bin ? [bin] : [];
18
+ }
19
+ function isExecutable(file) {
20
+ try {
21
+ accessSync(file, constants.X_OK);
22
+ return true;
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+ function resolveOverride(override, projectRoot) {
28
+ if (!override.includes("/")) {
29
+ return (process.env.PATH ?? "").split(path.delimiter).map((dir) => path.join(path.resolve(projectRoot, dir || "."), override)).find(isExecutable);
30
+ }
31
+ const bin = path.resolve(projectRoot, override);
32
+ return isExecutable(bin) ? bin : void 0;
33
+ }
34
+ function resolveLiveTsx(projectRoot) {
35
+ const override = process.env.TSX_BIN;
36
+ if (override) {
37
+ const bin = resolveOverride(override, path.resolve(projectRoot));
38
+ return bin ? { bin, source: "TSX_BIN" } : null;
39
+ }
40
+ const target = path.join(path.resolve(projectRoot), "node_modules/.bin/tsx");
41
+ if (existsSync(target)) return { bin: target, source: "target" };
42
+ const harness = harnessTsxBin();
43
+ return harness && existsSync(harness) ? { bin: harness, source: "harness" } : null;
44
+ }
45
+ export {
46
+ harnessTsxBin,
47
+ liveTsxCandidates,
48
+ resolveLiveTsx
49
+ };
@@ -1,11 +1,10 @@
1
+ import "./host.js";
1
2
  import { spawnSync } from "node:child_process";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import { Command } from "commander";
6
- import { color } from "./cli-color.js";
7
- import { withCommandJournal } from "./command-journal.js";
8
- import { JsonStreamWriter } from "./json-stream.js";
7
+ import { color, withCommandJournal, JsonStreamWriter, detectAdapter } from "@farmslot/recipe-cli/harness";
9
8
  import { handleUpdate, maybeNudge } from "./commands/update.js";
10
9
  import { handlePin } from "./commands/pin.js";
11
10
  import { handleCallHelp } from "./commands/call.js";
@@ -13,7 +12,6 @@ import { handleHelp } from "./commands/help.js";
13
12
  import { handleTutorial } from "./commands/tutorial.js";
14
13
  import { handleSetupBase } from "./commands/setup-base.js";
15
14
  import { getAdapterSurface } from "./adapters/surface.js";
16
- import { detectAdapter } from "./harness.js";
17
15
  import { configPath, hydrateLibraryPathEnv, readConfig } from "./review/knowledge.js";
18
16
  import {
19
17
  PUBLIC_COMMAND_CONTRACTS,
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
- import brokerModule from "../adapters/mobile/bridge-runtime/lib/cdp-broker.cjs";
3
- import configModule from "../adapters/mobile/bridge-runtime/lib/config.cjs";
2
+ import brokerModule from "@farmslot/recipe-runner/cdp-broker";
3
+ import configModule from "@farmslot/adapter-rn/bridge-runtime/lib/config.cjs";
4
4
  import {
5
5
  createExtensionNetworkObserver
6
6
  } from "./adapters/extension/network-observer.js";
@@ -8,7 +8,7 @@ import { getAdapterSurface } from "./adapters/surface.js";
8
8
  import {
9
9
  indexArtifactManifest,
10
10
  writeContainedArtifact
11
- } from "./artifact-files.js";
11
+ } from "@farmslot/recipe-cli/harness";
12
12
  const AUTO_CAPTURE_ID = "run-network";
13
13
  const AUTO_ARTIFACT_PATH = "network/run-summary.json";
14
14
  const { brokerSocketPath, createBrokerClient } = brokerModule;
@@ -179,7 +179,7 @@ function selectMobileBrokerTarget(value, env) {
179
179
  }
180
180
  function mobileTargetPin(env) {
181
181
  const platform = String(
182
- env.MM_HARNESS_EXPLICIT_PLATFORM ?? env.RECIPE_HARNESS_PLATFORM ?? ""
182
+ env.RECIPE_RN_EXPLICIT_PLATFORM ?? env.RECIPE_HARNESS_PLATFORM ?? ""
183
183
  ).toLowerCase();
184
184
  return String(
185
185
  platform === "android" ? env.ANDROID_TARGET_DEVICE_NAME ?? env.ANDROID_DEVICE ?? "" : platform === "ios" ? env.IOS_SIMULATOR ?? "" : env.IOS_SIMULATOR ?? env.ANDROID_TARGET_DEVICE_NAME ?? env.ANDROID_DEVICE ?? ""
package/dist/paths.js CHANGED
@@ -1,43 +1,10 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
+ import { recipeRuntimeDir, recipeRuntimePath } from "@farmslot/recipe-cli/harness";
4
5
  import { adapterRegistry } from "./adapters/registry.js";
5
6
  const runnerDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
6
- const pathDefaults = readPathDefaults();
7
- const DEFAULT_RECIPE_RUNTIME_DIR = pathDefaults.recipeRuntimeDir;
8
- const DEFAULT_RECIPE_HARNESS_ROOT = pathDefaults.recipeHarnessRoot;
9
7
  const PREPARE_PROGRESS_ARTIFACT = "prepare/progress.json";
10
- function recipeRuntimeDir() {
11
- return validateRelativeRecipePath("RECIPE_RUNTIME_DIR", process.env.RECIPE_RUNTIME_DIR || DEFAULT_RECIPE_RUNTIME_DIR);
12
- }
13
- function recipeHarnessRoot() {
14
- return validateRelativeRecipePath("RECIPE_HARNESS_ROOT", process.env.RECIPE_HARNESS_ROOT || DEFAULT_RECIPE_HARNESS_ROOT);
15
- }
16
- function harnessExecutable() {
17
- return path.resolve(process.env.MM_HARNESS_EXECUTABLE ?? path.join(runnerDir, "bin/mm-harness"));
18
- }
19
- function readPathDefaults() {
20
- const defaultsPath = path.join(runnerDir, "adapters/shared/path-defaults.json");
21
- const parsed = JSON.parse(fs.readFileSync(defaultsPath, "utf8"));
22
- return {
23
- recipeRuntimeDir: validateRelativeRecipePath("recipeRuntimeDir", parsed.recipeRuntimeDir || ""),
24
- recipeHarnessRoot: validateRelativeRecipePath("recipeHarnessRoot", parsed.recipeHarnessRoot || "")
25
- };
26
- }
27
- function validateRelativeRecipePath(name, value) {
28
- if (!value || path.isAbsolute(value)) throw new Error(`${name} must be a non-empty relative path: ${value}`);
29
- if (!/^[A-Za-z0-9._/-]+$/u.test(value)) throw new Error(`${name} contains unsupported characters: ${value}`);
30
- for (const part of value.split("/")) {
31
- if (!part || part === "." || part === "..") throw new Error(`${name} contains unsafe path component: ${value}`);
32
- }
33
- return value;
34
- }
35
- function recipeRuntimePath(projectRoot, ...segments) {
36
- return path.join(projectRoot, recipeRuntimeDir(), ...segments);
37
- }
38
- function recipeHarnessPath(projectRoot, ...segments) {
39
- return path.join(projectRoot, recipeHarnessRoot(), ...segments);
40
- }
41
8
  function walletFixturePath(projectRoot) {
42
9
  return recipeRuntimePath(projectRoot, "wallet-fixture.json");
43
10
  }
@@ -51,42 +18,51 @@ function recipeWatchLogCandidates() {
51
18
  ];
52
19
  }
53
20
  function resolveLocalProtocolRoot() {
54
- const candidates = [
55
- process.env.METAMASK_RUNNER_PROTOCOL_ROOT,
56
- readConfiguredProtocolRoot(),
57
- findProtocolRoot(runnerDir),
58
- findProtocolRoot(process.cwd())
59
- ].filter(Boolean);
60
- const root = candidates[0];
21
+ const root = process.env.METAMASK_RUNNER_PROTOCOL_ROOT || readConfiguredProtocolRoot();
61
22
  return root ? path.resolve(root) : void 0;
62
23
  }
63
- function resolveRequiredLocalProtocolRoot(reason) {
64
- const root = resolveLocalProtocolRoot();
65
- if (!root) {
66
- throw new Error(
67
- `${reason} requires a local protocol/runtime checkout. Set METAMASK_RUNNER_PROTOCOL_ROOT or create .farmslot-root for this dev-only path.`
68
- );
24
+ function resolveFarmslotPoolDir(starts = [runnerDir, process.cwd()]) {
25
+ const configured = resolveLocalProtocolRoot();
26
+ if (configured) return path.join(configured, "pool");
27
+ for (const start of starts) {
28
+ const pool = findPoolDir(start);
29
+ if (pool) return pool;
69
30
  }
70
- return root;
31
+ return void 0;
71
32
  }
33
+ const LIBRARY_ACTION_NAMESPACE = "metamask";
72
34
  function readConfiguredProtocolRoot() {
73
35
  const configPath = path.join(runnerDir, ".farmslot-root");
74
36
  if (!fs.existsSync(configPath)) return void 0;
75
37
  const value = fs.readFileSync(configPath, "utf8").trim();
76
38
  return value || void 0;
77
39
  }
78
- function findProtocolRoot(start) {
40
+ function findPoolDir(start) {
79
41
  let dir = path.resolve(start);
80
42
  while (dir !== path.dirname(dir)) {
81
- if (isProtocolRoot(dir)) return dir;
82
- const sibling = path.join(dir, "farmslot");
83
- if (isProtocolRoot(sibling)) return sibling;
43
+ for (const root of [dir, path.join(dir, "farmslot")]) {
44
+ const pool = path.join(root, "pool");
45
+ if (holdsSlotPool(pool)) return pool;
46
+ }
84
47
  dir = path.dirname(dir);
85
48
  }
86
49
  return void 0;
87
50
  }
88
- function isProtocolRoot(candidate) {
89
- return fs.existsSync(path.join(candidate, "packages/recipe-runner/package.json")) && fs.existsSync(path.join(candidate, "packages/protocol/package.json"));
51
+ function holdsSlotPool(candidate) {
52
+ let names;
53
+ try {
54
+ names = fs.readdirSync(candidate);
55
+ } catch {
56
+ return false;
57
+ }
58
+ return names.some((name) => {
59
+ if (!name.endsWith(".json") || name.includes(".bak")) return false;
60
+ try {
61
+ return Array.isArray(JSON.parse(fs.readFileSync(path.join(candidate, name), "utf8"))?.slots);
62
+ } catch {
63
+ return false;
64
+ }
65
+ });
90
66
  }
91
67
  function assertAdapter(adapter) {
92
68
  const ids = adapterRegistry.list();
@@ -140,8 +116,8 @@ async function importRecipeHarnessRuntimeReactNativeBridge() {
140
116
  }
141
117
  async function importMetaMaskNativeUiProvider() {
142
118
  return importProtocolPackage(
143
- "@farmslot/expo-recipe",
144
- "packages/expo-recipe/src/index.ts"
119
+ "@farmslot/adapter-rn",
120
+ "packages/adapter-rn/src/index.ts"
145
121
  );
146
122
  }
147
123
  async function importRecipeRunnerCli() {
@@ -176,12 +152,10 @@ function isMissingPackageError(error, packageName) {
176
152
  return (code === "ERR_MODULE_NOT_FOUND" || code === "ERR_PACKAGE_PATH_NOT_EXPORTED") && error.message.includes(packageName);
177
153
  }
178
154
  export {
179
- DEFAULT_RECIPE_HARNESS_ROOT,
180
- DEFAULT_RECIPE_RUNTIME_DIR,
155
+ LIBRARY_ACTION_NAMESPACE,
181
156
  PREPARE_PROGRESS_ARTIFACT,
182
157
  assertAdapter,
183
158
  extensionIdPath,
184
- harnessExecutable,
185
159
  importMetaMaskNativeUiProvider,
186
160
  importRecipeHarness,
187
161
  importRecipeHarnessAppLifecycle,
@@ -192,14 +166,10 @@ export {
192
166
  importRecipeRunnerCli,
193
167
  manifestPath,
194
168
  readJson,
195
- recipeHarnessPath,
196
- recipeHarnessRoot,
197
169
  recipePath,
198
- recipeRuntimeDir,
199
- recipeRuntimePath,
200
170
  recipeWatchLogCandidates,
171
+ resolveFarmslotPoolDir,
201
172
  resolveLocalProtocolRoot,
202
- resolveRequiredLocalProtocolRoot,
203
173
  runnerDir,
204
174
  walletFixturePath
205
175
  };
@@ -4,13 +4,13 @@ import { gzip } from "node:zlib";
4
4
  import { getAdapterSurface } from "./adapters/surface.js";
5
5
  import { createExtensionPerformanceBackend } from "./adapters/extension/performance-observer.js";
6
6
  import { TRACE_KINDS } from "./adapters/performance/cdp-trace.js";
7
- import { summarizeFrames } from "./adapters/mobile/frame-metrics.js";
7
+ import { summarizeFrames } from "@farmslot/adapter-rn";
8
8
  import { summarizeJavaScriptTasks } from "./adapters/performance/js-task-metrics.js";
9
9
  import {
10
10
  indexArtifactManifest,
11
11
  readContainedJsonArtifact,
12
12
  writeContainedArtifact
13
- } from "./artifact-files.js";
13
+ } from "@farmslot/recipe-cli/harness";
14
14
  const sessions = /* @__PURE__ */ new Map();
15
15
  const DEFAULT_MAX_DURATION_MS = 5 * 60 * 1e3;
16
16
  const MAX_DURATION_MS = 60 * 60 * 1e3;
package/dist/progress.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { parseArgs } from "node:util";
3
- import { color } from "./cli-color.js";
3
+ import { color } from "@farmslot/recipe-cli/harness";
4
4
  function shouldEmitJson(explicit) {
5
5
  if (explicit) return true;
6
6
  const env = process.env.RECIPE_PROGRESS_JSON;
@@ -1,13 +1,11 @@
1
1
  import fs from "node:fs";
2
2
  import { createHash } from "node:crypto";
3
3
  import { loadRecipeLibraries, resolveRecipeDependencies, rootResolutionRef } from "@farmslot/recipe-runner";
4
- import { isSensitiveKey } from "./command-journal.js";
4
+ import { isSensitiveKey, CliError, isRecord, optionStrings, EXIT } from "@farmslot/recipe-cli/harness";
5
5
  import { resolveActionManifest } from "./manifest.js";
6
6
  import { describeManifestActions } from "./commands/manifest.js";
7
7
  import { recipeComposition } from "@farmslot/recipe-cli";
8
8
  import { resolveMetaMaskLibrarySources, validateRunRecipeStatic } from "./commands/run-engine.js";
9
- import { CliError, isRecord, optionStrings } from "./commands/parse-args.js";
10
- import { EXIT } from "./commands/shared.js";
11
9
  import { MAX_ADVICE_STATE_BYTES } from "./recipe-assistance-rubric.js";
12
10
  function digest(value) {
13
11
  return createHash("sha256").update(value).digest("hex");
@@ -18,6 +18,7 @@ const READ_ONLY_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
18
18
  "metamask.perps.read_positions",
19
19
  "metamask.perps.read_orders",
20
20
  "metamask.perps.read_account",
21
+ "metamask.perps.quote_fees",
21
22
  "metamask.perps.read_snapshot",
22
23
  "metamask.perps.assert_visible_consistency",
23
24
  "metamask.perps.assert_positions",
@@ -175,65 +176,7 @@ function isPathWithin(root, candidate) {
175
176
  function isMissing(error) {
176
177
  return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
177
178
  }
178
- function explicitRecipeTrustOptions(values) {
179
- const { sourceTrust, sourceKind, sourceName, sourceDigest, approvalDigest } = values;
180
- const source = sourceTrust || sourceKind || sourceName || sourceDigest ? {
181
- trust: sourceTrust ?? "unknown",
182
- kind: sourceKind ?? "recipe-file",
183
- ...sourceName ? { name: sourceName } : {},
184
- ...sourceDigest ? { digest: sourceDigest } : {}
185
- } : void 0;
186
- return {
187
- ...source ? { source } : {},
188
- ...approvalDigest ? { approval: { planDigest: approvalDigest } } : {}
189
- };
190
- }
191
- function recipeTrustFailure(error) {
192
- if (!error || typeof error !== "object") return void 0;
193
- const value = error;
194
- if (typeof value.code !== "string" || !value.code.startsWith("RECIPE_") || typeof value.message !== "string" || typeof value.userAction !== "string") return void 0;
195
- const failure = value.failure && typeof value.failure === "object" ? value.failure : void 0;
196
- const blocked = Array.isArray(failure?.blocked) ? failure.blocked.flatMap((entry) => {
197
- if (!entry || typeof entry !== "object") return [];
198
- const node = entry;
199
- if (typeof node.nodeId !== "string" || typeof node.action !== "string" || !Array.isArray(node.capabilities)) return [];
200
- const origin = node.origin && typeof node.origin === "object" ? node.origin : void 0;
201
- const adapterOrigin = node.adapterOrigin && typeof node.adapterOrigin === "object" ? node.adapterOrigin : void 0;
202
- const implementation = adapterOrigin ? {
203
- ...typeof adapterOrigin.kind === "string" ? { kind: adapterOrigin.kind } : {},
204
- ...typeof adapterOrigin.trust === "string" ? { trust: adapterOrigin.trust } : {},
205
- ...typeof adapterOrigin.digest === "string" ? { digest: adapterOrigin.digest } : {}
206
- } : void 0;
207
- return [{
208
- nodeId: node.nodeId,
209
- action: node.action,
210
- capabilities: node.capabilities.filter(
211
- (item) => typeof item === "string"
212
- ),
213
- source: typeof origin?.kind === "string" ? origin.kind : "unknown",
214
- ...implementation && Object.keys(implementation).length > 0 ? { implementation } : {}
215
- }];
216
- }) : void 0;
217
- const recipeDigest = typeof failure?.recipeDigest === "string" ? failure.recipeDigest : void 0;
218
- const userAction = recipeDigest && (value.code === "RECIPE_TRUST_REQUIRED" || value.code === "RECIPE_APPROVAL_MISMATCH") && !value.userAction.includes("execution environment") ? `${value.userAction}; rerun with the same project root, artifact directory, and execution environment` : value.userAction;
219
- return {
220
- code: value.code,
221
- message: value.message,
222
- userAction,
223
- ...failure ? {
224
- details: {
225
- // Farmslot reports the approved execution plan digest as
226
- // recipeDigest in failures; approval input names it planDigest.
227
- ...recipeDigest ? { recipeDigest } : {},
228
- ...typeof failure.trust === "string" ? { trust: failure.trust } : {},
229
- ...blocked?.length ? { blocked } : {}
230
- }
231
- } : {}
232
- };
233
- }
234
179
  export {
235
180
  actionSourceIntegrity,
236
- explicitRecipeTrustOptions,
237
- metaMaskActionExecutionCapabilities,
238
- recipeTrustFailure
181
+ metaMaskActionExecutionCapabilities
239
182
  };
@@ -1,9 +1,9 @@
1
+ import { detectAdapter, usageError } 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 { detectAdapter } from "../harness.js";
6
- import { usageError } from "../commands/parse-args.js";
6
+ import "../adapters/surface.js";
7
7
  const REVIEW_ADAPTERS = ["mobile", "extension", "core", "terminal"];
8
8
  const ADVISOR_STAGES = ["plan", "draft", "evidence"];
9
9
  function parseAdvisorSettings(value) {
package/dist/runner.js CHANGED
@@ -10,10 +10,11 @@ import {
10
10
  import { loadMetaMaskExtensionActionManifest, loadMetaMaskMobileActionManifest } from "./manifest.js";
11
11
  import { actionSourceIntegrity, metaMaskActionExecutionCapabilities } from "./recipe-security.js";
12
12
  import {
13
+ createRecordingTargetProvider,
13
14
  prepareLiveAdapterScript
14
- } from "./live-adapter-contract.js";
15
- import { createMetaMaskRecordingTargetProvider } from "./recording-target.js";
15
+ } from "@farmslot/recipe-cli/harness";
16
16
  import {
17
+ LIBRARY_ACTION_NAMESPACE,
17
18
  importMetaMaskNativeUiProvider,
18
19
  importRecipeHarness,
19
20
  importRecipeHarnessAppLifecycle,
@@ -109,7 +110,7 @@ async function createMetaMaskRunner(adapter, actionManifest, options = {}) {
109
110
  ),
110
111
  logger,
111
112
  recording: {
112
- targetProvider: createMetaMaskRecordingTargetProvider(adapter),
113
+ targetProvider: createRecordingTargetProvider(adapter),
113
114
  ...videoRecorder ? { videoRecorder } : {}
114
115
  },
115
116
  runner: runnerProvenance(),
@@ -204,6 +205,7 @@ async function prepareLiveAdapterImplementations(platform, actions, sources, tru
204
205
  const executable = await prepareLiveAdapterScript({
205
206
  platform,
206
207
  action,
208
+ namespace: LIBRARY_ACTION_NAMESPACE,
207
209
  implementationRoot: source.implementationRoot,
208
210
  allowRuntimeImports: source.tier === "canonical" || source.trust === "trusted" || source.tier === "task" && trustTaskActions
209
211
  });
@@ -1,10 +1,10 @@
1
+ import { recipeRuntimeDir } from "@farmslot/recipe-cli/harness";
1
2
  import { createHash } from "node:crypto";
2
3
  import { execFileSync } from "node:child_process";
3
4
  import fs from "node:fs";
4
5
  import os from "node:os";
5
6
  import path from "node:path";
6
7
  import { getAdapterSurface } from "./adapters/surface.js";
7
- import { recipeRuntimeDir } from "./paths.js";
8
8
  function ensureRuntimeContext(target, adapter) {
9
9
  const repoRoot = fs.realpathSync(target);
10
10
  const runtimeDir = recipeRuntimeDir();