@deeeed/metamask-harness 0.92.22 → 0.92.24

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.
@@ -9,11 +9,9 @@ import {
9
9
  readConfig,
10
10
  referenceHint,
11
11
  REVIEW_ADAPTERS,
12
- renderReviewChecklist,
13
12
  renderReviewHelp,
14
13
  resolveLibrary,
15
- resolveReference,
16
- defaultBaseRef
14
+ resolveReference
17
15
  } from "../review/knowledge.js";
18
16
  const EXIT_OK = 0;
19
17
  let cachedHarnessVersion;
@@ -32,16 +30,15 @@ function adapterFromOptions() {
32
30
  }
33
31
  return scope ?? "unscoped";
34
32
  }
35
- function resolveReviewContext(options, { fetch = true } = {}) {
33
+ function resolveReviewContext(options) {
36
34
  const target = targetPath(options);
37
35
  const adapter = adapterFromOptions();
38
36
  const domain = optionString(options, "domain") ?? process.env.MM_HARNESS_DOMAIN;
39
37
  const config = readConfig(configPath());
40
38
  const context = { target, adapter, ...domain ? { domain } : {} };
41
39
  if (domain) {
42
- const library = resolveLibrary(domain, { target, config, fetch });
40
+ const library = resolveLibrary(domain, { target, config });
43
41
  if (library) context.knowledge = loadDomainKnowledge(library, adapter === "unscoped" ? void 0 : adapter);
44
- else if (fetch) throw usageError(`No library found for domain "${domain}". Set RECIPE_LIBRARY_PATH="${domain}=<path>" or run: mm-harness config set libraries.${domain} <path>`);
45
42
  }
46
43
  const parity = parityAdapter(adapter);
47
44
  if (parity) {
@@ -53,7 +50,7 @@ function resolveReviewContext(options, { fetch = true } = {}) {
53
50
  }
54
51
  function handleHelpReview(argv, harnessVersion2) {
55
52
  const parsed = parseArgs(argv);
56
- const context = resolveReviewContext(parsed.options, { fetch: false });
53
+ const context = resolveReviewContext(parsed.options);
57
54
  const input = {
58
55
  adapter: context.adapter,
59
56
  harnessVersion: harnessVersion2,
@@ -70,66 +67,8 @@ function handleHelpReview(argv, harnessVersion2) {
70
67
  }
71
68
  return EXIT_OK;
72
69
  }
73
- async function handleReview(argv, version = harnessVersion()) {
74
- const parsed = parseArgs(argv);
75
- const subcommand = parsed.positional[0];
76
- if (subcommand !== "checklist") {
77
- throw usageError("Usage: mm-harness review checklist [--domain <name>] [--since <sha>] [--base <ref>] [--out <file>] [--json]");
78
- }
79
- const context = resolveReviewContext(parsed.options);
80
- const since = optionString(parsed.options, "since");
81
- const base = optionString(parsed.options, "base") ?? defaultBaseRef(context.target);
82
- const checklist = renderReviewChecklist({
83
- adapter: context.adapter,
84
- target: context.target,
85
- base,
86
- harnessVersion: version,
87
- ...since ? { since } : {},
88
- ...context.domain ? { domain: context.domain } : {},
89
- ...context.knowledge ? { knowledge: context.knowledge } : {},
90
- ...context.reference ? { reference: context.reference } : {},
91
- ...context.referenceMissing ? { referenceMissing: context.referenceMissing } : {}
92
- });
93
- const out = optionString(parsed.options, "out");
94
- if (out) {
95
- const file = path.resolve(out);
96
- fs.mkdirSync(path.dirname(file), { recursive: true });
97
- fs.writeFileSync(file, checklist);
98
- }
99
- if (optionFlag(parsed.options, "json")) {
100
- process.stdout.write(
101
- `${JSON.stringify(
102
- {
103
- command: "review",
104
- subcommand: "checklist",
105
- target: context.target,
106
- adapter: context.adapter,
107
- domain: context.domain ?? null,
108
- library: context.knowledge?.library ?? null,
109
- reference: context.reference ?? null,
110
- referenceMissing: context.referenceMissing ?? null,
111
- base,
112
- since: since ?? null,
113
- out: out ? path.resolve(out) : null,
114
- checklist
115
- },
116
- null,
117
- 2
118
- )}
119
- `
120
- );
121
- } else if (out) {
122
- process.stdout.write(`review checklist: ${path.resolve(out)}
123
- `);
124
- } else {
125
- process.stdout.write(checklist);
126
- }
127
- return EXIT_OK;
128
- }
129
70
  export {
130
71
  adapterFromOptions,
131
72
  handleHelpReview,
132
- handleReview,
133
- harnessVersion,
134
- resolveReviewContext
73
+ harnessVersion
135
74
  };
package/dist/doctor.js CHANGED
@@ -5,16 +5,6 @@ import { normalizeFeatureFlagRecord } from "./feature-flags.js";
5
5
  import { getAdapterSurface } from "./adapters/surface.js";
6
6
  import { isPin } from "./harness-pin.js";
7
7
  import { readJson } from "./paths.js";
8
- function requiredDoctorCheckSummary(checks) {
9
- const required = checks.filter((check) => check.required);
10
- const failed = required.filter((check) => check.status === "fail").map((check) => check.id);
11
- return {
12
- status: failed.length === 0 ? "pass" : "fail",
13
- total: required.length,
14
- passed: required.length - failed.length,
15
- failed
16
- };
17
- }
18
8
  function repoShape(target) {
19
9
  const exists = (rel) => fs.existsSync(path.join(target, rel));
20
10
  const packageInfo = readPackageInfo(target);
@@ -188,7 +178,6 @@ export {
188
178
  fixtureSummary,
189
179
  renderRuntimeContext,
190
180
  repoShape,
191
- requiredDoctorCheckSummary,
192
181
  runnerInstallKind,
193
182
  runnerProvenance,
194
183
  runtimeContextSummary
@@ -9,22 +9,36 @@ import {
9
9
  createHarnessCli,
10
10
  handleActions,
11
11
  handleCall,
12
+ handleChecklist,
13
+ handleDoctor,
14
+ handleExecutionTemplate,
12
15
  handleHarness,
13
16
  handleLast,
14
17
  handleLaunch,
18
+ handleLogs,
19
+ handlePrepare,
20
+ handleRecipeQuality,
15
21
  handleReload,
22
+ handleReview,
16
23
  handleRun,
24
+ handleStatus,
17
25
  handleStop,
26
+ handleTaskInit,
18
27
  harnessHost,
19
28
  optionValues,
20
29
  parseArgs,
30
+ resolveAdapter,
21
31
  targetPath
22
32
  } from "@farmslot/recipe-cli/harness";
23
33
  import { handleStopViewers } from "./commands/stop.js";
24
- import { stopOptions } from "./provider-lifecycle.js";
34
+ import { cliDoctorOptions, configureProviderReadiness } from "./provider-doctor.js";
35
+ import { cliRecipeQualityOptions, cliReviewOptions, helperCommandOptions } from "./provider-helpers.js";
36
+ import { cliStatusOptions, prepareOptions, stopOptions } from "./provider-lifecycle.js";
37
+ import { createLogsOptions } from "./provider-logs.js";
25
38
  import { adoptPluginAdapter, afterAdapterLoad, configuredLibrarySources } from "./adapters/plugin-surface.js";
26
39
  import { withMainnetOrderAbort } from "./adapters/perps-mainnet-cleanup.js";
27
40
  import { adapterRegistry } from "./adapters/registry.js";
41
+ import { getAdapterSurface } from "./adapters/surface.js";
28
42
  import {
29
43
  explainMissingAdapterLibrary,
30
44
  HIDDEN_COMMANDS,
@@ -34,32 +48,22 @@ import {
34
48
  withAdapterList
35
49
  } from "./command-contract.js";
36
50
  import { handleCheck } from "./commands/check.js";
37
- import { handleChecklist } from "./commands/checklist.js";
38
51
  import { handleCompletionCandidates, invalidateCompletionCache } from "./commands/completion-candidates.js";
39
52
  import { handleConfig } from "./commands/config.js";
40
53
  import { handleDebug } from "./commands/debug.js";
41
54
  import { applyDeviceTargeting } from "./commands/device-target.js";
42
- import { handleDoctor } from "./commands/doctor.js";
43
- import { handleDomain } from "./commands/domain.js";
44
55
  import { handleEnsureReady } from "./commands/ensure-ready.js";
45
- import { handleExecutionTemplate } from "./commands/execution-template.js";
46
56
  import { handleFixtures } from "./commands/fixtures.js";
47
57
  import { handleHelp } from "./commands/help.js";
48
- import { handleLogs } from "./commands/logs.js";
49
58
  import { overlayCommandOptions } from "./commands/overlay.js";
50
59
  import { handlePin } from "./commands/pin.js";
51
60
  import { handlePrBody } from "./commands/pr-body.js";
52
- import { handlePrepare } from "./commands/prepare.js";
53
61
  import { handleProvision } from "./commands/provision.js";
54
- import { handleRecipeQuality } from "./commands/recipe-quality.js";
55
62
  import { handleResolveExtension } from "./commands/resolve-extension.js";
56
- import { handleReview } from "./commands/review.js";
57
63
  import { handleRuntimeDecision } from "./commands/runtime-decision.js";
58
64
  import { handleRuntimeLaunch } from "./commands/runtime-launch.js";
59
65
  import { handleSelfTest } from "./commands/self-test.js";
60
66
  import { handleSetupBase } from "./commands/setup-base.js";
61
- import { handleStatus } from "./commands/status.js";
62
- import { handleTaskInit } from "./commands/task-init.js";
63
67
  import { handleTutorial } from "./commands/tutorial.js";
64
68
  import { handleUpdate, maybeNudge } from "./commands/update.js";
65
69
  import { metaMaskEngine } from "./engine.js";
@@ -114,6 +118,20 @@ function overlay(command) {
114
118
  return code;
115
119
  };
116
120
  }
121
+ const LOGS_OPTIONS = PUBLIC_COMMANDS.find((command) => command.name === "logs").contract.options;
122
+ function logsOptions(options) {
123
+ const surface = () => createLogsOptions(getAdapterSurface(resolveAdapter(options).adapter));
124
+ return {
125
+ compact: (request) => surface().compact(request),
126
+ window: (request) => surface().window(request),
127
+ announce: ({ source, logFile }) => process.stderr.write(
128
+ `${source} log: ${logFile}
129
+ tail it yourself: tail -f ${logFile} \xB7 page it: less +F ${logFile}
130
+ following below (Ctrl-C to stop) \u2026
131
+ `
132
+ )
133
+ };
134
+ }
117
135
  function invocationLibraries() {
118
136
  return optionValues(process.argv.slice(2), "--library");
119
137
  }
@@ -137,16 +155,16 @@ function withManifestEnvelope(command, run) {
137
155
  }
138
156
  const PUBLIC_RUN = {
139
157
  help: { run: (args) => handleHelp(args, { packageRoot, harnessVersion: hostVersion() }), nudge: false },
140
- review: { run: handleReview },
141
- domain: { run: handleDomain },
158
+ review: { run: (args) => handleReview(args, cliReviewOptions) },
159
+ domain: { run: (args) => handleReview(["domain", ...args], cliReviewOptions) },
142
160
  "pr-body": { run: handlePrBody },
143
161
  config: { run: handleConfig },
144
162
  tutorial: { run: (args) => handleTutorial(args, { harnessVersion: hostVersion() }), exit: "now", nudge: false },
145
163
  "setup-base": { run: handleSetupBase, raw: true, exit: "now", nudge: false },
146
- status: { run: (args) => handleStatus(parseArgs(args)) },
147
- checklist: { run: handleChecklist },
148
- prepare: { run: handlePrepare },
149
- task: { run: handleTaskInit },
164
+ status: { run: (args) => handleStatus(parseArgs(args), cliStatusOptions) },
165
+ checklist: { run: (args) => handleChecklist(args, helperCommandOptions.task) },
166
+ prepare: { run: (args) => handlePrepare(args, prepareOptions) },
167
+ task: { run: (args) => handleTaskInit(args, { surface: "skill" }) },
150
168
  "execution-template": { run: handleExecutionTemplate },
151
169
  actions: { run: (args) => handleActions(parseArgs(args), { catalog: metaMaskEngine }) },
152
170
  stop: { run: (args) => handleStop(args, stopOptions) },
@@ -155,16 +173,28 @@ const PUBLIC_RUN = {
155
173
  call: { run: (args) => withMainnetOrderAbort(targetPath(parseArgs(args).options), () => handleCall(args, callOptions)) },
156
174
  run: { run: (args) => withMainnetOrderAbort(targetPath(parseArgs(args).options), () => handleRun(args, runOptions)) },
157
175
  last: { run: (args) => handleLast(parseArgs(args)) },
158
- doctor: { run: (args) => handleDoctor(parseArgs(args)) },
176
+ doctor: {
177
+ run: (args) => {
178
+ const parsed = parseArgs(args);
179
+ const adapter = contextAdapter(targetPath(parsed.options));
180
+ if (adapter && adapterRegistry.has(adapter)) configureProviderReadiness(getAdapterSurface(adapter));
181
+ return handleDoctor(parsed, cliDoctorOptions);
182
+ }
183
+ },
159
184
  check: { run: handleCheck },
160
- "recipe-quality": { run: handleRecipeQuality },
185
+ "recipe-quality": { run: (args) => handleRecipeQuality(args, cliRecipeQualityOptions) },
161
186
  provision: { run: (args) => handleProvision(parseArgs(args)) },
162
187
  install: { run: overlay("install") },
163
188
  verify: { run: overlay("verify") },
164
189
  cleanup: { run: overlay("cleanup") },
165
190
  launch: { run: handleLaunch },
166
191
  "runtime-launch": { run: (args) => handleRuntimeLaunch(parseArgs(args)) },
167
- logs: { run: handleLogs },
192
+ logs: {
193
+ run: (args) => {
194
+ const parsed = parseArgs(args, { optionSpecs: LOGS_OPTIONS });
195
+ return handleLogs(parsed, logsOptions(parsed.options));
196
+ }
197
+ },
168
198
  debug: { run: handleDebug },
169
199
  reload: { run: handleReload },
170
200
  update: { run: handleUpdate, exit: "now", nudge: false },
package/dist/paths.js CHANGED
@@ -4,7 +4,6 @@ import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import { recipeRuntimeDir, recipeRuntimePath } from "@farmslot/recipe-cli/harness";
5
5
  import { adapterRegistry } from "./adapters/registry.js";
6
6
  const runnerDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
7
- const PREPARE_PROGRESS_ARTIFACT = "prepare/progress.json";
8
7
  function walletFixturePath(projectRoot) {
9
8
  return recipeRuntimePath(projectRoot, "wallet-fixture.json");
10
9
  }
@@ -157,7 +156,6 @@ function isMissingPackageError(error, packageName) {
157
156
  }
158
157
  export {
159
158
  LIBRARY_ACTION_NAMESPACE,
160
- PREPARE_PROGRESS_ARTIFACT,
161
159
  assertAdapter,
162
160
  extensionIdPath,
163
161
  harnessSlotPoolDir,
@@ -3,7 +3,7 @@ import { farmslotReadyIndicator } from "./commands/farmslot-ready.js";
3
3
  import { updateStatus } from "./commands/update.js";
4
4
  import { doctorReportOptions, fixtureSummary } from "./doctor.js";
5
5
  import { loadActionManifest, validateManifest } from "./manifest.js";
6
- import { statusOptions } from "./provider-lifecycle.js";
6
+ import { cliDevicePreview, statusOptions } from "./provider-lifecycle.js";
7
7
  import { ensureRuntimeContext } from "./runtime-context.js";
8
8
  const doctorOptions = {
9
9
  ...statusOptions,
@@ -25,12 +25,14 @@ const doctorOptions = {
25
25
  userAction: (failed, retry) => failed.includes("wallet-fixture") ? `choose one wallet fixture source from nextActions, run it, then retry: ${retry}` : void 0
26
26
  }
27
27
  };
28
+ const cliDoctorOptions = { ...doctorOptions, previewDevice: cliDevicePreview };
28
29
  function configureProviderReadiness(runtime) {
29
30
  Object.assign(runtime.readiness, {
30
31
  readyIndicator: (devices) => farmslotReadyIndicator(runtime.id, [...devices])
31
32
  });
32
33
  }
33
34
  export {
35
+ cliDoctorOptions,
34
36
  configureProviderReadiness,
35
37
  doctorOptions
36
38
  };
@@ -1,14 +1,118 @@
1
- import { checkDiffSummaryReady, runCloseout, teachRecipeCompletion } from "./commands/checklist.js";
1
+ import { spawnSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import path from "node:path";
5
+ import {
6
+ usageError
7
+ } from "@farmslot/recipe-cli/harness";
8
+ import { adapterFromOptions, harnessVersion } from "./commands/review.js";
2
9
  import {
3
10
  BASE_REVIEW_STEPS,
4
11
  REVIEW_ADAPTERS,
12
+ VERDICT_STEP,
5
13
  configPath,
14
+ discoverLibraries,
6
15
  parityAdapter,
7
16
  parityReviewSteps,
8
17
  readConfig,
9
18
  referenceHint,
19
+ resolveLibrary,
10
20
  resolveReference
11
21
  } from "./review/knowledge.js";
22
+ const require2 = createRequire(import.meta.url);
23
+ function handoffCommand() {
24
+ if (process.env.HANDOFF_BIN) {
25
+ try {
26
+ fs.accessSync(process.env.HANDOFF_BIN, fs.constants.X_OK);
27
+ return { command: process.env.HANDOFF_BIN, prefix: [] };
28
+ } catch {
29
+ return { command: process.execPath, prefix: [process.env.HANDOFF_BIN] };
30
+ }
31
+ }
32
+ return {
33
+ command: process.execPath,
34
+ prefix: [require2.resolve("@farmslot/handoff/cli")]
35
+ };
36
+ }
37
+ function closeoutArgs(taskDir, args) {
38
+ const normalized = args.flatMap((arg) => {
39
+ const option = ["--config", "--destination", "--metadata"].find(
40
+ (name) => arg.startsWith(`${name}=`)
41
+ );
42
+ return option ? [option, arg.slice(option.length + 1)] : [arg];
43
+ });
44
+ if (process.env.METAMASK_AGENTIC_LEARNINGS_DIR && !normalized.includes("--destination")) {
45
+ return [
46
+ "closeout",
47
+ taskDir,
48
+ ...normalized,
49
+ "--destination",
50
+ process.env.METAMASK_AGENTIC_LEARNINGS_DIR
51
+ ];
52
+ }
53
+ return ["closeout", taskDir, ...normalized];
54
+ }
55
+ function runCloseout(taskDir, args, stdio) {
56
+ try {
57
+ const { command, prefix } = handoffCommand();
58
+ return spawnSync(command, [...prefix, ...closeoutArgs(taskDir, args)], {
59
+ encoding: "utf8",
60
+ stdio
61
+ });
62
+ } catch (error) {
63
+ const message = error instanceof Error ? error.message : String(error);
64
+ return {
65
+ status: 1,
66
+ stdout: "",
67
+ stderr: message,
68
+ error: new Error(message)
69
+ };
70
+ }
71
+ }
72
+ function checkDiffSummaryReady(taskDir, step) {
73
+ const summaryPath = path.join(
74
+ taskDir,
75
+ "artifacts",
76
+ "validation-summary.json"
77
+ );
78
+ try {
79
+ const summary = JSON.parse(fs.readFileSync(summaryPath, "utf8"));
80
+ if (summary.command === "check" && summary.action === "diff" && summary.status === "pass") {
81
+ return true;
82
+ }
83
+ } catch {
84
+ }
85
+ console.error(
86
+ `check-diff gate ${step} requires a task-local PASS at ${summaryPath}`
87
+ );
88
+ console.error(
89
+ `Run: mm-harness check diff [--fix] --artifacts-dir ${path.dirname(summaryPath)}`
90
+ );
91
+ console.error("Then rerun the same checklist mark command.");
92
+ return false;
93
+ }
94
+ function teachRecipeCompletion(taskDir, step) {
95
+ if (step !== "complete") return;
96
+ const artifactsDir = path.join(taskDir, "artifacts");
97
+ if (!fs.existsSync(path.join(artifactsDir, "recipe.json"))) return;
98
+ const qualityPath = path.join(artifactsDir, "recipe-quality.json");
99
+ const coveragePath = path.join(artifactsDir, "recipe-coverage.md");
100
+ const missingQuality = !fs.existsSync(qualityPath);
101
+ const missingCoverage = !fs.existsSync(coveragePath);
102
+ if (!missingQuality && !missingCoverage) return;
103
+ console.error("Recipe completion needs two closure artifacts; derive them from the real recipe run and evidence (do not stub them):");
104
+ if (missingCoverage) {
105
+ console.error(` - ${coveragePath}: map each proof target to its recipe node and evidence, or record a reasoned N/A.`);
106
+ }
107
+ if (missingQuality) {
108
+ const compactPath = path.join(artifactsDir, "recipe-quality-input.json");
109
+ console.error(" First run: mm-harness help");
110
+ console.error(` - ${qualityPath}: write ${compactPath} as:`);
111
+ console.error(' {"verdict":"pass|warn|fail","reasons":["evidence-backed reason"]}');
112
+ console.error(` then run: mm-harness recipe-quality build --input ${compactPath} --output ${qualityPath}`);
113
+ }
114
+ console.error("Then rerun the same checklist complete command.");
115
+ }
12
116
  const task = {
13
117
  stepGates: [{ label: /mm-harness check diff/u, ready: checkDiffSummaryReady }],
14
118
  closeout: runCloseout,
@@ -30,6 +134,32 @@ const review = {
30
134
  };
31
135
  }
32
136
  };
137
+ const cliReviewOptions = {
138
+ ...review,
139
+ usage: "Usage: mm-harness review checklist [--domain <name>] [--since <sha>] [--base <ref>] [--out <file>] [--json]",
140
+ domainCommand: "mm-harness domain",
141
+ defaultDomain: { read: () => process.env.MM_HARNESS_DOMAIN || void 0, label: "MM_HARNESS_DOMAIN" },
142
+ runtime: ({ action, domain }) => {
143
+ const adapter = adapterFromOptions();
144
+ if (action === "domain" && !domain && adapter === "unscoped") {
145
+ throw usageError(`No MetaMask checkout detected: pass --adapter <${REVIEW_ADAPTERS.join("|")}> or --domain <name>.`);
146
+ }
147
+ return adapter;
148
+ },
149
+ libraries: ({ target }) => discoverLibraries({ target, config: readConfig(configPath()) }).map((library) => ({
150
+ name: library.name,
151
+ root: library.root,
152
+ source: library.source,
153
+ revision: library.revision ?? null
154
+ })),
155
+ resolveLibrary: ({ domain, target }) => resolveLibrary(domain, { target, config: readConfig(configPath()), fetch: true }),
156
+ header: ({ target, library, reference, referenceMissing }) => [
157
+ `Materialized by mm-harness ${harnessVersion()} for ${target}.`,
158
+ ...library ? [`Domain knowledge: ${library.name}${library.revision ? ` @ ${library.revision}` : ""} (${library.root}, via ${library.source}).`] : [],
159
+ ...reference ? [`Parity reference: ${reference.adapter} checkout at ${reference.root} (via ${reference.source}).`] : referenceMissing ? [`Parity reference: not checked \u2014 ${referenceMissing}.`] : []
160
+ ],
161
+ verdictRule: VERDICT_STEP
162
+ };
33
163
  async function recipeAdvice(options, mode) {
34
164
  const { handleRecipeAdvice } = await import("./commands/recipe-advice.js");
35
165
  return handleRecipeAdvice(options, mode);
@@ -48,6 +178,18 @@ const recipeQuality = {
48
178
  missingAction: "Use build, plan, advise or advisor-status."
49
179
  };
50
180
  const helperCommandOptions = { task, review, recipeQuality };
181
+ const cliRecipeQualityOptions = {
182
+ subcommands: recipeQuality.subcommands,
183
+ subcommandOptions: {
184
+ names: recipeQuality.subcommandOptions.names,
185
+ message: "advice options require the advise subcommand",
186
+ userAction: "mm-harness recipe-quality advise --recipe <recipe.json> --task <TASK.md> --adapter <adapter> --advisor <provider>"
187
+ },
188
+ missingAction: "missing action: use build, plan, or advise",
189
+ usageNote: "apply the quality bar from mm-harness help, then build its compact verdict here."
190
+ };
51
191
  export {
192
+ cliRecipeQualityOptions,
193
+ cliReviewOptions,
52
194
  helperCommandOptions
53
195
  };
@@ -1,7 +1,11 @@
1
+ import {
2
+ usageError
3
+ } from "@farmslot/recipe-cli/harness";
1
4
  import { stopProjectCollector } from "../library/actions/shared/analytics/collector.mjs";
2
5
  import { collectCheckoutView, renderCheckoutView } from "./checkout-view.js";
3
6
  import { applyDeviceTargeting } from "./commands/device-target.js";
4
- import { featureFlagReport } from "./feature-flags.js";
7
+ import { doctorReportOptions } from "./doctor.js";
8
+ import { featureFlagReport, FeatureFlagSpecError, parseRemoteFlagSpec } from "./feature-flags.js";
5
9
  const statusOptions = {
6
10
  checkoutView(target, adapter) {
7
11
  const view = collectCheckoutView(target, adapter);
@@ -14,6 +18,41 @@ const statusOptions = {
14
18
  persistPin: false
15
19
  })
16
20
  };
21
+ const cliDevicePreview = (command, adapter, options) => applyDeviceTargeting(command, adapter, options, { gate: false, rerun: "" });
22
+ const cliStatusOptions = { ...statusOptions, previewDevice: cliDevicePreview, journal: false };
23
+ const prepareOptions = {
24
+ usage: "mm-harness prepare [--target <checkout>] [--platform mobile|extension|core] [--mobile-platform ios|android] [--clear-metro] [--artifacts-dir <dir>] [--json]",
25
+ launchHint: "can take minutes on Mobile: Metro bundle and app start",
26
+ steps: [{
27
+ id: "fixtures",
28
+ command: "fixtures set",
29
+ hint: "under a minute",
30
+ argv: ({ device, forwarded, targetArgs }) => [
31
+ "fixtures",
32
+ "set",
33
+ ...device.flatMap((platform) => ["--platform", platform]),
34
+ ...forwarded,
35
+ "--json",
36
+ ...targetArgs
37
+ ]
38
+ }],
39
+ deviceTarget: { option: "mobilePlatform", flag: "--mobile-platform", choices: ["ios", "android"] },
40
+ clearMetroOnly: "mobile only",
41
+ forward: {
42
+ option: "remoteFlag",
43
+ flag: "--remote-flag",
44
+ validate(value) {
45
+ try {
46
+ parseRemoteFlagSpec(value);
47
+ } catch (error) {
48
+ if (!(error instanceof FeatureFlagSpecError)) throw error;
49
+ throw usageError(`${error.message}
50
+ Next: ${error.userAction}`);
51
+ }
52
+ }
53
+ },
54
+ provenance: doctorReportOptions.provenance
55
+ };
17
56
  const stopOptions = {
18
57
  companions: async (target) => {
19
58
  const collector = await stopProjectCollector(target);
@@ -21,6 +60,9 @@ const stopOptions = {
21
60
  }
22
61
  };
23
62
  export {
63
+ cliDevicePreview,
64
+ cliStatusOptions,
65
+ prepareOptions,
24
66
  statusOptions,
25
67
  stopOptions
26
68
  };