@deeeed/metamask-harness 0.92.29 → 0.92.31

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.92.31 - 2026-10-19
6
+
7
+ ### Changed
8
+
9
+ - Action manifests resolve through the shared Farmslot runtime catalog. Pins `@farmslot/recipe-cli` 0.23.8. As before, a selected library's manifest that declares an official action or an observer is refused, a plugin's own and its parent plugin's actions keep their library as their source, and official actions are labelled `official` and listed first. What changes:
10
+ - A library's `manifests/shared.action-manifest.json`, and an action manifest a library maps in `recipe-library.json` `actions`, now declare actions for the adapter, as in `farmslot`.
11
+ - Resolving actions reads and validates the whole library (`recipe-library.json`, its `requires` and its files) and records each source's digest, so a plan's custom-action `adapterOrigin.digest` changes. A library that declares `requires` on `@farmslot/recipe-cli` is now refused by `actions` too, as `run` and `call` already did.
12
+ - A plugin's own custom actions are listed before those of the parent it extends, and the plugin's declaration wins when both declare the same action.
13
+ - A library selected under the name `personal` is labelled `team`; the default personal library is still `personal`.
14
+ - An empty `observers` list is omitted from the resolved manifest.
15
+ - An invalid or unreadable action manifest, from a library or a task, reports the shared message (`Action manifest <file> is invalid: …` or `… is unreadable: …`). An official-action conflict now says `libraries may declare namespaced custom actions only`.
16
+ - Resolving actions takes about 20 ms longer (library digests); `call <TAB>` completion pays it too.
17
+ - Launch `Next:` hints use the shared Farmslot shell quoting: a target path, simulator name or serial containing `,` or `%` is now quoted, and a `'` is written as `'\''`. Both forms are valid shell. Internal copies of SDK helpers (`shellQuote`, `assertAdapter`), the unused JSONL progress emitter and a stale second command registry are removed, and `provision` and `install --runway` share one Runway output path; their output is unchanged.
18
+
19
+ ## 0.92.30 - 2026-10-18
20
+
21
+ ### Fixed
22
+
23
+ - Mobile Android recordings: after `adb screenrecord` restarts its segment (the 150 s roll or the 180 s limit), the next proof node now gets the same 1 s lead-in as the first one. Before, a node that started right after the roll fell outside both segments, so its claim stayed weak. Pins `@farmslot/adapter-rn` 0.22.29.
24
+
5
25
  ## 0.92.29 - 2026-10-18
6
26
 
7
27
  ### Fixed
@@ -560,9 +560,9 @@
560
560
  "id": "lib/cli-ux",
561
561
  "entry": "adapters/shared/cli-ux.sh",
562
562
  "kind": "lib",
563
- "purpose": "Shared bash UX helpers: progress, log-tui watch, failure playbooks, harness stale warnings.",
563
+ "purpose": "Shared bash UX helpers: log-tui watch, failure playbooks, harness stale warnings.",
564
564
  "inputs": "sourced by adapters that invoke recipe CLI commands",
565
- "outputs": "recipe_progress / recipe_watch_rebuild_log / recipe_failure_playbook functions"
565
+ "outputs": "recipe_watch_rebuild_log / recipe_failure_playbook functions"
566
566
  },
567
567
  {
568
568
  "id": "lib/log-tui",
@@ -25,19 +25,6 @@ recipe_color() {
25
25
  esac
26
26
  }
27
27
 
28
- recipe_progress_script() {
29
- if [ -n "${RECIPE_PROGRESS_SCRIPT:-}" ]; then
30
- printf '%s' "$RECIPE_PROGRESS_SCRIPT"
31
- return 0
32
- fi
33
- local root="${RECIPE_RUNNER_ROOT:?}"
34
- if [ -f "$root/dist/progress.js" ]; then
35
- printf '%s' "$root/dist/progress.js"
36
- else
37
- printf '%s' "$root/src/progress.ts"
38
- fi
39
- }
40
-
41
28
  recipe_log_tui_script() {
42
29
  printf '%s' "${RECIPE_LOG_TUI_SCRIPT:-${RECIPE_RUNNER_ROOT:?}/adapters/shared/log-tui.mjs}"
43
30
  }
@@ -91,22 +78,6 @@ recipe_instant_hint() {
91
78
  "$(recipe_color dim "$message")" >&2
92
79
  }
93
80
 
94
- recipe_progress() {
95
- local verb="$1" phase="$2" message="$3" elapsed="${4:-}"
96
- local args=(emit --command "$verb" --phase "$phase" --message "$message")
97
- [ -n "$elapsed" ] && args+=(--elapsed-ms "$elapsed")
98
- [ "${RECIPE_PROGRESS_JSON:-}" = "1" ] && args+=(--json)
99
- node "$(recipe_progress_script)" "${args[@]}" || true
100
- }
101
-
102
- recipe_done() {
103
- local verb="$1" status="$2" message="$3" elapsed="${4:-}"
104
- local args=(done --command "$verb" --status "$status" --message "$message")
105
- [ -n "$elapsed" ] && args+=(--elapsed-ms "$elapsed")
106
- [ "${RECIPE_PROGRESS_JSON:-}" = "1" ] && args+=(--json)
107
- node "$(recipe_progress_script)" "${args[@]}" || true
108
- }
109
-
110
81
  recipe_failure_playbook() {
111
82
  local verb="$1" reason="$2"
112
83
  shift 2
@@ -1,12 +1,11 @@
1
1
  import path from "node:path";
2
- import { colorHumanMessage, EXIT, spawnScript } from "@farmslot/recipe-cli/harness";
2
+ import { colorHumanMessage, EXIT, spawnScript, shellQuoteArg } from "@farmslot/recipe-cli/harness";
3
3
  import {
4
4
  extensionDepsBlock,
5
5
  extensionRuntimeReusable,
6
6
  installExtensionDeps,
7
7
  launchExtension
8
8
  } from "../../commands/launch/extension.js";
9
- import { shellQuote } from "../../commands/launch/quote.js";
10
9
  import {
11
10
  fixtureFeatureFlags,
12
11
  sameFeatureFlags,
@@ -73,7 +72,7 @@ const extensionLaunchProfile = {
73
72
  if (remoteFlagSpec !== void 0 && watch) {
74
73
  return {
75
74
  message: "--remote-flag cannot be applied with --watch: the watcher never re-creates the dist snapshot that carries the pin.",
76
- userAction: `mm-harness launch --adapter extension --remote-flag ${remoteFlagSpec} --target ${shellQuote(target)}`
75
+ userAction: `mm-harness launch --adapter extension --remote-flag ${remoteFlagSpec} --target ${shellQuoteArg(target)}`
77
76
  };
78
77
  }
79
78
  return void 0;
@@ -148,7 +147,7 @@ const extensionLaunchProfile = {
148
147
  recoverable: false,
149
148
  // The next step must leave the artifact alone: a --build here would replace
150
149
  // the artifact under validation with a source build.
151
- userAction: Object.keys(fixtureFeatureFlags(walletFixturePath(target))).length > 0 ? `remove remoteFeatureFlags from ${shellQuote(walletFixturePath(target))}, then: mm-harness launch --adapter extension --target ${shellQuote(target)}` : `mm-harness launch --adapter extension --target ${shellQuote(target)}`,
150
+ userAction: Object.keys(fixtureFeatureFlags(walletFixturePath(target))).length > 0 ? `remove remoteFeatureFlags from ${shellQuoteArg(walletFixturePath(target))}, then: mm-harness launch --adapter extension --target ${shellQuoteArg(target)}` : `mm-harness launch --adapter extension --target ${shellQuoteArg(target)}`,
152
151
  exitCode: EXIT.infra,
153
152
  originalError: output.trim() || void 0
154
153
  };
@@ -159,7 +158,7 @@ const extensionLaunchProfile = {
159
158
  code: "EXTENSION_PRODUCT_CONFIG_REQUIRED",
160
159
  message: block?.message ?? "Extension product configuration was not ready when the build started.",
161
160
  recoverable: false,
162
- userAction: block?.userAction ?? `mm-harness launch --adapter extension --target ${shellQuote(target)}`,
161
+ userAction: block?.userAction ?? `mm-harness launch --adapter extension --target ${shellQuoteArg(target)}`,
163
162
  exitCode: EXIT.infra
164
163
  };
165
164
  }
@@ -168,7 +167,7 @@ const extensionLaunchProfile = {
168
167
  code: "EXTENSION_BROWSER_REQUIRED",
169
168
  message: "the checkout Playwright browser is not installed; system Chrome is not a compatible unpacked-extension fallback. Installing the browser cache requires user approval.",
170
169
  recoverable: false,
171
- userAction: `ask the user for approval, then run: cd ${shellQuote(target)} && yarn playwright install chromium`,
170
+ userAction: `ask the user for approval, then run: cd ${shellQuoteArg(target)} && yarn playwright install chromium`,
172
171
  exitCode: EXIT.infra
173
172
  };
174
173
  }
@@ -177,7 +176,7 @@ const extensionLaunchProfile = {
177
176
  code: output.includes("EXTENSION_BACKGROUND_UNRESPONSIVE:") ? "EXTENSION_BACKGROUND_UNRESPONSIVE" : "EXTENSION_STARTUP_UNVERIFIED",
178
177
  message: "Extension startup is not healthy; inspect the background error before restarting or resetting wallet state.",
179
178
  recoverable: false,
180
- userAction: `mm-harness logs --target ${shellQuote(target)} --full`,
179
+ userAction: `mm-harness logs --target ${shellQuoteArg(target)} --full`,
181
180
  exitCode: EXIT.runtime,
182
181
  originalError: output.trim() || void 0
183
182
  };
@@ -187,7 +186,7 @@ const extensionLaunchProfile = {
187
186
  code: "EVM_RPC_EXECUTION_FAILED",
188
187
  message: "Background RPC fetch failed during JavaScript execution; this does not establish an Infura authentication or connectivity failure.",
189
188
  recoverable: false,
190
- userAction: `mm-harness logs --target ${shellQuote(target)} --full`,
189
+ userAction: `mm-harness logs --target ${shellQuoteArg(target)} --full`,
191
190
  exitCode: EXIT.runtime,
192
191
  originalError: output.trim() || void 0
193
192
  };
@@ -197,7 +196,7 @@ const extensionLaunchProfile = {
197
196
  code: "EVM_RPC_UNREACHABLE",
198
197
  message: "EVM RPC readiness failed; Infura configured does not mean RPC reachable.",
199
198
  recoverable: false,
200
- userAction: `Check the effective INFURA_PROJECT_ID from the environment, .metamaskprodrc, or ${shellQuote(path.join(target, ".metamaskrc"))}, then rerun: mm-harness launch --verify --target ${shellQuote(target)}`,
199
+ userAction: `Check the effective INFURA_PROJECT_ID from the environment, .metamaskprodrc, or ${shellQuoteArg(path.join(target, ".metamaskrc"))}, then rerun: mm-harness launch --verify --target ${shellQuoteArg(target)}`,
201
200
  exitCode: EXIT.runtime,
202
201
  originalError: output.trim() || void 0
203
202
  };
@@ -237,7 +236,7 @@ const extensionLaunchProfile = {
237
236
  message: pinned.message,
238
237
  recoverable: false,
239
238
  // A rebuild re-creates the snapshot; the flag is repeated only when one was asked for.
240
- userAction: `mm-harness launch --adapter extension --build${spec ? ` --remote-flag ${spec}` : ""} --target ${shellQuote(target)}`,
239
+ userAction: `mm-harness launch --adapter extension --build${spec ? ` --remote-flag ${spec}` : ""} --target ${shellQuoteArg(target)}`,
241
240
  exitCode: EXIT.runtime
242
241
  };
243
242
  },
@@ -1,9 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { recipeRuntimePath, EXIT, str, writeInteractiveProgress } from "@farmslot/recipe-cli/harness";
3
+ import { recipeRuntimePath, EXIT, str, writeInteractiveProgress, shellQuoteArg } from "@farmslot/recipe-cli/harness";
4
4
  import { applyDeviceTargeting } from "../../commands/device-target.js";
5
5
  import { launchMobile } from "../../commands/launch/mobile.js";
6
- import { shellQuote } from "../../commands/launch/quote.js";
7
6
  const METRO_RESTARTED = "metro.restarted";
8
7
  function mobileProvisioningBlocked(output) {
9
8
  return /open-device: configured iOS simulator '.+' does not exist|open-device: no MetaMask bundle found|fast mode requires an installed (?:iOS dev client|Android dev client)/u.test(output);
@@ -20,8 +19,8 @@ function mobileBridgeTargetMissing(output) {
20
19
  function mobileProvisionCommand(target, platformTarget) {
21
20
  const platform = platformTarget === "android" ? "android" : "ios";
22
21
  const device = platform === "ios" ? process.env.SIM_UDID || process.env.IOS_SIMULATOR : process.env.ADB_SERIAL || process.env.ANDROID_SERIAL || process.env.ANDROID_DEVICE;
23
- const parts = ["mm-harness", "provision", "runway", platform, "--adapter", "mobile", "--target", shellQuote(target)];
24
- if (device) parts.push("--device", shellQuote(device));
22
+ const parts = ["mm-harness", "provision", "runway", platform, "--adapter", "mobile", "--target", shellQuoteArg(target)];
23
+ if (device) parts.push("--device", shellQuoteArg(device));
25
24
  return parts.join(" ");
26
25
  }
27
26
  function isIosSimulatorUdid(value) {
@@ -90,7 +89,7 @@ const mobileLaunchProfile = {
90
89
  ok: false,
91
90
  code: "DEVICE_WRONG_PLATFORM",
92
91
  message: `--device ${device} did not resolve to an Android device for launch android.`,
93
- userAction: `mm-harness status --target ${shellQuote(target)} --all-devices --json`
92
+ userAction: `mm-harness status --target ${shellQuoteArg(target)} --all-devices --json`
94
93
  };
95
94
  }
96
95
  if (platformTarget === "ios" && device && !process.env.IOS_SIMULATOR) {
@@ -98,7 +97,7 @@ const mobileLaunchProfile = {
98
97
  ok: false,
99
98
  code: "DEVICE_WRONG_PLATFORM",
100
99
  message: `--device ${device} did not resolve to an iOS simulator for launch ios.`,
101
- userAction: `mm-harness status --target ${shellQuote(target)} --all-devices --json`
100
+ userAction: `mm-harness status --target ${shellQuoteArg(target)} --all-devices --json`
102
101
  };
103
102
  }
104
103
  return void 0;
@@ -154,7 +153,7 @@ const mobileLaunchProfile = {
154
153
  code: "MOBILE_DEVICE_AMBIGUOUS",
155
154
  message: `more than one iOS simulator matches ${ambiguousIosSimulator}.`,
156
155
  recoverable: false,
157
- userAction: `mm-harness status --adapter mobile --target ${shellQuote(target)} --all-devices --json`,
156
+ userAction: `mm-harness status --adapter mobile --target ${shellQuoteArg(target)} --all-devices --json`,
158
157
  exitCode: EXIT.runtime,
159
158
  originalError: output.trim() || void 0
160
159
  });
@@ -164,7 +163,7 @@ const mobileLaunchProfile = {
164
163
  code: "MOBILE_DEVICE_NOT_FOUND",
165
164
  message: `the configured iOS simulator ${missingIosSimulator} no longer exists.`,
166
165
  recoverable: false,
167
- userAction: `mm-harness status --adapter mobile --target ${shellQuote(target)} --all-devices --json`,
166
+ userAction: `mm-harness status --adapter mobile --target ${shellQuoteArg(target)} --all-devices --json`,
168
167
  exitCode: EXIT.runtime,
169
168
  originalError: output.trim() || void 0
170
169
  });
@@ -1,6 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
+ import { shellQuote } from "@farmslot/recipe-cli/harness";
4
5
  import { resolveLiveTsx } from "../live-tsx.js";
5
6
  const requireFromHarness = createRequire(import.meta.url);
6
7
  const CORE_RUNTIME_DEPS = ["immer"];
@@ -64,9 +65,6 @@ function readYarnNodeLinker(target) {
64
65
  return void 0;
65
66
  }
66
67
  }
67
- function shellQuote(value) {
68
- return `'${value.replace(/'/gu, `'\\''`)}'`;
69
- }
70
68
  export {
71
69
  coreDependencyBlock
72
70
  };
@@ -1,6 +1,6 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import fs from "node:fs";
3
- import { color, colorHumanMessage, recipeRuntimePath, handleHarness, EXIT, flag, str, checkHealBounds, ensureOverlay, newHealState } from "@farmslot/recipe-cli/harness";
3
+ import { color, colorHumanMessage, recipeRuntimePath, handleHarness, EXIT, flag, str, checkHealBounds, ensureOverlay, newHealState, shellQuoteArg } from "@farmslot/recipe-cli/harness";
4
4
  import { walletFixturePath } from "../../paths.js";
5
5
  import { getAdapterSurface } from "../../adapters/surface.js";
6
6
  import {
@@ -10,7 +10,6 @@ import {
10
10
  remoteFlagRequestFor
11
11
  } from "../../feature-flags.js";
12
12
  import { webDappOnlyLaunchFlags } from "@farmslot/adapter-web/web-dapp";
13
- import { shellQuote } from "./quote.js";
14
13
  async function runDeviceLaunch(context, profile) {
15
14
  const { adapter, options, target, heal, jsonOutput, machine, stream, usage } = context;
16
15
  const webDappOnly = webDappOnlyLaunchFlags(options);
@@ -268,7 +267,7 @@ function launchPass(launch, profile, state, remoteFlags) {
268
267
  }
269
268
  function launchFail(launch, profile, state, failure) {
270
269
  const { adapter, platformTarget, target, stream, tier } = launch;
271
- const userAction = failure.userAction ?? `mm-harness doctor --fix --adapter ${adapter} --target ${shellQuote(target)} --json`;
270
+ const userAction = failure.userAction ?? `mm-harness doctor --fix --adapter ${adapter} --target ${shellQuoteArg(target)} --json`;
272
271
  if (stream.enabled) {
273
272
  for (const mutation of state.mutations) stream.mutation(mutation);
274
273
  for (const recovery of state.recovered) stream.recovery(recovery);
@@ -4,6 +4,7 @@ import {
4
4
  shellQuoteArg
5
5
  } from "@farmslot/recipe-cli/harness";
6
6
  import { getAdapterSurface } from "../adapters/surface.js";
7
+ import { runRunwayProvision } from "./provision.js";
7
8
  const USAGE = `mm-harness \u2014 install and validate the MetaMask recipe runtime in a checkout.
8
9
 
9
10
  Run it from inside a MetaMask checkout; the platform (mobile | extension | core | terminal)
@@ -40,7 +41,7 @@ async function handleRunwayInstall(adapter, target, forward, json) {
40
41
  const surface = getAdapterSurface(adapter);
41
42
  const runtimeDir = argValue(forward, "--runtime-dir");
42
43
  const watcherPort = argValue(forward, "--watcher-port");
43
- const result = await surface.runwayProvision.run(target, {
44
+ return runRunwayProvision(surface, adapter, target, {
44
45
  json,
45
46
  platform: argValue(forward, "--platform") ?? "ios",
46
47
  branch: argValue(forward, "--branch"),
@@ -56,29 +57,15 @@ async function handleRunwayInstall(adapter, target, forward, json) {
56
57
  force: hasArg(forward, "--force"),
57
58
  resolveOnly: hasArg(forward, "--resolve-only"),
58
59
  rerunCommand
60
+ }, {
61
+ command: "mm-harness install --runway",
62
+ failed: "runway install failed",
63
+ resolved: `Runway app for ${adapter}`,
64
+ installed: "installed Runway app",
65
+ subject: `for ${adapter}`,
66
+ cacheFallback: "skip",
67
+ quoteTarget: shellQuoteArg
59
68
  });
60
- const provisionedSimulator = typeof result.simulator === "object" && result.simulator ? result.simulator : void 0;
61
- const provisionedDevice = typeof provisionedSimulator?.udid === "string" ? provisionedSimulator.udid : typeof provisionedSimulator?.name === "string" ? provisionedSimulator.name : void 0;
62
- const next = result.status === "pass" && surface.runwayProvision.launchNext && result.resolveOnly !== true ? `mm-harness launch ${String(result.platform ?? "ios")} --adapter ${adapter} --target ${shellQuoteArg(target)}` + (provisionedDevice ? ` --device ${shellQuoteArg(provisionedDevice)}` : "") : void 0;
63
- const output = next ? { ...result, next } : result;
64
- if (json) {
65
- console.log(JSON.stringify(output, null, 2));
66
- } else if (result.status === "pass") {
67
- const cache = typeof result.cache === "object" && result.cache ? result.cache : void 0;
68
- const simulator = provisionedSimulator;
69
- const artifact = typeof result.artifact === "object" && result.artifact ? result.artifact : void 0;
70
- if (result.resolveOnly) {
71
- console.error(`\u2713 resolved Runway app for ${adapter} ${result.platform ?? ""} run=${artifact?.runId ?? "unknown"} revision=${artifact?.revision ?? "unknown"} artifact=${artifact?.artifactName ?? "unknown"}`);
72
- } else {
73
- const action = result.skipped ? "already provisioned" : "installed Runway app";
74
- console.error(`\u2713 ${action} for ${adapter} ${result.platform ?? ""} simulator=${simulator?.name ?? "unknown"} udid=${simulator?.udid ?? "unknown"} cache=${cache?.status ?? "skip"}`);
75
- }
76
- if (next) console.error(` Next: ${next}`);
77
- } else {
78
- console.error(`\u2717 mm-harness install --runway: ${result.error?.message ?? "runway install failed"}
79
- Next: ${result.error?.userAction ?? rerunCommand}`);
80
- }
81
- return result.exitCode;
82
69
  }
83
70
  function runwayInstallRerunCommand(adapter, target, forward, json) {
84
71
  const parts = ["mm-harness", "install", "--runway", "--adapter", adapter, "--target", shellQuoteArg(target)];
@@ -1,5 +1,4 @@
1
- import { contextAdapter, adapterDetectNext, usageOut, applyWatcherPortOption, optionFlag, optionString, shellQuote, shellQuoteArg, targetPath } from "@farmslot/recipe-cli/harness";
2
- import { assertAdapter } from "../paths.js";
1
+ import { assertAdapter, contextAdapter, adapterDetectNext, usageOut, applyWatcherPortOption, optionFlag, optionString, shellQuote, shellQuoteArg, targetPath } from "@farmslot/recipe-cli/harness";
3
2
  import { getAdapterSurface } from "../adapters/surface.js";
4
3
  async function handleProvision({ positional, options, rawArgv }) {
5
4
  applyWatcherPortOption(options);
@@ -18,7 +17,7 @@ async function handleProvision({ positional, options, rawArgv }) {
18
17
  const rerunCommand = provisionRerunCommand(rawArgv, options, adapter, target);
19
18
  const platform = optionString(options, "platform") ?? optionString(options, "devicePlatform") ?? (positional[0] === "runway" ? positional[1] : positional[0]) ?? "ios";
20
19
  surface.runwayProvision.before?.(target, { json, platform, resolveOnly: optionFlag(options, "resolveOnly") });
21
- const result = await surface.runwayProvision.run(target, {
20
+ return runRunwayProvision(surface, adapter, target, {
22
21
  json,
23
22
  platform,
24
23
  branch: optionString(options, "branch"),
@@ -34,27 +33,38 @@ async function handleProvision({ positional, options, rawArgv }) {
34
33
  force: optionFlag(options, "force"),
35
34
  resolveOnly: optionFlag(options, "resolveOnly"),
36
35
  rerunCommand
36
+ }, {
37
+ command: "mm-harness provision",
38
+ failed: "provision failed",
39
+ resolved: adapter,
40
+ installed: "provisioned",
41
+ subject: adapter,
42
+ cacheFallback: "unknown",
43
+ quoteTarget: shellQuote
37
44
  });
45
+ }
46
+ async function runRunwayProvision(surface, adapter, target, options, labels) {
47
+ const result = await surface.runwayProvision.run(target, options);
38
48
  const provisionedSimulator = typeof result.simulator === "object" && result.simulator ? result.simulator : void 0;
39
49
  const provisionedDevice = typeof provisionedSimulator?.udid === "string" ? provisionedSimulator.udid : typeof provisionedSimulator?.name === "string" ? provisionedSimulator.name : void 0;
40
- const next = result.status === "pass" && surface.runwayProvision.launchNext && result.resolveOnly !== true ? `mm-harness launch ${String(result.platform ?? platform)} --adapter ${adapter} --target ${shellQuote(target)}` + (provisionedDevice ? ` --device ${shellQuoteArg(provisionedDevice)}` : "") : void 0;
50
+ const next = result.status === "pass" && surface.runwayProvision.launchNext && result.resolveOnly !== true ? `mm-harness launch ${String(result.platform ?? options.platform)} --adapter ${adapter} --target ${labels.quoteTarget(target)}` + (provisionedDevice ? ` --device ${shellQuoteArg(provisionedDevice)}` : "") : void 0;
41
51
  const output = next ? { ...result, next } : result;
42
- if (json) {
52
+ if (options.json) {
43
53
  console.log(JSON.stringify(output, null, 2));
44
54
  } else if (result.status === "pass") {
45
55
  const cache = typeof result.cache === "object" && result.cache ? result.cache : void 0;
46
56
  const simulator = provisionedSimulator;
47
57
  const artifact = typeof result.artifact === "object" && result.artifact ? result.artifact : void 0;
48
58
  if (result.resolveOnly) {
49
- console.error(`\u2713 resolved ${adapter} ${result.platform ?? ""} run=${artifact?.runId ?? "unknown"} revision=${artifact?.revision ?? "unknown"} artifact=${artifact?.artifactName ?? "unknown"}`);
59
+ console.error(`\u2713 resolved ${labels.resolved} ${result.platform ?? ""} run=${artifact?.runId ?? "unknown"} revision=${artifact?.revision ?? "unknown"} artifact=${artifact?.artifactName ?? "unknown"}`);
50
60
  } else {
51
- const action = result.skipped ? "already provisioned" : "provisioned";
52
- console.error(`\u2713 ${action} ${adapter} ${result.platform ?? ""} simulator=${simulator?.name ?? "unknown"} udid=${simulator?.udid ?? "unknown"} cache=${cache?.status ?? "unknown"}`);
61
+ const action = result.skipped ? "already provisioned" : labels.installed;
62
+ console.error(`\u2713 ${action} ${labels.subject} ${result.platform ?? ""} simulator=${simulator?.name ?? "unknown"} udid=${simulator?.udid ?? "unknown"} cache=${cache?.status ?? labels.cacheFallback}`);
53
63
  }
54
64
  if (next) console.error(` Next: ${next}`);
55
65
  } else {
56
- console.error(`\u2717 mm-harness provision: ${result.error?.message ?? "provision failed"}
57
- Next: ${result.error?.userAction ?? rerunCommand}`);
66
+ console.error(`\u2717 ${labels.command}: ${result.error?.message ?? labels.failed}
67
+ Next: ${result.error?.userAction ?? options.rerunCommand}`);
58
68
  }
59
69
  return result.exitCode;
60
70
  }
@@ -129,5 +139,6 @@ function findRawOption(rawArgv, flags) {
129
139
  return void 0;
130
140
  }
131
141
  export {
132
- handleProvision
142
+ handleProvision,
143
+ runRunwayProvision
133
144
  };
package/dist/engine.js CHANGED
@@ -1,4 +1,3 @@
1
- import path from "node:path";
2
1
  import { getAdapterSurface } from "./adapters/surface.js";
3
2
  import { consoleClassifier } from "./console-classifier.js";
4
3
  import {
@@ -9,18 +8,13 @@ import {
9
8
  loadTrustedMutationBase,
10
9
  validateTrustedMutationPlan
11
10
  } from "./funding-execution-context.js";
12
- import { resolveActionManifest, validateManifest } from "./manifest.js";
13
- import { LIBRARY_ACTION_NAMESPACE, runnerDir } from "./paths.js";
11
+ import { BUNDLED_LIBRARY, resolveActionManifest, validateManifest } from "./manifest.js";
14
12
  import { metaMaskActionExecutionCapabilities } from "./recipe-security.js";
15
13
  async function loadEngineMutation({ cli, adapter, ...run }) {
16
14
  return await loadTrustedMutationBase({ ...fundingOptionsFromCli(cli), ...run }) ?? (getAdapterSurface(adapter).actions.trustedMutation === true ? fixturePolicyMutationBase({ artifactsDir: run.artifactsDir, rootRecipe: run.rootRecipe }) : void 0);
17
15
  }
18
16
  const metaMaskEngine = {
19
- bundledLibrary: {
20
- name: "metamask",
21
- root: path.resolve(runnerDir, "library"),
22
- actionNamespace: LIBRARY_ACTION_NAMESPACE
23
- },
17
+ bundledLibrary: BUNDLED_LIBRARY,
24
18
  resolveActionManifest,
25
19
  validateManifest,
26
20
  actionCapabilities: metaMaskActionExecutionCapabilities,
package/dist/manifest.js CHANGED
@@ -1,8 +1,8 @@
1
- import fs from "node:fs";
2
1
  import path from "node:path";
3
2
  import { OFFICIAL_RECIPE_ACTIONS } from "@farmslot/protocol";
3
+ import { createRuntimeRecipeCatalog } from "@farmslot/recipe-cli/harness";
4
4
  import { getAdapterSurface } from "./adapters/surface.js";
5
- import { manifestPath, readJson, importRecipeProtocol, runnerDir } from "./paths.js";
5
+ import { LIBRARY_ACTION_NAMESPACE, manifestPath, readJson, importRecipeProtocol, runnerDir } from "./paths.js";
6
6
  function loadMetaMaskMobileActionManifest() {
7
7
  return asActionManifest(readJson(manifestPath("mobile")));
8
8
  }
@@ -24,155 +24,38 @@ class ActionManifestError extends Error {
24
24
  this.userAction = `fix the ${adapter} action manifest the message names, then rerun`;
25
25
  }
26
26
  }
27
+ const BUNDLED_LIBRARY = {
28
+ name: "metamask",
29
+ root: path.resolve(runnerDir, "library"),
30
+ actionNamespace: LIBRARY_ACTION_NAMESPACE
31
+ };
27
32
  async function resolveActionManifest(adapter, overridePath, librarySources, taskActionRoot) {
28
33
  try {
29
- return await resolveActionManifestSources(adapter, overridePath, librarySources, taskActionRoot);
34
+ const catalog = createRuntimeRecipeCatalog({ runtime: getAdapterSurface(adapter), bundledLibrary: BUNDLED_LIBRARY });
35
+ return officialActions(
36
+ await catalog.resolveActionManifest(adapter, overridePath, librarySources, taskActionRoot),
37
+ !overridePath
38
+ );
30
39
  } catch (error) {
31
- throw error instanceof ActionManifestError ? error : new ActionManifestError(adapter, error);
40
+ throw new ActionManifestError(adapter, error);
32
41
  }
33
42
  }
34
- async function resolveActionManifestSources(adapter, overridePath, librarySources, taskActionRoot) {
35
- if (overridePath) {
36
- const manifestPath2 = path.resolve(overridePath);
37
- const implementationRoot = path.resolve(
38
- taskActionRoot ?? defaultImplementationRoot(manifestPath2)
39
- );
40
- const manifest = loadActionManifest(adapter, manifestPath2);
41
- await validateManifest(manifest);
42
- return {
43
- manifest,
44
- actionSources: await describeManifestSources(manifest, {
45
- name: "task",
46
- tier: "task",
47
- manifestPath: manifestPath2,
48
- implementationRoot,
49
- trust: "unknown"
50
- })
51
- };
52
- }
53
- const surfaceActions = getAdapterSurface(adapter).actions;
54
- const canonicalPath = path.resolve(surfaceActions.manifestPath());
55
- const canonicalRoot = path.dirname(path.dirname(canonicalPath));
56
- const bundledRoot = path.resolve(runnerDir, "library");
57
- const canonicalName = canonicalRoot === bundledRoot ? "metamask" : path.basename(canonicalRoot);
58
- const sources = librarySources?.length ? librarySources : [{ name: canonicalName, root: canonicalRoot }];
59
- const manifests = [];
60
- for (const source of sources) {
61
- const file = path.resolve(source.root, "manifests", `${adapter}.action-manifest.json`);
62
- if (!fs.existsSync(file)) continue;
63
- const manifest = asActionManifest(readJson(file));
64
- await validateManifest(manifest);
65
- manifests.push({ source, file, manifest });
66
- }
67
- for (const declared of surfaceActions.manifestPaths?.() ?? []) {
68
- const file = path.resolve(declared);
69
- if (file === canonicalPath || manifests.some((entry) => entry.file === file)) continue;
70
- const manifest = asActionManifest(readJson(file));
71
- await validateManifest(manifest);
72
- const root = path.dirname(path.dirname(file));
73
- const source = sources.find((candidate) => path.resolve(candidate.root) === root) ?? { name: path.basename(root), root };
74
- manifests.push({ source, file, manifest });
75
- }
76
- if (!manifests.some((entry) => entry.file === canonicalPath)) {
77
- const manifest = loadActionManifest(adapter);
78
- await validateManifest(manifest);
79
- manifests.push({
80
- source: { name: canonicalName, root: canonicalRoot },
81
- file: canonicalPath,
82
- manifest
83
- });
84
- }
85
- const canonical = manifests.find((entry) => entry.file === canonicalPath);
86
- if (!canonical) throw new Error(`No ${adapter} action manifest could be resolved.`);
87
- const isBundled = (entry) => path.resolve(entry.source.root) === bundledRoot;
88
- const bundledParents = manifests.filter((entry) => entry !== canonical && isBundled(entry));
89
- const official = new Set(OFFICIAL_RECIPE_ACTIONS);
90
- const mergedActions = {};
91
- const actionSources = /* @__PURE__ */ new Map();
92
- for (const entry of [canonical, ...bundledParents]) {
93
- for (const [name, action] of Object.entries(actionsOf(entry.manifest))) {
94
- if (!official.has(name) || name in mergedActions) continue;
95
- mergedActions[name] = action;
96
- actionSources.set(name, { name: "official", tier: "official", manifestPath: entry.file });
97
- }
98
- }
99
- const observers = bundledParents.length ? [canonical, ...bundledParents].flatMap((entry) => entry.manifest.observers ?? []).filter((observer, index, all) => all.findIndex((other) => other.ref === observer.ref) === index) : canonical.manifest.observers;
100
- const custom = /* @__PURE__ */ new Set();
101
- const ordered = [
102
- ...manifests.filter((entry) => entry.file !== canonicalPath),
103
- canonical
104
- ];
105
- for (const entry of ordered) {
106
- const isCanonical = entry.file === canonicalPath;
107
- const bundled = isBundled(entry);
108
- if (!isCanonical && !bundled && entry.manifest.observers?.length) {
109
- throw new Error(
110
- `Library manifest ${entry.file} declares observers, but external library observers are not executable; remove observers or use an explicit task action manifest.`
111
- );
112
- }
113
- const sourceName = bundled ? "metamask" : entry.source.name ?? path.basename(entry.source.root);
114
- const tier = bundled ? "canonical" : sourceName === "personal" ? "personal" : "team";
115
- const implementationRoot = path.join(entry.source.root, "actions");
116
- const sourceInfo = {
117
- name: sourceName,
118
- tier,
119
- manifestPath: entry.file,
120
- implementationRoot,
121
- trust: entry.source.provenance?.trust ?? (bundled ? "trusted" : "unknown")
122
- };
123
- for (const [name, action] of Object.entries(actionsOf(entry.manifest))) {
124
- if (official.has(name)) {
125
- if (!isCanonical && !bundled) {
126
- throw new Error(
127
- `Library action ${name} from ${entry.file} conflicts with an official action; team libraries may declare namespaced custom actions only.`
128
- );
129
- }
130
- continue;
131
- }
132
- if (custom.has(name)) {
133
- const existing = actionSources.get(name);
134
- if (existing && existing.name !== sourceName) {
135
- existing.shadows = [...existing.shadows ?? [], sourceName];
136
- }
137
- continue;
138
- }
139
- custom.add(name);
140
- mergedActions[name] = action;
141
- actionSources.set(name, { ...sourceInfo });
43
+ const OFFICIAL = new Set(OFFICIAL_RECIPE_ACTIONS);
44
+ function officialActions({ manifest, actionSources }, listFirst) {
45
+ const names = Object.keys(manifest.actions);
46
+ for (const name of names) {
47
+ const source = actionSources.get(name);
48
+ if (OFFICIAL.has(name) && source) {
49
+ actionSources.set(name, { name: "official", tier: "official", manifestPath: source.manifestPath });
142
50
  }
143
51
  }
52
+ if (!listFirst) return { manifest, actionSources };
53
+ const ordered = [...names.filter((name) => OFFICIAL.has(name)), ...names.filter((name) => !OFFICIAL.has(name))];
144
54
  return {
145
- manifest: {
146
- $schema: canonical.manifest.$schema,
147
- actions: mergedActions,
148
- ...observers ? { observers } : {}
149
- },
55
+ manifest: { ...manifest, actions: Object.fromEntries(ordered.map((name) => [name, manifest.actions[name]])) },
150
56
  actionSources
151
57
  };
152
58
  }
153
- function actionsOf(manifest) {
154
- return manifest.actions;
155
- }
156
- async function describeManifestSources(manifest, customSource) {
157
- const official = new Set(OFFICIAL_RECIPE_ACTIONS);
158
- const sources = /* @__PURE__ */ new Map();
159
- for (const name of Object.keys(actionsOf(manifest))) {
160
- if (official.has(name)) {
161
- sources.set(name, {
162
- name: "official",
163
- tier: "official",
164
- manifestPath: customSource.manifestPath
165
- });
166
- continue;
167
- }
168
- sources.set(name, { ...customSource });
169
- }
170
- return sources;
171
- }
172
- function defaultImplementationRoot(selectedManifestPath) {
173
- const manifestDirectory = path.dirname(selectedManifestPath);
174
- return path.basename(manifestDirectory) === "manifests" ? path.join(path.dirname(manifestDirectory), "actions") : path.join(manifestDirectory, "actions");
175
- }
176
59
  async function validateManifest(manifest) {
177
60
  const { validateRecipeActionManifestDocument } = await importRecipeProtocol();
178
61
  const result = validateRecipeActionManifestDocument(manifest);
@@ -188,6 +71,7 @@ function asActionManifest(value) {
188
71
  }
189
72
  export {
190
73
  ActionManifestError,
74
+ BUNDLED_LIBRARY,
191
75
  loadActionManifest,
192
76
  loadMetaMaskCoreActionManifest,
193
77
  loadMetaMaskExtensionActionManifest,
package/dist/paths.js CHANGED
@@ -1,8 +1,7 @@
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";
5
- import { adapterRegistry } from "./adapters/registry.js";
4
+ import { assertAdapter, recipeRuntimeDir, recipeRuntimePath } from "@farmslot/recipe-cli/harness";
6
5
  const runnerDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
7
6
  function walletFixturePath(projectRoot) {
8
7
  return recipeRuntimePath(projectRoot, "wallet-fixture.json");
@@ -67,16 +66,6 @@ function holdsSlotPool(candidate) {
67
66
  }
68
67
  });
69
68
  }
70
- function assertAdapter(adapter) {
71
- const ids = adapterRegistry.list();
72
- if (ids.length === 0) {
73
- throw new Error("internal: no adapters are registered yet; import src/adapters/surface.ts before validating an adapter");
74
- }
75
- if (typeof adapter !== "string" || !adapterRegistry.has(adapter)) {
76
- const choices = ids.length > 1 ? `${ids.slice(0, -1).join(", ")}, or ${ids.at(-1)}` : ids.join("");
77
- throw new Error(`Adapter must be ${choices}.`);
78
- }
79
- }
80
69
  function manifestPath(adapter) {
81
70
  assertAdapter(adapter);
82
71
  return path.join(runnerDir, "library/manifests", `${adapter}.action-manifest.json`);
@@ -156,7 +145,6 @@ function isMissingPackageError(error, packageName) {
156
145
  }
157
146
  export {
158
147
  LIBRARY_ACTION_NAMESPACE,
159
- assertAdapter,
160
148
  extensionIdPath,
161
149
  harnessSlotPoolDir,
162
150
  importMetaMaskNativeUiProvider,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.92.29",
3
+ "version": "0.92.31",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"
@@ -22,13 +22,13 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@farmslot/adapter-node": "0.1.39",
25
- "@farmslot/adapter-rn": "0.22.28",
25
+ "@farmslot/adapter-rn": "0.22.29",
26
26
  "@farmslot/adapter-sdk": "0.18.5",
27
27
  "@farmslot/adapter-web": "0.10.20",
28
28
  "@farmslot/agent-runtime": "0.29.5",
29
29
  "@farmslot/handoff": "0.3.2",
30
30
  "@farmslot/protocol": "0.55.0",
31
- "@farmslot/recipe-cli": "0.23.6",
31
+ "@farmslot/recipe-cli": "0.23.8",
32
32
  "@farmslot/recipe-runner": "0.39.5",
33
33
  "agent-device": "0.19.3",
34
34
  "commander": "^12.0.0",
@@ -1,240 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { listRecipeFiles } from "./recipe-files.js";
5
- const SPEC = {
6
- shim: [
7
- { name: "sync", desc: "Copy fixture, write slot context, install harness", flags: ["--dry-run", "--no-install"] },
8
- { name: "help", aliases: ["-h", "--help"], desc: "Show usage" }
9
- ],
10
- shared: [
11
- { name: "help", desc: "Load version-matched recipe guidance (help review composes the review guide)", args: ["review"], flags: ["--json", "--adapter", "--target", "--domain"] },
12
- { name: "review", desc: "Materialize a review checklist composed from the base review and a team library", args: ["checklist"], flags: ["--domain", "--since", "--base", "--out", "--adapter", "--target", "--json"] },
13
- { name: "domain", desc: "Which team library owns the change (declared value, else owned-paths.json)", flags: ["--domain", "--base", "--adapter", "--target", "--json"] },
14
- { name: "pr-body", desc: "Render the publishable PR body: pr-description.md plus the recipe and run sections built from artifacts", args: ["render", "<task-dir>"], flags: ["--command", "--out", "--json"] },
15
- { name: "config", desc: "Per-engineer locations and optional advisor settings", args: ["list", "path", "get", "set", "unset"], flags: ["--json"] },
16
- { name: "tutorial", desc: "Open the visual recipe tutorial", flags: ["--json", "--no-open"] },
17
- { name: "setup-base", desc: "Bootstrap numbered product checkouts", flags: ["--dir", "--counts", "--only", "--dry-run", "--force", "--json", "--show-config", "--reset-config", "--skip-harness-update"] },
18
- { name: "status", desc: "Home status + next commands", flags: ["--json", "--fast", "--watch", "--task"] },
19
- { name: "ports", desc: "Slot ports and runtime paths", flags: ["--json"] },
20
- { name: "up", desc: "Decide + run minimum work to reach ready", flags: ["--json", "--dry-run"] },
21
- { name: "sync", desc: "Refresh harness + canonicalize wallet fixture", flags: ["--json"] },
22
- { name: "pin", desc: "Copy this install into an immutable store for a task to run", flags: ["--json"] },
23
- { name: "logs", aliases: ["tail"], desc: "Compact build events or full log", flags: ["--full", "-f", "--window", "--events", "--source", "--json"] },
24
- { name: "debug", aliases: ["devtools", "inspect"], desc: "Open DevTools UI", flags: ["--json", "--no-open"] },
25
- { name: "reload", desc: "Reload the connected app runtime", flags: ["--json"] },
26
- { name: "fixtures", desc: "Manage the canonical wallet fixture (sync/set/reset/generate)", args: ["sync", "set", "reset", "generate"], flags: ["--fixture", "--out", "--adapter", "--target", "--device", "--json"] },
27
- { name: "actions", desc: "List runnable recipe actions", flags: ["--json", "--matrix", "--categories", "--category", "--action", "--library"] },
28
- { name: "doctor", desc: "Check harness/orchestration health", flags: ["--json", "--target", "--adapter", "--runtime-dir", "--expect-live", "--print-ready", "--cdp-port", "--device"] },
29
- { name: "run", desc: "Execute a proof recipe (path or library name, e.g. run perps.smoke)", args: ["recipe.json|name"], flags: ["--list", "--device", "--plan", "--proof"] },
30
- { name: "execution-template", desc: "Discover and materialize shared agent checklists", args: ["list", "materialize", "lint", "new"], flags: ["--dir", "--domain-dir", "--project-worker", "--project-name", "--package-templates", "--package-id", "--flow", "--run-mode", "--platform", "--domain", "--id", "--provenance", "--include-shadowed", "--no-include-shadowed", "--title", "--force", "--json"] },
31
- { name: "recipe-quality", desc: "Build canonical quality or request optional recipe planning/advice", args: ["build", "plan", "advise", "advisor-status"], flags: ["--input", "--output", "--recipe", "--task", "--evidence", "--params", "--adapter", "--library", "--advisor", "--advisor-model", "--advisor-stages", "--json"] },
32
- { name: "interactive", aliases: ["menu"], desc: "Interactive command menu" },
33
- { name: "prepare", desc: "Run the readiness chain and write the readiness record", flags: ["--target", "--platform", "--mobile-platform", "--artifacts-dir", "--runtime-dir", "--json"] },
34
- { name: "task", desc: "Create a task directory from a shared execution template", args: ["init"], flags: ["--flow", "--run-mode", "--platform", "--template", "--title", "--task-text", "--task-file", "--ticket", "--source-ref", "--surface", "--project", "--repo", "--attempt-id", "--var", "--task-dir-label", "--mode-preamble", "--addendum-file", "--mark-command", "--dir", "--domain-dir", "--project-worker", "--project-name", "--package-templates", "--package-id", "--domain", "--json"] },
35
- { name: "runtime-status", desc: "Structured runtime status JSON", flags: ["--json", "--target", "--cdp-port", "--runtime-dir"] }
36
- ],
37
- extension: [
38
- { name: "decision", aliases: ["decide"], desc: "Runtime decision (what next?)" },
39
- { name: "update", aliases: ["sync-runtime"], desc: "Perform recommended runtime action" },
40
- { name: "ready", aliases: ["ensure-ready"], desc: "Ensure one healthy extension home tab" },
41
- { name: "watch", aliases: ["start-watch", "watcher"], desc: "Start/reuse webpack watcher", flags: ["--full", "-f"] },
42
- { name: "stop", aliases: ["stop-watch", "stop-watcher"], desc: "Stop this checkout watcher" },
43
- { name: "rebuild", aliases: ["reset", "reopen", "browser", "launch", "full-launch", "runtime-launch", "build", "build:once", "build-once", "refresh-once"], desc: "Clean webpack + browser launch", flags: ["--json", "--dry-run", "--full"] },
44
- { name: "sidepanel", desc: "Side panel helper", args: ["cycle", "open", "close", "toggle", "status"] },
45
- { name: "prepare", flags: ["--target", "--cdp-port", "--runtime-dir", "--validate"] },
46
- { name: "debug", args: ["page", "worker"], flags: ["--json", "--no-open"] },
47
- { name: "reload", desc: "Reload the live Extension UI over CDP", flags: ["--json"] }
48
- ],
49
- mobile: [
50
- { name: "ios", aliases: ["start"], desc: "Start Metro + launch iOS dev client" },
51
- { name: "android", desc: "Start Metro + launch Android dev client" },
52
- { name: "setup:ios", desc: "iOS launch + wallet fixture setup" },
53
- { name: "setup:android", desc: "Android launch + wallet fixture setup" },
54
- { name: "stop", desc: "Stop Metro for this slot" },
55
- { name: "app-status", aliases: ["bridge-status"], desc: "Bridge/runtime status" },
56
- { name: "route", aliases: ["get-route"], desc: "Current navigation route" },
57
- { name: "navigate", desc: "Navigate by route name", args: ["RouteName"] },
58
- { name: "back", aliases: ["go-back"], desc: "Navigate back" },
59
- { name: "unlock", desc: "Unlock with fixture password", args: ["password"] },
60
- { name: "setup-wallet", aliases: ["wallet-setup"], desc: "Apply wallet fixture", args: ["fixture.json"] },
61
- { name: "accounts", aliases: ["list-accounts"], desc: "List accounts" },
62
- { name: "select-account", aliases: ["switch-account"], desc: "Switch account", args: ["address"] },
63
- { name: "screenshot", desc: "Capture simulator/device screenshot", args: ["path"] },
64
- { name: "dev-menu", aliases: ["devmenu"], desc: "Open RN developer menu", flags: ["--json", "--no-open"] },
65
- { name: "prepare", flags: ["--target", "--platform", "--preflight-mode", "--port", "--simulator", "--adb-serial", "--runtime-dir", "--wallet-setup", "--wallet-fixture"] },
66
- { name: "debug", flags: ["--json", "--no-open", "--action"] },
67
- { name: "reload", desc: "Reload the app without restarting Metro", flags: ["--json"] }
68
- ]
69
- };
70
- const GLOBAL_FLAGS = ["--json", "--dry-run", "--full", "-f", "--help", "-h", "--no-open", "--no-color"];
71
- function mergeCommands(adapter) {
72
- const byName = /* @__PURE__ */ new Map();
73
- const add = (cmd) => {
74
- const existing = byName.get(cmd.name);
75
- if (!existing) {
76
- byName.set(cmd.name, {
77
- ...cmd,
78
- aliases: [...cmd.aliases || []],
79
- flags: [...cmd.flags || []],
80
- args: [...cmd.args || []]
81
- });
82
- return;
83
- }
84
- existing.aliases = [.../* @__PURE__ */ new Set([...existing.aliases || [], ...cmd.aliases || []])];
85
- existing.flags = [.../* @__PURE__ */ new Set([...existing.flags || [], ...cmd.flags || []])];
86
- existing.args = [.../* @__PURE__ */ new Set([...existing.args || [], ...cmd.args || []])];
87
- if (!existing.desc && cmd.desc) existing.desc = cmd.desc;
88
- };
89
- for (const cmd of SPEC.shared) add(cmd);
90
- for (const cmd of SPEC[adapter] || []) add(cmd);
91
- return [...byName.values()];
92
- }
93
- function commandTokens(adapter, { includeAliases = true } = {}) {
94
- const tokens = /* @__PURE__ */ new Set();
95
- for (const cmd of mergeCommands(adapter)) {
96
- tokens.add(cmd.name);
97
- if (includeAliases) for (const alias of cmd.aliases || []) tokens.add(alias);
98
- }
99
- return [...tokens].sort();
100
- }
101
- function shimCommandTokens() {
102
- const tokens = /* @__PURE__ */ new Set();
103
- for (const cmd of SPEC.shim) {
104
- tokens.add(cmd.name);
105
- for (const alias of cmd.aliases || []) tokens.add(alias);
106
- }
107
- return [...tokens].sort();
108
- }
109
- function lookupCommand(adapter, token) {
110
- for (const cmd of mergeCommands(adapter)) {
111
- if (cmd.name === token) return cmd;
112
- if ((cmd.aliases || []).includes(token)) return cmd;
113
- }
114
- for (const cmd of SPEC.shim) {
115
- if (cmd.name === token) return cmd;
116
- if ((cmd.aliases || []).includes(token)) return cmd;
117
- }
118
- return null;
119
- }
120
- function flagsForPosition(adapter, commandToken) {
121
- const cmd = lookupCommand(adapter, commandToken);
122
- const flags = new Set(GLOBAL_FLAGS);
123
- if (cmd) for (const flag of cmd.flags || []) flags.add(flag);
124
- return [...flags].sort();
125
- }
126
- function argsForPosition(adapter, commandToken, argIndex) {
127
- const cmd = lookupCommand(adapter, commandToken);
128
- if (!cmd?.args?.length) return [];
129
- const value = cmd.args[Math.max(0, argIndex)];
130
- return value ? [value] : cmd.args;
131
- }
132
- const RUN_RECIPE_DIRS = [
133
- (adapter, root) => path.join(root, "temp/recipe/harness", adapter, "runner/recipes"),
134
- (adapter, root) => path.join(root, "temp/agentic/recipe-harness", adapter, "runner/recipes")
135
- ];
136
- function listRunRecipes(adapter, root, cwd = process.cwd()) {
137
- const byBase = /* @__PURE__ */ new Map();
138
- for (const resolveDir of RUN_RECIPE_DIRS) {
139
- const dir = resolveDir(adapter, root);
140
- if (!fs.existsSync(dir)) continue;
141
- for (const rel of listRecipeFiles(dir)) {
142
- if (byBase.has(rel)) continue;
143
- byBase.set(rel, path.join(dir, rel));
144
- }
145
- }
146
- return [...byBase.values()].map((abs) => path.relative(cwd, abs)).sort();
147
- }
148
- function usage() {
149
- process.stderr.write(`Usage:
150
- cli-commands dump --adapter extension|mobile|shim [--aliases]
151
- cli-commands describe --adapter extension|mobile|shim
152
- cli-commands flags --adapter <adapter> --command <name>
153
- cli-commands args --adapter <adapter> --command <name> [--index <n>]
154
- cli-commands run-recipes --adapter extension|mobile --root <repo> [--cwd <dir>]
155
- cli-commands global-flags
156
- `);
157
- }
158
- async function main() {
159
- const { parseArgs: parseArgsUtil } = await import("node:util");
160
- const { values, positionals } = parseArgsUtil({
161
- allowPositionals: true,
162
- args: process.argv.slice(2),
163
- options: {
164
- adapter: { type: "string" },
165
- command: { type: "string" },
166
- aliases: { type: "boolean", default: true },
167
- index: { type: "string", default: "0" },
168
- root: { type: "string" },
169
- cwd: { type: "string" }
170
- }
171
- });
172
- const command = positionals[0];
173
- if (!command || command === "--help" || command === "-h") {
174
- usage();
175
- process.exit(command ? 0 : 2);
176
- }
177
- if (command === "global-flags") {
178
- process.stdout.write(`${GLOBAL_FLAGS.join("\n")}
179
- `);
180
- return;
181
- }
182
- const adapter = values.adapter;
183
- if (!adapter && command !== "global-flags") {
184
- usage();
185
- process.exit(2);
186
- }
187
- if (command === "dump") {
188
- process.stdout.write(`${commandTokens(adapter, { includeAliases: values.aliases }).join("\n")}
189
- `);
190
- return;
191
- }
192
- if (command === "describe") {
193
- const rows = adapter === "shim" ? SPEC.shim : mergeCommands(adapter);
194
- for (const cmd of rows) {
195
- const aliases = (cmd.aliases || []).join("|");
196
- const label = aliases ? `${cmd.name}|${aliases}` : cmd.name;
197
- process.stdout.write(`${label} ${cmd.desc || cmd.name}
198
- `);
199
- }
200
- return;
201
- }
202
- if (command === "flags") {
203
- process.stdout.write(`${flagsForPosition(adapter, values.command || "").join("\n")}
204
- `);
205
- return;
206
- }
207
- if (command === "args") {
208
- const index = Number.parseInt(values.index, 10) || 0;
209
- process.stdout.write(`${argsForPosition(adapter, values.command || "", index).join("\n")}
210
- `);
211
- return;
212
- }
213
- if (command === "run-recipes") {
214
- const root = values.root ? path.resolve(values.root) : process.cwd();
215
- const cwd = values.cwd ? path.resolve(values.cwd) : process.cwd();
216
- process.stdout.write(`${listRunRecipes(adapter, root, cwd).join("\n")}
217
- `);
218
- return;
219
- }
220
- usage();
221
- process.exit(2);
222
- }
223
- const _filename = fileURLToPath(import.meta.url);
224
- const isMain = process.argv[1] && path.resolve(process.argv[1]) === _filename;
225
- if (isMain) {
226
- main().catch((error) => {
227
- process.stderr.write(`${error instanceof Error ? error.message : String(error)}
228
- `);
229
- process.exit(1);
230
- });
231
- }
232
- export {
233
- SPEC,
234
- argsForPosition,
235
- commandTokens,
236
- flagsForPosition,
237
- listRunRecipes,
238
- lookupCommand,
239
- shimCommandTokens
240
- };
@@ -1,7 +0,0 @@
1
- function shellQuote(value) {
2
- if (/^[A-Za-z0-9_./:@%+=,-]+$/u.test(value)) return value;
3
- return `'${value.replace(/'/gu, `'"'"'`)}'`;
4
- }
5
- export {
6
- shellQuote
7
- };
package/dist/progress.js DELETED
@@ -1,94 +0,0 @@
1
- import path from "node:path";
2
- import { parseArgs } from "node:util";
3
- import { color } from "@farmslot/recipe-cli/harness";
4
- function shouldEmitJson(explicit) {
5
- if (explicit) return true;
6
- const env = process.env.RECIPE_PROGRESS_JSON;
7
- return env === "1" || env === "true";
8
- }
9
- function emitProgress(event, { json = false } = {}) {
10
- const payload = {
11
- schemaVersion: 1,
12
- type: "progress",
13
- ts: (/* @__PURE__ */ new Date()).toISOString(),
14
- ...event
15
- };
16
- if (json || shouldEmitJson(false)) {
17
- process.stderr.write(`${JSON.stringify(payload)}
18
- `);
19
- return;
20
- }
21
- const stream = process.stderr;
22
- const prefix = color("label", "recipe", { stream });
23
- if (payload.phase === "done") {
24
- const statusStyle = payload.status === "pass" ? "ok" : "err";
25
- const statusWord = payload.status === "pass" ? "done" : "failed";
26
- const cmd2 = payload.command ? color("cmd", `${payload.command}`, { stream }) : "";
27
- const elapsed2 = Number.isFinite(payload.elapsedMs) ? color("comment", ` (${Math.round(payload.elapsedMs / 1e3)}s)`, { stream }) : "";
28
- process.stderr.write(`${prefix}: ${cmd2} ${color(statusStyle, statusWord, { stream })} ${payload.message || ""}${elapsed2}
29
- `);
30
- return;
31
- }
32
- const cmd = payload.command ? `${color("cmd", payload.command, { stream })}: ` : "";
33
- const phase = payload.phase ? `${color("accent", `[${payload.phase}]`, { stream })} ` : "";
34
- const elapsed = Number.isFinite(payload.elapsedMs) ? color("comment", ` (${Math.round(payload.elapsedMs / 1e3)}s)`, { stream }) : "";
35
- process.stderr.write(`${prefix}: ${cmd}${phase}${payload.message || ""}${elapsed}
36
- `);
37
- }
38
- function usage() {
39
- console.error(`Usage:
40
- progress emit --command <name> --phase <id> --message <text> [--elapsed-ms <n>] [--json]
41
- progress done --command <name> --status pass|fail --message <text> [--elapsed-ms <n>] [--json]`);
42
- }
43
- async function main() {
44
- const [command, ...rest] = process.argv.slice(2);
45
- if (!command || command === "--help" || command === "-h") {
46
- usage();
47
- process.exit(command ? 0 : 2);
48
- }
49
- const { values } = parseArgs({
50
- args: rest,
51
- options: {
52
- command: { type: "string" },
53
- phase: { type: "string" },
54
- message: { type: "string" },
55
- status: { type: "string" },
56
- "elapsed-ms": { type: "string" },
57
- json: { type: "boolean", default: false }
58
- },
59
- allowPositionals: false
60
- });
61
- const elapsedMs = values["elapsed-ms"] ? Number.parseInt(values["elapsed-ms"], 10) : void 0;
62
- const json = values.json || shouldEmitJson(false);
63
- if (command === "emit") {
64
- emitProgress({
65
- command: values.command || "recipe",
66
- phase: values.phase || "working",
67
- message: values.message || "",
68
- elapsedMs: Number.isFinite(elapsedMs) ? elapsedMs : void 0
69
- }, { json });
70
- return;
71
- }
72
- if (command === "done") {
73
- emitProgress({
74
- command: values.command || "recipe",
75
- phase: "done",
76
- status: values.status || "pass",
77
- message: values.message || "",
78
- elapsedMs: Number.isFinite(elapsedMs) ? elapsedMs : void 0
79
- }, { json });
80
- return;
81
- }
82
- usage();
83
- process.exit(2);
84
- }
85
- const isMain = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(import.meta.filename ?? "");
86
- if (isMain) {
87
- main().catch((error) => {
88
- console.error(error instanceof Error ? error.message : String(error));
89
- process.exit(1);
90
- });
91
- }
92
- export {
93
- emitProgress
94
- };
@@ -1,14 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- function listRecipeFiles(root, base = "") {
4
- const out = [];
5
- for (const entry of fs.readdirSync(path.join(root, base), { withFileTypes: true })) {
6
- const rel = path.join(base, entry.name);
7
- if (entry.isDirectory()) out.push(...listRecipeFiles(root, rel));
8
- else if (entry.isFile() && entry.name.endsWith(".recipe.json")) out.push(rel);
9
- }
10
- return out;
11
- }
12
- export {
13
- listRecipeFiles
14
- };