@deeeed/metamask-harness 0.82.1 → 0.84.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 (73) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +8 -1
  3. package/adapters/core/cleanup.sh +5 -29
  4. package/adapters/core/controller-import-probe.mjs +26 -0
  5. package/adapters/manifest.json +22 -102
  6. package/adapters/shared/session-lock.mjs +43 -0
  7. package/adapters/web-dapp/metamask-signer.mjs +212 -0
  8. package/dist/adapters/core/actions.js +0 -3
  9. package/dist/adapters/core/overlay.js +1 -5
  10. package/dist/adapters/core/readiness.js +133 -2
  11. package/dist/adapters/core/surface.js +53 -41
  12. package/dist/adapters/extension/extension-id.js +1 -0
  13. package/dist/adapters/extension/fixtures.js +60 -1
  14. package/dist/adapters/extension/network-observer.js +19 -206
  15. package/dist/adapters/extension/run.js +5 -3
  16. package/dist/adapters/extension/runtime.js +112 -20
  17. package/dist/adapters/mobile/android-gfxinfo-performance.js +3 -1
  18. package/dist/adapters/mobile/devices.js +5 -1
  19. package/dist/adapters/mobile/performance-observer.js +9 -6
  20. package/dist/adapters/mobile/run.js +1 -1
  21. package/dist/adapters/mobile/surface.js +1 -2
  22. package/dist/adapters/performance/trace-kinds.js +36 -0
  23. package/dist/adapters/plugin-surface.js +34 -34
  24. package/dist/adapters/session-lock.js +23 -0
  25. package/dist/adapters/web-dapp/console-capture.js +2 -20
  26. package/dist/adapters/web-dapp/run.js +31 -25
  27. package/dist/adapters/web-dapp/surface.js +32 -108
  28. package/dist/command-contract.js +1363 -630
  29. package/dist/commands/core-readiness.js +1 -9
  30. package/dist/commands/doctor.js +1 -1
  31. package/dist/commands/launch/device.js +1 -1
  32. package/dist/doctor.js +5 -2
  33. package/dist/mm-harness-cli.js +228 -1210
  34. package/dist/performance-observation.js +28 -5
  35. package/docs/CONTRIBUTING.md +6 -4
  36. package/docs/cli-contract.md +1 -1
  37. package/docs/feature-guard.md +1 -1
  38. package/library/actions/core/perps/_controller.mjs +10 -0
  39. package/library/actions/extension/wallet/import.mjs +21 -4
  40. package/library/actions/extension/wallet/visible-session.mjs +32 -12
  41. package/library/actions/shared/console-collector.mjs +1 -1
  42. package/library/actions/web-dapp/app/launch.mjs +1 -1
  43. package/library/actions/web-dapp/platform/adapter-web-lib.mjs +18 -0
  44. package/library/actions/web-dapp/platform/hud.mjs +1 -1
  45. package/library/actions/web-dapp/platform/page.mjs +2 -3
  46. package/library/actions/web-dapp/platform/runtime.mjs +29 -178
  47. package/package.json +7 -6
  48. package/scripts/completions.sh +1 -1
  49. package/adapters/web-dapp/cleanup.mjs +0 -69
  50. package/adapters/web-dapp/inject.mjs +0 -75
  51. package/adapters/web-dapp/launch.mjs +0 -631
  52. package/adapters/web-dapp/lib/blocked-hosts.mjs +0 -24
  53. package/adapters/web-dapp/lib/cdp-client.mjs +0 -84
  54. package/adapters/web-dapp/lib/display.mjs +0 -115
  55. package/adapters/web-dapp/lib/hud.mjs +0 -148
  56. package/adapters/web-dapp/lib/policy.mjs +0 -79
  57. package/adapters/web-dapp/lib/processes.mjs +0 -187
  58. package/adapters/web-dapp/lib/readiness.mjs +0 -272
  59. package/adapters/web-dapp/stop.mjs +0 -38
  60. package/adapters/web-dapp/verify.mjs +0 -51
  61. package/adapters/web-dapp/wallet-host.mjs +0 -520
  62. package/dist/adapters/core/live-script-env.js +0 -36
  63. package/dist/adapters/extension/browser-cdp.js +0 -174
  64. package/dist/adapters/extension/performance-observer.js +0 -75
  65. package/dist/adapters/performance/cdp-trace.js +0 -406
  66. package/dist/adapters/performance/js-task-metrics.js +0 -35
  67. package/dist/adapters/web-dapp/doctor-checks.js +0 -33
  68. package/dist/adapters/web-dapp/launch.js +0 -87
  69. package/dist/adapters/web-dapp/overlay.js +0 -21
  70. package/dist/adapters/web-dapp/readiness.js +0 -10
  71. package/dist/adapters/web-dapp/stop.js +0 -20
  72. package/dist/async.js +0 -19
  73. package/dist/cli.js +0 -202
@@ -1,983 +1,186 @@
1
1
  import "./host.js";
2
+ import "./adapters/surface.js";
2
3
  import { spawnSync } from "node:child_process";
3
4
  import fs from "node:fs";
4
5
  import path from "node:path";
5
6
  import { fileURLToPath } from "node:url";
6
- import { Command } from "commander";
7
- import { color, withCommandJournal, JsonStreamWriter, detectAdapter, handleCallHelp } from "@farmslot/recipe-cli/harness";
8
- import { handleUpdate, maybeNudge } from "./commands/update.js";
9
- import { handlePin } from "./commands/pin.js";
10
- import { metaMaskEngine } from "./engine.js";
7
+ import {
8
+ createHarnessCli,
9
+ detectAdapter,
10
+ handleActions,
11
+ handleCall,
12
+ handleHarness,
13
+ handleLast,
14
+ handleLaunch,
15
+ handleReload,
16
+ handleRun,
17
+ handleStop,
18
+ harnessHost,
19
+ parseArgs,
20
+ targetPath
21
+ } from "@farmslot/recipe-cli/harness";
22
+ import { stopProjectCollector } from "../library/actions/shared/analytics/collector.mjs";
23
+ import { adoptPluginAdapter, afterAdapterLoad, configuredLibrarySources } from "./adapters/plugin-surface.js";
24
+ import { adapterRegistry } from "./adapters/registry.js";
25
+ import {
26
+ explainMissingAdapterLibrary,
27
+ HIDDEN_COMMANDS,
28
+ PUBLIC_COMMANDS,
29
+ REPLACED_OPTIONS
30
+ } from "./command-contract.js";
31
+ import { handleCheck } from "./commands/check.js";
32
+ import { handleChecklist } from "./commands/checklist.js";
33
+ import { handleCompletionCandidates, invalidateCompletionCache } from "./commands/completion-candidates.js";
34
+ import { handleConfig } from "./commands/config.js";
35
+ import { handleDebug } from "./commands/debug.js";
36
+ import { applyDeviceTargeting } from "./commands/device-target.js";
37
+ import { handleDoctor } from "./commands/doctor.js";
38
+ import { handleDomain } from "./commands/domain.js";
39
+ import { handleEnsureReady } from "./commands/ensure-ready.js";
40
+ import { handleExecutionTemplate } from "./commands/execution-template.js";
41
+ import { handleFixtures } from "./commands/fixtures.js";
11
42
  import { handleHelp } from "./commands/help.js";
12
- import { handleTutorial } from "./commands/tutorial.js";
43
+ import { handleLogs } from "./commands/logs.js";
44
+ import { overlayCommandOptions } from "./commands/overlay.js";
45
+ import { handlePin } from "./commands/pin.js";
46
+ import { handlePrBody } from "./commands/pr-body.js";
47
+ import { handlePrepare } from "./commands/prepare.js";
48
+ import { handleProvision } from "./commands/provision.js";
49
+ import { handleRecipeQuality } from "./commands/recipe-quality.js";
50
+ import { handleResolveExtension } from "./commands/resolve-extension.js";
51
+ import { handleReview } from "./commands/review.js";
52
+ import { handleRuntimeDecision } from "./commands/runtime-decision.js";
53
+ import { handleRuntimeHealth } from "./commands/runtime-health.js";
54
+ import { handleRuntimeLaunch } from "./commands/runtime-launch.js";
55
+ import { handleSelfTest } from "./commands/self-test.js";
13
56
  import { handleSetupBase } from "./commands/setup-base.js";
14
- import { loadSelectedAdapter } from "./adapters/plugin-surface.js";
15
- import { getAdapterSurface } from "./adapters/surface.js";
57
+ import { handleStatus } from "./commands/status.js";
58
+ import { handleTaskInit } from "./commands/task-init.js";
59
+ import { handleTutorial } from "./commands/tutorial.js";
60
+ import { handleUpdate, maybeNudge } from "./commands/update.js";
61
+ import { metaMaskEngine } from "./engine.js";
16
62
  import { ActionManifestError } from "./manifest.js";
63
+ import { walletFixturePath } from "./paths.js";
17
64
  import { configPath, hydrateLibraryPathEnv, readConfig } from "./review/knowledge.js";
18
- import {
19
- PUBLIC_COMMAND_CONTRACTS,
20
- publicCommandNames,
21
- validatePublicInvocation
22
- } from "./command-contract.js";
23
65
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
24
- globalThis.__MM_HARNESS_WRAPPER__ = true;
25
- const { main: recipeMain } = await import("./cli.js");
26
- const rawArgv = process.argv.slice(2);
27
- const REAL = [
28
- {
29
- name: "help",
30
- summary: "Load the version-matched recipe guide for a person or agent.",
31
- example: "mm-harness help",
32
- helpText: `mm-harness help [flags]
33
-
34
- Loads the complete recipe guide shipped with this harness.
35
-
36
- --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
37
- --target <path> Checkout path (default: cwd)
38
- --json Load the same guide as structured agent context
39
-
40
- help review [--domain <name>] prints the review guide: the base review every PR
41
- gets, plus the team library's anti-pattern families, parity rule, and shared
42
- package notes when --domain names a library (see mm-harness review --help).
43
-
44
- Example:
45
- mm-harness help
46
- mm-harness help --json
47
- mm-harness help review --domain perps`
48
- },
49
- {
50
- name: "review",
51
- summary: "Materialize a review checklist: base review + team library patterns + parity + verdict.",
52
- example: "mm-harness review checklist --domain perps",
53
- helpText: `mm-harness review checklist [flags]
54
-
55
- Writes a phase-structured checklist (Setup, Base review, Domain patterns,
56
- Parity, Verdict) that mm-harness checklist mark can track. Domain patterns are
57
- one line per "##" section of the library's review/antipatterns.md; Parity names
58
- the other platform's checkout when one resolves and says "not checked" otherwise.
59
-
60
- Library resolution for --domain <name>: RECIPE_LIBRARY_PATH entry \u2192 engineer
61
- config (mm-harness config) \u2192 sibling directory beside the checkout \u2192 shallow
62
- fetch into <checkout>/.skills-cache/<name> (known libraries only). Reference
63
- checkout resolution: MM_HARNESS_REF_<ADAPTER> \u2192 engineer config \u2192 sibling
64
- checkout of that adapter.
65
-
66
- --domain <name> Team library to compose (default: MM_HARNESS_DOMAIN; none = base review only)
67
- --since <sha> Incremental re-review: scope the checklist to <sha>..HEAD
68
- --base <ref> Base ref for the diff range (default: origin/main when present)
69
- --out <file> Also write the checklist to <file>
70
- --adapter, --target, --json
71
-
72
- Example:
73
- mm-harness review checklist --domain perps
74
- mm-harness review checklist --domain perps --since 4dd9be9 --out temp/tasks/review/CHECKLIST.md
75
- mm-harness review checklist --json`
76
- },
77
- {
78
- name: "domain",
79
- summary: "Which team library owns the change: declared value first, else owned-paths.json against the changed files.",
80
- example: "mm-harness domain",
81
- helpText: `mm-harness domain [flags]
82
-
83
- Prints the domain name (or "none"). A declared --domain / MM_HARNESS_DOMAIN wins;
84
- otherwise the changed files (base...HEAD, working tree, untracked) are matched
85
- against owned-paths.json in every reachable library (RECIPE_LIBRARY_PATH,
86
- mm-harness config, sibling directories, .skills-cache).
87
-
88
- --domain <name> Declared domain (skips classification)
89
- --base <ref> Base ref for the changed-file range (default: origin/main when present)
90
- --adapter, --target, --json
91
-
92
- Example:
93
- mm-harness domain
94
- mm-harness domain --json`
95
- },
96
- {
97
- name: "pr-body",
98
- summary: "Render the publishable PR body: the authored description plus the recipe and run log sections built from artifacts.",
99
- example: "mm-harness pr-body render temp/tasks/dev/TAT-1",
100
- helpText: `mm-harness pr-body render <task-dir> [flags]
101
-
102
- Reads <task-dir>/artifacts/pr-description.md (the PR body the worker wrote in
103
- the repository PR template shape) and inserts the machine sections rendered
104
- from artifacts: "Validation Recipe" from artifacts/recipe.json, "Validation
105
- Logs" from artifacts/recipe-run/report.md, and "Recipe Workflow" from
106
- artifacts/workflow.mmd when it exists. Any such section the worker pasted by
107
- hand is replaced. The sections go before the first checklist section, else at
108
- the end. Fences are always longer than any backtick run in the content and
109
- close on their own line. Writes <task-dir>/artifacts/pr-body.md.
110
-
111
- --command <text> Recipe invocation to show above the run log
112
- --out <file> Write elsewhere than artifacts/pr-body.md
113
- --json Machine-readable result
114
-
115
- Example:
116
- mm-harness pr-body render temp/tasks/dev/TAT-1 --json`
117
- },
118
- {
119
- name: "config",
120
- summary: "Per-engineer locations and optional recipe advisor settings.",
121
- example: "mm-harness config set references.extension ~/dev/metamask-extension",
122
- helpText: `mm-harness config <list|path|get <key>|set <key> <value>|unset <key>> [--json]
123
-
124
- Stored outside every repo in ~/.mm-harness/config.json (override: MM_HARNESS_CONFIG).
125
- Keys: libraries.<name> (team library checkout), references.<mobile|extension|core|terminal>
126
- (the checkout used as the parity reference), advisor.enabled (true/false),
127
- advisor.provider, advisor.model, advisor.stages (plan,draft,evidence). Advisor
128
- settings contain no credentials and default to disabled with planning only.
129
- Env always wins over location config:
130
- RECIPE_LIBRARY_PATH for libraries, MM_HARNESS_REF_<ADAPTER> for references.
131
-
132
- Example:
133
- mm-harness config set libraries.perps ~/dev/experimental-metamask-recipe-perps
134
- mm-harness config set references.extension ~/dev/metamask-extension
135
- mm-harness config set advisor.provider typesafe
136
- mm-harness config set advisor.enabled true
137
- mm-harness config list`
138
- },
139
- {
140
- name: "tutorial",
141
- summary: "Open the protected visual recipe tutorial.",
142
- example: "mm-harness tutorial",
143
- helpText: `mm-harness tutorial [flags]
144
-
145
- Source checkouts open the local tutorial on port 8765. Installed releases
146
- open the protected MetaMask GitHub Pages site.
147
-
148
- --no-open Print the tutorial URL without opening it
149
- --json Print version, URL, and open status as JSON
150
-
151
- Example:
152
- mm-harness tutorial`
153
- },
154
- {
155
- name: "setup-base",
156
- summary: "Bootstrap numbered MetaMask product checkouts and install their dependencies.",
157
- example: "mm-harness setup-base --only core --counts core=1",
158
- helpText: `mm-harness setup-base [flags]
159
-
160
- Bootstrap numbered MetaMask Extension, Mobile, and Core checkouts under one
161
- base directory, then run each checkout's pinned dependency install.
162
-
163
- --dir <base> Destination root
164
- --counts <repo=n,...> Copies per selected repository
165
- --only <extension,mobile,core> Repository subset
166
- --dry-run Print the plan without changing anything
167
- --force Override the free-space refusal
168
- --json Machine-readable terminal summary
169
- --show-config Print saved preferences and exit
170
- --reset-config Remove saved preferences and exit
171
- --skip-harness-update Skip the installed-version notice
172
-
173
- Example:
174
- mm-harness setup-base --only core --counts core=1`
175
- },
176
- {
177
- name: "status",
178
- aliases: ["health", "home"],
179
- summary: "Home dashboard for a checkout: detected adapter, next command, and (mobile) connected devices with the selected target.",
180
- example: "mm-harness status",
181
- helpText: `mm-harness status [flags]
182
-
183
- Home dashboard for the current checkout \u2014 the detected adapter, the next command
184
- to run, and (mobile only) the connected devices with live app state. Aliases: health, home.
185
-
186
- --target <path> Checkout path (default: cwd)
187
- --json Machine-readable envelope { adapter, target, view, devices[], next }
188
- --fast Skip all live-state probes (instant output, safe for scripts)
189
- --all-devices Mobile only: show every connected device instead of the slot-scoped target
190
- --watch Live read-only view of the task this checkout is working through (refreshes every 2s, Ctrl-C to leave)
191
- --task <dir> Task directory for the view (default: the most recently marked one under temp/tasks/)
192
-
193
- The task view reads task files only \u2014 no probe, no launch, no write \u2014 and shows
194
- the same facts whether a skill or a control plane started the run: slot and
195
- device isolation, template and flow, fixture and sandbox readiness, then the
196
- checklist with the time each row took, how long the current row has been
197
- running, and acceptance criteria with recorded verdicts. The home dashboard
198
- also shows criteria for an ongoing task. --task without --watch prints it once.
199
- With --json, --watch emits one
200
- object per change (newline-delimited) instead of redrawing.
201
-
202
- The checkout view (also under "view" in --json, and printed by doctor) is what
203
- this checkout is bound to: the runtime-context fields that keep it off other
204
- slots' ports and devices, the recipe libraries this machine resolves, and the
205
- wallet fixture it last applied.
206
-
207
- Human output: static device list prints immediately; live-state lines append
208
- after the ~2s probe window (screen, wallet, account, fixture per device).
209
- By default mobile status shows the configured target only; --device <id>
210
- switches the active target for that command. Other devices that answer on the
211
- same Metro are reported as additional reachable targets.
212
-
213
- The devices[] section carries per device:
214
- { platform, id, name, state, selected } always present
215
- { currentScreen, walletState, selectedAccount, fixtureStatus } when bridge reachable
216
- { liveState: 'no-bridge' } when bridge unreachable or --fast
217
-
218
- All RN targets attached to the checkout's Metro are probed and matched to their
219
- devices; a device whose target cannot be confidently matched (Metro's device
220
- description matches neither the serial nor the model name) degrades to
221
- liveState:'no-bridge'. An attached target whose build predates __AGENTIC__
222
- reports liveState:'bridge-absent'.
223
- Extension/core live-state probing is V1-pending (use doctor for runtime status).
224
-
225
- Example:
226
- mm-harness status
227
- mm-harness status --json
228
- mm-harness status --fast
229
- mm-harness status --watch`
230
- },
231
- {
232
- name: "checklist",
233
- summary: "Mark checklist progress and close out scrubbed learning packages.",
234
- example: "mm-harness checklist mark <task-dir> start",
235
- helpText: `mm-harness checklist <mark|closeout> <task-dir> [step] [options]
236
-
237
- mark delegates checklist state to @farmslot/agent-runtime. Successful complete
238
- and no-change marks stage a learning package through @farmslot/handoff.
239
- closeout exposes that same Handoff boundary directly; --share remains explicit.
240
- The task directory is the one mm-harness task init wrote. Without a
241
- checklist-target.json it marks CHECKLIST.md and writes SIGNAL.json; only a
242
- role switch writes that manifest, to point at its own checklist.
243
-
244
- Steps: start | 1 | 2 | ... | complete | no-change | blocked
245
-
246
- Example:
247
- mm-harness checklist mark temp/tasks/recipe-cook/<task> start
248
- mm-harness checklist mark temp/tasks/recipe-cook/<task> 1
249
- mm-harness checklist mark temp/tasks/recipe-cook/<task> sub start qa-cases --step 2 --from inputs/qa-cases.md
250
- mm-harness checklist mark temp/tasks/recipe-cook/<task> sub qa-cases 1
251
- mm-harness checklist mark temp/tasks/recipe-cook/<task> complete --mark-last
252
- mm-harness checklist closeout temp/tasks/recipe-cook/<task> --share`
253
- },
254
- {
255
- name: "prepare",
256
- summary: "Bring this checkout to a proven-ready state and write the readiness record.",
257
- example: "mm-harness prepare --platform extension",
258
- helpText: `mm-harness prepare [flags]
259
-
260
- Run the readiness chain for this checkout and record what it did as one
261
- protocol readiness record (sandbox.json). Steps run in order and stop at the
262
- first failure; later steps are recorded as skipped.
263
-
264
- doctor --fix status launch --verify fixtures set verify
265
-
266
- Core skips launch and fixtures (headless; verify is its readiness gate).
267
- Mobile infers ios|android from the connected devices reported by status and
268
- refuses with the numbered choice when that is ambiguous.
269
-
270
- --target <path> Checkout path (default: cwd)
271
- --platform <mobile|extension|core|terminal> Force the platform (auto-detected inside a checkout)
272
- --mobile-platform <ios|android> Mobile target when status cannot decide it
273
- --clear-metro Mobile only: clear Metro's transform cache in the launch step
274
- --artifacts-dir <dir> Where sandbox.json and the per-step reports land
275
- (default: <target>/temp/recipe/runtime)
276
- --json Print the readiness record
277
-
278
- Exit 0 when the record is ready, 1 otherwise.
279
-
280
- Example:
281
- mm-harness prepare
282
- mm-harness prepare --platform mobile --mobile-platform ios --json
283
- mm-harness prepare --artifacts-dir temp/tasks/fix/TAT-1/artifacts --json`
284
- },
285
- {
286
- name: "task",
287
- summary: "Create the task directory an agent works from, out of a shared execution template.",
288
- example: 'mm-harness task init temp/tasks/fix/TAT-1 --flow fix-bug --template fix-bug/mobile --title "Wrong total"',
289
- helpText: `mm-harness task init <task-dir> [options]
290
-
291
- Write the task directory from a selected execution template: TASK.md, the
292
- CHECKLIST.md whose boxes are the steps, a mark shim routed through
293
- mm-harness checklist mark, and inputs/ (handoff.json, worker-terminal-contract.json).
294
- checklist-target.json is NOT written \u2014 absent means CHECKLIST.md + SIGNAL.json.
295
-
296
- Applied when you do not pass them: --surface skill, --mark-command for this
297
- executable, and --platform from the detected checkout.
298
-
299
- Task:
300
- --title <t> Task title (required)
301
- --task-text <s> | --task-file <p> Task description, inline or from a file
302
- --acceptance <criterion> Authored acceptance criterion (repeatable, preserved in order)
303
- --ticket <key> Ticket key
304
- --source-ref <url> Where the task came from
305
-
306
- Selection (same catalog as execution-template):
307
- --flow <flow> Exact flow (required)
308
- --run-mode <mode> autonomous|interactive|validation (optional;
309
- set by the Farmslot control plane, omit for
310
- skill/manual runs)
311
- --platform <platform> Exact platform
312
- --template <id> Exact template id (required)
313
- --domain <domain> Exact domain
314
- --dir <path> Custom flow-tree source (repeatable)
315
- --domain-dir <domain=path> Domain-scoped team source (repeatable)
316
- --package-templates <path> Canonical package flow-tree root
317
- --package-id <id> Source label for the package root
318
- --project-worker <path> Project template directory
319
- --project-name <name> Source label for project templates
320
-
321
- Identity and rendering:
322
- --surface <s> Handoff surface (default: skill)
323
- --project <name> --repo <owner/name> --attempt-id <id>
324
- --var KEY=VALUE Template placeholder value (repeatable)
325
- --task-dir-label <label> Task directory as written into the documents
326
- --mode-preamble <text> Run-mode preamble for TASK.md
327
- --addendum-file <path> Extra task-document section
328
- --mark-command <cmd> Override the recorded mark command
329
- --json Machine-readable output
330
-
331
- Example:
332
- mm-harness task init temp/tasks/fix/TAT-1 --flow fix-bug \\
333
- --template fix-bug/mobile --package-templates <path> \\
334
- --title "Wrong total on the receipt" --ticket TAT-1 --json`
335
- },
336
- {
337
- name: "execution-template",
338
- summary: "Discover, validate, and materialize shared agent checklists.",
339
- example: "mm-harness execution-template list --package-templates <path> --json",
340
- helpText: `mm-harness execution-template <list|materialize|lint|new> [options]
341
-
342
- Shared Markdown checklist catalog used by direct and orchestrated workflows.
343
- Sources use the flow-tree shape <root>/<flow>/<variant>.md.
344
-
345
- list List compatible templates and portable source provenance.
346
- materialize Select one template, copy its immutable snapshot, and optionally
347
- write a provenance record.
348
- lint Validate one template file or flow-tree directory.
349
- new Create a minimal starter template.
350
-
351
- Common source/filter options:
352
- --dir <path> Custom flow-tree source (repeatable)
353
- --domain-dir <domain=path> Domain-scoped team source (repeatable)
354
- --package-templates <path> Canonical package flow-tree root
355
- --package-id <id> Source label for the package root
356
- --project-worker <path> Project template directory
357
- --project-name <name> Source label for project templates
358
- --flow <flow> Exact flow
359
- --platform <platform> Exact platform
360
- --run-mode <mode> autonomous|interactive|validation (optional;
361
- set by the Farmslot control plane, omit for
362
- skill/manual runs)
363
- --domain <domain> Exact domain
364
- --id <id> Exact template id
365
- --json Machine-readable output
366
-
367
- Materialize:
368
- mm-harness execution-template materialize <output> --flow fix-bug \\
369
- --platform mobile --id fix-bug/mobile \\
370
- --package-templates <path> --provenance <path> --json`
371
- },
372
- {
373
- name: "actions",
374
- summary: "Discover typed single operations and their fields.",
375
- example: "mm-harness actions --adapter mobile",
376
- helpText: `mm-harness actions [query] [flags]
377
-
378
- Discover typed single operations and their field schemas for this checkout.
379
-
380
- query Search names, categories, fields, and descriptions (typo-tolerant)
381
- --action <name> Describe one action; fuzzy-resolves like call (short or full name)
382
- --matrix Compare action availability across Mobile, Extension, Core, and Terminal
383
- --categories List compact action categories and counts
384
- --category <name> List only one category (for example ui, wallet, or perps)
385
- --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
386
- --target <path> Checkout path (default: cwd)
387
- --raw Dump raw action registry JSON
388
- --json Machine-readable output
389
-
390
- Example:
391
- mm-harness actions --adapter mobile
392
- mm-harness actions --matrix --json
393
- mm-harness actions positions --adapter mobile
394
- mm-harness actions --adapter mobile --categories --json
395
- mm-harness actions --adapter mobile --category ui --json
396
- mm-harness actions --adapter mobile --action assert_orders
397
- mm-harness actions --adapter extension --raw`
398
- },
399
- {
400
- name: "stop",
401
- summary: "Stop this checkout runtime (mobile Metro / Extension watcher + owned browser) and close its log windows.",
402
- example: "mm-harness stop",
403
- helpText: `mm-harness stop [flags]
404
-
405
- Stop the runtime this checkout owns and close its tmux log-tail windows,
406
- scoped to this checkout so concurrent slots are untouched. Idempotent \u2014
407
- nothing running is success, not an error. Behavior is per platform:
408
- mobile stop the port-scoped Metro dev server
409
- extension stop webpack watcher, owned Chrome/CDP profile processes, viewers, and stale markers
410
- core headless \u2014 no dev server to stop (teaching error)
411
-
412
- --port <port> Metro/webpack port override; Extension CDP remains checkout-context scoped
413
- --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
414
- --target <path> Checkout path (default: cwd)
415
- --json Machine-readable output
416
-
417
- Example:
418
- mm-harness stop
419
- mm-harness stop --port 8061`
420
- },
421
- {
422
- name: "call",
423
- summary: "Run one action in isolation as a one-node recipe through the real engine path (fuzzy short names; --arg k=v; same trace/evidence as run).",
424
- example: "mm-harness call ensure_unlocked",
425
- helpText: `mm-harness call <action> [key=value ...] [--arg k=v ...] [flags]
426
-
427
- Run one action in isolation as a one-node recipe through the real engine path.
428
- Fuzzy short-name: 'ensure_unlocked' resolves to 'metamask.wallet.ensure_unlocked'
429
- if unique; ambiguous = exit 2. Pass action fields as key=value shorthand or
430
- with --arg k=v; numbers, booleans, arrays, and objects are parsed as JSON-like values.
431
- Actions differ per adapter \u2014 list this checkout's with:
432
- mm-harness actions.
433
-
434
- --list List actions accepted by call for this adapter; no <action> needed
435
- --domain <name> With --list: filter by action domain, e.g. perps or swap-bridge
436
- --source <name> With --list: filter by origin, e.g. metamask, perps, or official
437
- --sort <domain|name|library> With --list: grouping/order (default: domain; JSON: name)
438
- --arg k=v Action field value (repeatable; equivalent to key=value shorthand)
439
- --device <udid|serial|name> Mobile only: target this device (sets IOS_SIMULATOR / ADB_SERIAL and updates agentic-runtime.json). Without it, >1 connected mobile device fails fast and lists them. Do not shut down other devices to force selection.
440
- --cdp-port <port> Extension only: bind the action to this live browser runtime.
441
- --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
442
- --target <path> Checkout path (default: cwd)
443
- --artifacts-dir <dir> Where to write evidence (default: temp dir)
444
- --action-manifest <path> Override the action manifest
445
- --library <name=path> Add/override a recipe-library source (repeatable)
446
- --heal <off|infra-only|auto> Healing policy (default: infra-only); auto-ensures the overlay
447
- --json Machine-readable output
448
-
449
- Example (real actions; run mm-harness actions for this checkout's full set):
450
- mm-harness call ensure_unlocked --adapter extension # a wallet action (extension/mobile)
451
- mm-harness call navigate page=perps --adapter mobile
452
- mm-harness call command cmd="echo hi" --adapter core # the universal action (all adapters)
453
-
454
- Remote feature flags (mobile applies at runtime; extension pins at launch):
455
- mm-harness call metamask.feature_flags.read --adapter mobile
456
- mm-harness call metamask.feature_flags.set --adapter mobile \\
457
- --arg 'flags={"perpsTAT3938AbtestScreenVsBottomSheet":"treatment"}'
458
- mm-harness call metamask.feature_flags.clear --adapter mobile`
459
- },
460
- {
461
- name: "run",
462
- summary: "Execute a complete proof and write its evidence.",
463
- example: "mm-harness run recipe.json",
464
- helpText: `mm-harness run <recipe|recipe.json> [key=value ...] [flags]
465
-
466
- Validate and execute a complete proof, then write summary, trace, diagnostics,
467
- and artifacts. Inside a checkout, the adapter, target, ports, and artifact
468
- directory are normally detected automatically.
469
-
470
- Discovery:
471
- --list List complete recipes accepted by run for this adapter; no <recipe> needed
472
- --domain <name> With --list: filter by recipe domain, e.g. perps or swap-bridge
473
- --source <name> With --list: filter by library origin, e.g. metamask or perps
474
- --sort <domain|name|library> With --list: grouping/order (default: domain; JSON: name)
475
- --describe Show one recipe's parameters, composed actions/recipes, and runnable command
476
- --plan Validate + print execution plan, touching nothing. Exit 5 if invalid.
477
- --proof Preflight behavioral proof bindings before planning/execution; runtime evidence is still required.
478
-
479
- Common overrides:
480
- --device <udid|serial|name> Mobile only: target this device (sets IOS_SIMULATOR / ADB_SERIAL and updates agentic-runtime.json). Without it, >1 connected mobile device fails fast and lists them. Do not shut down other devices to force selection.
481
- --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
482
- --platform <ios|android> Mobile platform preference; use --adapter for the product adapter
483
- --target <path> Checkout path (default: cwd)
484
- --artifacts-dir <dir> Override evidence output (default: active task/artifacts, otherwise a checkout-local temp/recipe/runs/<run>)
485
- --funding-request <absolute-file> Original consented mutation request for external-mutation actions
486
- --funding-preflight <absolute-file> Eligible funding preflight bound to the request bytes
487
- --funding-release-proof <absolute-file> Exact release proof independently bound to funding eligibility
488
- --funding-balance-proof <absolute-file> Exact balance proof and contained visible evidence chain
489
- --library <name=path> Add/override a recipe-library source (repeatable)
490
- --heal <off|infra-only|auto> Healing policy (default: infra-only); auto-ensures the overlay
491
- --json Machine-readable output
492
- --json-stream Line-flushed JSONL progress + terminal event
493
- --record-video=full-run Record a video of the run
494
- --hud <auto|show|hide> Recipe HUD policy (auto shows normal Mobile recipes and a headful terminal browser; hide is explicit)
495
-
496
- Advanced integrations:
497
- runtime: --cdp-port, --watcher-port/--metro-port, --runtime-dir, --slot,
498
- --validation-runtime-dir, --launch-existing-dist
499
- managed trust boundary (not needed for normal runs; Farmslot supplies it):
500
- --source-trust, --source-kind, --source-name, --source-digest,
501
- --approve-plan (binds approval to the exact reviewed plan)
502
- schema: --action-manifest
503
-
504
- Example:
505
- mm-harness run wallet.smoke --describe
506
- mm-harness run perps.release-market-search query=Bitcoin --plan --adapter mobile
507
- mm-harness run recipe.json market=ETH --adapter extension`
508
- },
509
- {
510
- name: "last",
511
- summary: "Show the last significant command, verdict, timestamps, and evidence paths for this checkout.",
512
- example: "mm-harness last --json",
513
- helpText: `mm-harness last [flags]
514
-
515
- Read the atomic per-checkout resumability journal. Discovery commands do not
516
- replace it, and an interrupted process remains recorded as verdict=running.
517
-
518
- --target <path> Checkout path (default: cwd)
519
- --runtime-dir <dir> Runtime dir containing command operation records
520
- --json Machine-readable envelope
521
-
522
- Example:
523
- mm-harness last --json`
524
- },
525
- {
526
- name: "doctor",
527
- summary: "Readiness check for a checkout \u2014 no app launch. --fix repairs the overlay/runtime without launching.",
528
- example: "mm-harness doctor",
529
- helpText: `mm-harness doctor [flags]
530
-
531
- Readiness check for a checkout \u2014 no app launch. Reports the same checkout view
532
- status does \u2014 runtime-context isolation, resolved recipe libraries, and the last
533
- applied wallet fixture \u2014 so there is no hunting for files.
534
-
535
- --fix Repair the overlay/runtime-context WITHOUT launching (no fixture reseed); --json adds fixed[]/failed[]
536
- --expect-live Exit-coded liveness gate only (human verdict on stderr). Use without --print-ready when callers need pass/fail without a Farmslot indicator line
537
- --print-ready Farmslot health_check mode: implies --expect-live and prints health.ready_indicator on stdout (extension/mobile: OK; core: ready)
538
- --cdp-port <port> Extension CDP port for the liveness probe (env: CDP_PORT / RECIPE_CDP_PORT)
539
- --device <udid|serial|name> Mobile only: target this device (env: IOS_SIMULATOR / ADB_SERIAL). doctor reports the connected devices; it never gates on ambiguity.
540
- --all-devices Mobile only: show every connected device instead of the slot-scoped target
541
- --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
542
- --target <path> Checkout path (default: cwd)
543
- --runtime-dir <dir> Runtime dir containing agentic-runtime.json (relative to target)
544
- --json Machine-readable output (incompatible with --print-ready)
545
-
546
- Example:
547
- mm-harness doctor
548
- mm-harness doctor --fix --json
549
- mm-harness doctor --adapter extension --target /path/to/checkout --cdp-port 6662 --print-ready
550
- mm-harness doctor --adapter mobile --target /path/to/checkout`
551
- },
552
- {
553
- name: "check",
554
- summary: "Run bounded repo-local checks for an active git diff and write validation artifacts.",
555
- example: "mm-harness check diff --profile fast",
556
- helpText: `mm-harness check diff [flags]
557
-
558
- Run bounded repo-local checks for the active git diff. Fast profile runs changed-file
559
- ESLint, Prettier, and changed test files. Full profile also runs repo typecheck
560
- when available. Does not launch an app or run a recipe.
561
-
562
- --target <path> Checkout path (default: cwd)
563
- --adapter <mobile|extension|core|terminal> Adapter label (auto-detected when possible)
564
- --base <ref> Diff base (default: PR base, then remote HEAD, then repo fallback)
565
- --profile <fast|full> Validation depth (default: fast)
566
- --fix Fix changed files, then validate them
567
- --artifacts-dir <dir> Write validation-summary.json/.md and per-check logs
568
- --json Machine-readable envelope
569
-
570
- Example:
571
- mm-harness check diff --fix --profile fast --artifacts-dir artifacts/validation
572
- mm-harness check diff --profile full --json`
573
- },
574
- {
575
- name: "recipe-quality",
576
- summary: "Build canonical recipe quality or request optional recipe advice.",
577
- example: "mm-harness recipe-quality build --input compact.json --output artifacts/recipe-quality.json",
578
- helpText: `mm-harness recipe-quality build|plan|advise|advisor-status [flags]
579
-
580
- Build artifacts/recipe-quality.json from the compact fields a recipe-quality pass
581
- produces (verdict + reasons + optional guidance/dimensions/findings/delta/training).
582
- The artifact is validated against the RecipeQualityArtifact schema before it is
583
- written \u2014 an invalid input never reaches disk. Shorthand without "build" is
584
- also accepted when --input/--output are present.
585
-
586
- Apply the quality bar from mm-harness help to the real graph and
587
- evidence, then use this command to build the canonical artifact from that
588
- compact verdict. An optional independent reviewer may still critique it.
589
-
590
- --input <compact.json> Compact verdict JSON: { "verdict": "pass|warn|fail", "reasons": [..],
591
- "betterVersionGuidance"?: [..], "dimensions"?: {..}, "trainingFields"?: {..}, \u2026 }
592
- --output <path> Where to write the built artifact (parent dirs created)
593
- --json Machine-readable envelope { status, outputPath, artifact }
594
-
595
- Exit: 0 built \xB7 2 missing/unreadable args \xB7 5 invalid input (teaching escape names the invalid field).
596
-
597
- Optional advice:
598
- advisor-status --json
599
- Resolve saved settings and overrides without reading tasks or calling a model.
600
- plan --task <task.json> --adapter <mobile|extension|core|terminal> --advisor typesafe
601
- Assess each acceptance criterion before a recipe exists. Task JSON contains
602
- acceptanceCriteria: ["criterion", "..."] (1\u201320 entries), with optional title/description.
603
- Returns known candidate refs for reuse/composition, or an explicit discovery gap.
604
- advise --recipe <recipe.json> --task <TASK.md> --adapter <mobile|extension|core|terminal>
605
- --advisor <provider|off> Override saved policy for this task (currently: typesafe)
606
- --advisor-model <model> Override the provider default model or alias
607
- --advisor-stages <csv> Select plan,draft,evidence (saved/default: plan)
608
- --evidence <file.json> Explicit trace, summary, or evidence metadata to review
609
- --params <json> Root recipe parameter values
610
- --library <name=path> Resolve the same library sources as run (repeatable)
611
- --output <advice.json> Optional separate advisory artifact
612
-
613
- Precedence: explicit CLI > MM_HARNESS_ADVISOR[_MODEL|_STAGES] > advisor settings > disabled.
614
- Set MM_HARNESS_ADVISOR to a provider or off for a Farmslot run. A key never enables advice.
615
- Saved settings only run selected stages; an explicit --advisor call can request a stage directly.
616
- --advisor off disables calls regardless of saved settings. No disabled advice artifact is written.
617
- TypeSafe uses TYPESAFE_API_KEY. Missing keys skip advice; provider failures report unavailable.
618
- Results record provider, requestedModel, and the model that answered.
619
- Advice never changes recipe execution, canonical quality, or existing exit status.
620
- Input must be statically valid. Context beyond the bound is skipped, never truncated.
621
-
622
- Example:
623
- mm-harness recipe-quality build --input compact.json --output artifacts/recipe-quality.json
624
- mm-harness recipe-quality --input compact.json --output artifacts/recipe-quality.json
625
- mm-harness recipe-quality build --input compact.json --output artifacts/recipe-quality.json --json
626
- mm-harness recipe-quality advise --recipe artifacts/recipe.json --task TASK.md --adapter extension --advisor typesafe --output artifacts/recipe-advice.json --json
627
- mm-harness recipe-quality plan --task task.json --adapter extension --advisor typesafe --output artifacts/recipe-plan.json --json`
628
- },
629
- {
630
- name: "provision",
631
- summary: "Install the cached Runway iOS dev client on a prepared mobile slot (no deps, no Metro).",
632
- example: "mm-harness provision runway ios --adapter mobile",
633
- helpText: `mm-harness provision [runway ios] [flags]
634
-
635
- Install the cached Runway iOS dev client on the slot simulator. This is a thin
636
- provisioning path only: artifact cache + simulator create + simctl install.
637
- JavaScript dependencies and Metro remain dispatch-time launch concerns.
638
-
639
- --adapter <mobile|extension|core|terminal> Target adapter (mobile supported; extension/core/terminal teach)
640
- --target <path> Slot checkout path (default: cwd)
641
- --platform <ios> Platform (default ios)
642
- --simulator <name|udid> Override agentic-runtime.json simulator (alias: --device)
643
- --device <name|udid> Alias for --simulator
644
- --slot <id> Farm slot id recorded in the provision baseline
645
- --watcher-port <port> Farm Metro/watcher port carried through context and Next:
646
- --runtime-dir <dir> Runtime dir containing agentic-runtime.json (relative to target)
647
- --runtime <id> iOS runtime id used if simulator must be created
648
- --device-type <id> Device type id used if simulator must be created
649
- --branch <ref> Probe this ref before default branch
650
- --default-branch <ref> Fallback ref (default main)
651
- --run <id> Exact GitHub Actions run id
652
- --cache-root <dir> Override shared runway cache root
653
- --force Reinstall even when the app is already present
654
- --resolve-only Resolve artifact metadata only; no simulator/cache/install
655
- --json Machine-readable envelope; progress stays stderr
656
-
657
- Example:
658
- mm-harness provision runway ios --adapter mobile --target /path/to/slot
659
- mm-harness provision runway ios --adapter mobile --slot scratch-1 --runtime-dir temp/recipe/runtime-8081
660
- mm-harness provision runway ios --run 28676856835 --resolve-only --json`
661
- },
662
- {
663
- name: "install",
664
- summary: "Install the per-checkout runtime overlay, or --runway to install a cached mobile dev client.",
665
- example: "mm-harness install",
666
- helpText: `mm-harness install [flags]
667
-
668
- Install the per-checkout runtime overlay (for CI / agents).
669
- Add --runway on a mobile slot to install the cached Runway iOS dev client only:
670
- artifact cache + simulator create + simctl install. JavaScript dependencies
671
- and Metro remain dispatch-time launch concerns.
672
-
673
- --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
674
- --target <path> Checkout path (default: cwd)
675
- --runway Thin mobile Runway artifact install instead of overlay install
676
- --platform <ios> Runway platform (default ios)
677
- --simulator <name|udid> Override agentic-runtime.json simulator
678
- --runtime <id> iOS runtime id used if simulator must be created
679
- --device-type <id> Device type id used if simulator must be created
680
- --branch <ref> Probe this ref before default branch
681
- --default-branch <ref> Fallback ref (default main)
682
- --run <id> Exact GitHub Actions run id
683
- --cache-root <dir> Override shared runway cache root
684
- --force Reinstall even when the app is already present
685
- --resolve-only Runway metadata only; no simulator/cache/install
686
-
687
- Example:
688
- mm-harness install
689
- mm-harness install --adapter extension --target /path/to/checkout
690
- mm-harness install --runway --adapter mobile --target /path/to/slot`
691
- },
692
- {
693
- name: "verify",
694
- summary: "Check the overlay/runtime is present and healthy (no launch).",
695
- example: "mm-harness verify",
696
- helpText: `mm-harness verify [flags]
697
-
698
- Check the runtime overlay is present and healthy (no app launch).
699
- Extension live checks infer the checkout CDP port from runtime context,
700
- pool, or slot suffix when --cdp-port is omitted.
701
-
702
- --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
703
- --target <path> Checkout path (default: cwd)
704
- --json Machine-readable output
705
-
706
- Example:
707
- mm-harness verify
708
- mm-harness verify --adapter core --target /path/to/checkout`
709
- },
710
- {
711
- name: "cleanup",
712
- summary: "Remove the installed overlay and restore the checkout.",
713
- example: "mm-harness cleanup",
714
- helpText: `mm-harness cleanup [flags]
715
-
716
- Remove the installed overlay and restore the checkout.
717
-
718
- --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
719
- --target <path> Checkout path (default: cwd)
720
-
721
- Example:
722
- mm-harness cleanup
723
- mm-harness cleanup --adapter mobile --target /path/to/checkout`
724
- },
725
- {
726
- name: "launch",
727
- summary: "Launch the app and adapter dev server, auto-ensuring the runtime overlay first. Mobile: ios|android required.",
728
- example: "mm-harness launch ios",
729
- helpText: `mm-harness launch [ios|android] [flags]
730
-
731
- Launch the app \u2014 quick relaunch by default; --build for a full native/webpack build.
732
- Mobile: target is MANDATORY (ios | android). Extension: no target (--fullscreen default;
733
- --sidepanel to override). core is headless (teaching error \u2014 use verify/run).
734
- Terminal: starts or reuses the slot browser (visible by default) against the slot dev server.
735
- Extension quick launch REUSES a healthy live runtime (reload in place, no rebuild);
736
- --build is the escape hatch that always clean-builds + relaunches a fresh runtime.
737
-
738
- --build Full production-like build + relaunch (escape hatch; skips extension reuse)
739
- --clear-metro Mobile only: clear Metro's transform cache before launch
740
- --verify Launch then poll CDP/bridge until ready (absorbs the old \`live\`)
741
- --runway Post-launch runway check (mobile only; teaching error elsewhere)
742
- --watch Persistent webpack watcher then relaunch (extension only)
743
- --surface <fullscreen|sidepanel> Extension display mode (default: fullscreen)
744
- --sidepanel | --fullscreen Legacy aliases for --surface
745
- --url <dapp-url> Open a dapp beside the sidepanel (default: MetaMask test dapp)
746
- --remote-flag <KEY=VALUE[,...]> Pin remote feature flags. VALUE is a variant name (treatment),
747
- true/false, or JSON. Mobile applies it through
748
- RemoteFeatureFlagController once the bridge is ready and fails the
749
- launch if it does not read back; Extension pins it into the per-run
750
- dist snapshot manifest (so it skips the reuse fast path).
751
- --heal <off|infra-only|auto> Healing policy (default auto); bounds always enforced
752
- --device <udid|name> Target simulator/device (env: IOS_SIMULATOR / ADB_SERIAL)
753
- --cdp-port <port> Extension CDP port (env: CDP_PORT / RECIPE_CDP_PORT)
754
- --watcher-port <port> Metro/webpack port (env: WATCHER_PORT / METRO_PORT)
755
- --signer <extension|injected> Terminal: MetaMask extension (default) or the injected test wallet
756
- --account <name> Terminal: wallet-fixture account (default dev1)
757
- --fresh-profile Terminal: start from a new browser profile
758
- --headless Terminal: hide the browser (env: TERMINAL_HEADLESS=1); default is a
759
- visible window placed per slot (--headful is accepted as the default)
760
- --slow-mo <ms> Terminal: pause before each UI action (env: TERMINAL_SLOW_MO; default 0)
761
- --network <testnet|mainnet> Terminal: venue the slot dev server serves (default testnet)
762
- --mainnet-confirmation <REAL_FUNDS> Terminal: required with --network mainnet (real funds)
763
- Terminal flags are rejected for other adapters. The terminal launch
764
- refuses unless the farm next dev server on --watcher-port forces
765
- Hyperliquid testnet, or --network mainnet --mainnet-confirmation REAL_FUNDS
766
- is passed (a recipe launch node carries network/mainnet_confirmation).
767
- --adapter <mobile|extension|terminal> Target adapter (auto-detected inside a checkout)
768
- --target <path> Checkout path (default: cwd)
769
- --json Machine-readable summary (recovered[] / mutations[] / phase)
770
- --json-stream Line-flushed JSONL progress + terminal event
771
-
772
- Example:
773
- mm-harness launch ios
774
- mm-harness launch android --clear-metro --verify
775
- mm-harness launch ios --build
776
- mm-harness launch --verify --json
777
- mm-harness launch --remote-flag perpsTAT3938AbtestScreenVsBottomSheet=treatment`
778
- },
779
- {
780
- name: "logs",
781
- summary: "Tail Metro/webpack + app logs for the active checkout.",
782
- example: "mm-harness logs",
783
- helpText: `mm-harness logs [flags]
784
-
785
- Tail Metro/webpack + app logs for the active checkout.
786
- Teaching error if nothing is running (points at launch).
787
-
788
- --full Raw log tail (default = compact) (env: RECIPE_LOG_UI)
789
- --window (Re)open the read-only tmux tail window for the resolved dev-server port (leaves the process untouched)
790
- --events <n> Compact event count (default 10) (env: RECIPE_LOG_EVENTS)
791
- --source <label> Log source per adapter \u2014 mobile: metro|app;
792
- extension: extension|dapp|webpack|watcher|rebuild.
793
- Core is headless (teaching error).
794
- --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
795
- --target <path> Checkout path (default: cwd)
796
- --json Machine-readable output
797
-
798
- Example:
799
- mm-harness logs
800
- mm-harness logs --full
801
- mm-harness logs --window`
802
- },
803
- {
804
- name: "debug",
805
- summary: "Open the debug console \u2014 extension: Chrome DevTools via CDP; mobile: RN DevTools.",
806
- example: "mm-harness debug",
807
- helpText: `mm-harness debug [flags]
808
-
809
- Open the debug console \u2014 extension: Chrome DevTools via CDP; mobile: RN DevTools.
810
-
811
- --worker Extension service-worker DevTools
812
- --dev-menu Mobile RN developer menu
813
- --no-open Resolve target/method/endpoint without opening a UI
814
- --adapter <mobile|extension> Target adapter (auto-detected inside a checkout)
815
- --target <path> Checkout path (default: cwd)
816
- --json Machine-readable output
817
-
818
- Example:
819
- mm-harness debug
820
- mm-harness debug --worker`
821
- },
822
- {
823
- name: "reload",
824
- summary: "Reload the connected Mobile or Extension runtime without restarting its dev server.",
825
- example: "mm-harness reload",
826
- helpText: `mm-harness reload [flags]
827
-
828
- Mobile broadcasts the same reload command as Metro's interactive r key.
829
- Extension refreshes its loaded UI in place over CDP. Core is headless.
830
-
831
- --adapter <mobile|extension> Target adapter (auto-detected inside a checkout)
832
- --target <path> Checkout path (default: cwd)
833
- --json Machine-readable output
834
-
835
- Example:
836
- mm-harness reload`
837
- },
838
- {
839
- name: "pin",
840
- summary: "Copy this installed mm-harness into an immutable, versioned store and print the copy for a task to run, so a later global upgrade cannot change a running task.",
841
- example: "mm-harness pin",
842
- helpText: `mm-harness pin [flags]
843
-
844
- Copies the installed mm-harness running this command into an immutable,
845
- versioned store (~/.local/share/mm-harness/pins/<version>-<hash>/; APFS clone
846
- where available) and prints the copy's executable. A task records that path,
847
- so \`npm i -g\` upgrading the global install never changes the harness a running
848
- task runs. Idempotent: the same install returns the same pin. A source checkout
849
- (MM_HARNESS_BIN) or an existing pin is printed as is.
850
-
851
- --json Machine-readable { version, source, executable, pinned, created }
852
-
853
- MM_HARNESS_PIN_ROOT overrides the store location.
854
-
855
- Example:
856
- mm-harness pin
857
- mm-harness pin --json`
858
- },
859
- {
860
- name: "update",
861
- summary: "Update the installed mm-harness to the published latest (--check reports only; --json = {current, latest, updateAvailable}).",
862
- example: "mm-harness update",
863
- helpText: `mm-harness update [flags]
864
-
865
- Update the globally-installed mm-harness to the npm registry's latest.
866
- Bare form upgrades (npm i -g @deeeed/metamask-harness@latest) and prints old \u2192 new;
867
- already-current exits 0.
868
-
869
- --check Report only \u2014 exit 0 up-to-date / exit 1 update available (no install)
870
- --json Machine-readable { current, latest, updateAvailable }
871
-
872
- A passive once-a-day nudge also prints one stderr line when a newer version exists.
873
- Silence it with MM_HARNESS_NO_UPDATE_CHECK=1 (auto-off in CI).
874
-
875
- Example:
876
- mm-harness update
877
- mm-harness update --check --json`
878
- },
879
- {
880
- name: "runtime-launch",
881
- summary: "Launch and verify an Extension runtime or local Android release artifact.",
882
- example: "mm-harness runtime-launch --adapter extension --target <checkout> --cdp-port 9222 --artifact-file <zip> --artifact-version <X.Y.Z> --artifact-sha256 <digest>",
883
- helpText: `mm-harness runtime-launch --adapter <extension|mobile> --target <checkout> [flags]
884
-
885
- Verify and run an artifact already downloaded by the validation skill. The
886
- harness performs no network acquisition. Extension ZIPs run through the
887
- packaged browser/CDP path; Android APKs install to one explicitly named device.
888
-
889
- --artifact-file <path> Artifact downloaded by the validation skill
890
- --artifact-version <X.Y.Z> Expected manifest or Android version name
891
- --artifact-build-id <id> Android version code
892
- --artifact-package-id <id> Android application ID
893
- --artifact-sha256 <digest> Trusted SHA-256 from the acquisition step
894
- --artifact-cache <dir> Extension artifact cache root
895
- --platform <android> Mobile platform (downloaded iOS is unsupported)
896
- --device <serial> Explicit Android device serial
897
- --cdp-port <port> Extension CDP port
898
- --chrome-user-data-dir <dir> Isolated Chrome profile path
899
- --reset-profile Explicitly clear that profile; default preserves wallet state
900
- --artifacts-dir <dir> Validation evidence directory
901
- --start-watch Build through the checkout watcher
902
- --build-lavamoat Build through the production-like path
903
- --remote-flag <KEY=VARIANT> Pin checkout snapshot flags (not allowed for artifacts)
904
- --json Machine-readable terminal report
905
-
906
- Example:
907
- mm-harness runtime-launch --adapter extension --target /path/to/checkout --cdp-port 9222 --artifact-file /path/to/metamask-chrome-X.Y.Z.zip --artifact-version X.Y.Z --artifact-sha256 <digest>
908
- mm-harness runtime-launch --adapter mobile --platform android --target /path/to/checkout --device <serial> --artifact-file /path/to/app.apk --artifact-version X.Y.Z --artifact-build-id <code> --artifact-package-id io.metamask --artifact-sha256 <digest>`
909
- },
910
- {
911
- name: "fixtures",
912
- summary: "Manage the canonical wallet fixture (wallet DATA only) \u2014 init / sync / set / reset / generate / finalize.",
913
- example: "mm-harness fixtures set",
914
- helpText: `mm-harness fixtures [<init|sync|set|reset|generate|finalize>] [flags]
915
-
916
- Manage the ONE canonical wallet fixture per checkout \u2014 wallet DATA only.
917
- With no action, show the current fixture status and safe next command.
918
- init Create it from --from <path>, or explicitly choose --dev for a
919
- disposable public test wallet that must never hold real funds.
920
- sync Refresh the wallet fixture files on the target.
921
- set Apply the canonical fixture (SRP/password/accounts); the password is
922
- read FROM the fixture, never typed.
923
- reset Delete the current Mobile/Extension wallet, then apply and validate
924
- the canonical fixture from a clean state.
925
- generate Render the extension fixture-state.json from a wallet fixture, for
926
- pre-launch profile prefill (extension-only). Requires --fixture and --out.
927
- finalize Seed account labels/selection into the LIVE extension over CDP and
928
- validate parity (extension-only, post-launch). Requires --fixture,
929
- --state, --cdp-port, --extension-dir.
930
- Want different accounts? Edit the fixture file directly:
931
- <checkout>/temp/recipe/runtime/wallet-fixture.json
932
-
933
- --from <path> Existing wallet fixture source (init)
934
- --dev Create a disposable public test wallet (init)
935
- --force Replace an existing canonical fixture (init)
936
- --fixture <path> Wallet fixture path (generate/finalize input; agent override for set/sync \u2014 env: RECIPE_WALLET_FIXTURE)
937
- --out <path> generate: output fixture-state.json; finalize: optional validation report
938
- --state <path> finalize: the pre-launch fixture-state.json to seed
939
- --cdp-port <port> finalize: CDP port of the running extension
940
- --extension-dir <path> finalize: loaded extension dist (e.g. dist/chrome)
941
- --extension-id-file <path> finalize: optional file to read/write the resolved extension id
942
- --action-manifest <path> Extension set: override the wallet action manifest
943
- --remote-flag <KEY=VALUE[,...]> set/reset: one-run override layered over the fixture's remoteFeatureFlags
944
- --adapter <mobile|extension> Target adapter (auto-detected inside a checkout)
945
- --target <path> Checkout path (default: cwd)
946
- --device <udid|serial|name> Mobile only: target this device for sync/set
947
- --json Machine-readable output
948
-
949
- Example:
950
- mm-harness fixtures init --from /secure/path/wallet-fixture.json
951
- mm-harness fixtures init --dev
952
- mm-harness fixtures sync
953
- mm-harness fixtures set
954
- mm-harness fixtures reset
955
- mm-harness fixtures generate --fixture wallet-fixture.json --out fixture-state.json
956
- mm-harness fixtures finalize --fixture wallet-fixture.json --state fixture-state.json --cdp-port 6661 --extension-dir dist/chrome`
66
+ function hostVersion() {
67
+ return JSON.parse(fs.readFileSync(path.join(harnessHost().packageRoot, "package.json"), "utf8")).version;
68
+ }
69
+ const stopOptions = {
70
+ companions: async (target) => {
71
+ const collector = await stopProjectCollector(target);
72
+ return collector.stopped && collector.pid !== null ? [{ label: "analytics collector", pid: collector.pid }] : [];
957
73
  }
958
- ];
959
- const PUBLIC_COMMAND_EXAMPLES = Object.fromEntries(
960
- REAL.map((command) => [command.name, command.example])
961
- );
962
- function assertPublicContractMatchesSurface() {
963
- const registered = REAL.map((command) => command.name).sort();
964
- const contracted = publicCommandNames().sort();
965
- if (registered.join("\n") !== contracted.join("\n")) {
966
- throw new Error(
967
- `public command contract drift: registered=[${registered.join(", ")}], contracted=[${contracted.join(", ")}]`
968
- );
74
+ };
75
+ const targetDevice = (command, adapter, options) => applyDeviceTargeting(command, adapter, options, { gate: true, rerun: "" });
76
+ const runOptions = {
77
+ engine: metaMaskEngine,
78
+ targetDevice,
79
+ plan: {
80
+ // The wallet fixture `fixtures set` seeds.
81
+ steps: (target) => [{
82
+ step: "fixture.file",
83
+ confidence: "static",
84
+ status: "ok",
85
+ detail: fs.existsSync(walletFixturePath(target)) ? `present: ${walletFixturePath(target)}` : `absent: ${walletFixturePath(target)} (fixtures set to seed)`
86
+ }],
87
+ launchDetail: "would launch/attach the app + heal transport (Metro/Chrome/CDP) before executing"
969
88
  }
970
- for (const command of REAL) {
971
- const registeredAliases = [...command.aliases ?? []].sort();
972
- const contractedAliases = [...PUBLIC_COMMAND_CONTRACTS[command.name]?.aliases ?? []].sort();
973
- if (registeredAliases.join("\n") !== contractedAliases.join("\n")) {
974
- throw new Error(
975
- `public command alias contract drift for ${command.name}: registered=[${registeredAliases.join(", ")}], contracted=[${contractedAliases.join(", ")}]`
976
- );
89
+ };
90
+ const callOptions = {
91
+ engine: metaMaskEngine,
92
+ targetDevice,
93
+ // A wallet action makes the most useful first example.
94
+ exampleAction: (names) => names.find((name) => /wallet|unlock/u.test(name))
95
+ };
96
+ function overlay(command) {
97
+ return async (args) => {
98
+ if (command === "verify" && process.env.MM_HARNESS_SKIP_OVERLAY_ENSURE !== "1") {
99
+ try {
100
+ const { options } = parseArgs(args);
101
+ const target = targetPath(options);
102
+ const adapterRaw = (typeof options["adapter"] === "string" ? options["adapter"] : void 0) ?? (typeof options["platform"] === "string" ? options["platform"] : void 0) ?? detectAdapter(target);
103
+ if (adapterRaw && adapterRegistry.has(adapterRaw)) {
104
+ await handleHarness(["install", "--platform", adapterRaw, "--target", target, "--quiet"]);
105
+ }
106
+ } catch {
107
+ }
977
108
  }
978
- }
109
+ const code = await handleHarness([command, ...args], overlayCommandOptions);
110
+ if (code === 0 && command === "install") {
111
+ try {
112
+ invalidateCompletionCache(targetPath(parseArgs(args).options));
113
+ } catch {
114
+ }
115
+ }
116
+ return code;
117
+ };
979
118
  }
980
- assertPublicContractMatchesSurface();
119
+ function withManifestEnvelope(command, run) {
120
+ return async (args) => {
121
+ try {
122
+ return await run(args);
123
+ } catch (error) {
124
+ const own = args.includes("--") ? args.slice(0, args.indexOf("--")) : args;
125
+ if (!(error instanceof ActionManifestError) || !own.includes("--json")) throw error;
126
+ console.log(JSON.stringify({
127
+ schemaVersion: 1,
128
+ command,
129
+ status: "fail",
130
+ exitCode: 1,
131
+ error: { code: error.code, message: error.message, userAction: error.userAction }
132
+ }, null, 2));
133
+ return 1;
134
+ }
135
+ };
136
+ }
137
+ const PUBLIC_RUN = {
138
+ help: { run: (args) => handleHelp(args, { packageRoot, harnessVersion: hostVersion() }), nudge: false },
139
+ review: { run: handleReview },
140
+ domain: { run: handleDomain },
141
+ "pr-body": { run: handlePrBody },
142
+ config: { run: handleConfig },
143
+ tutorial: { run: (args) => handleTutorial(args, { harnessVersion: hostVersion() }), exit: "now", nudge: false },
144
+ "setup-base": { run: handleSetupBase, raw: true, exit: "now", nudge: false },
145
+ status: { run: (args) => handleStatus(parseArgs(args)) },
146
+ checklist: { run: handleChecklist },
147
+ prepare: { run: handlePrepare },
148
+ task: { run: handleTaskInit },
149
+ "execution-template": { run: handleExecutionTemplate },
150
+ actions: { run: (args) => handleActions(parseArgs(args), { catalog: metaMaskEngine }) },
151
+ stop: { run: (args) => handleStop(args, stopOptions) },
152
+ call: { run: (args) => handleCall(args, callOptions) },
153
+ run: { run: (args) => handleRun(args, runOptions) },
154
+ last: { run: (args) => handleLast(parseArgs(args)) },
155
+ doctor: { run: (args) => handleDoctor(parseArgs(args)) },
156
+ check: { run: handleCheck },
157
+ "recipe-quality": { run: handleRecipeQuality },
158
+ provision: { run: (args) => handleProvision(parseArgs(args)) },
159
+ install: { run: overlay("install") },
160
+ verify: { run: overlay("verify") },
161
+ cleanup: { run: overlay("cleanup") },
162
+ launch: { run: handleLaunch },
163
+ "runtime-launch": { run: (args) => handleRuntimeLaunch(parseArgs(args)) },
164
+ logs: { run: handleLogs },
165
+ debug: { run: handleDebug },
166
+ reload: { run: handleReload },
167
+ update: { run: handleUpdate, exit: "now", nudge: false },
168
+ pin: { run: handlePin, exit: "now", nudge: false },
169
+ fixtures: { run: handleFixtures }
170
+ };
171
+ const HIDDEN_RUN = {
172
+ "completion-candidates": { run: handleCompletionCandidates, nudge: false },
173
+ "runtime-health": { run: (args) => handleRuntimeHealth(parseArgs(args)) },
174
+ "runtime-decision": { run: (args) => handleRuntimeDecision(parseArgs(args, { recordIsFlag: true })) },
175
+ "resolve-extension": { run: (args) => handleResolveExtension(parseArgs(args)) },
176
+ "ensure-ready": { run: (args) => handleEnsureReady(parseArgs(args)) },
177
+ "self-test": { run: (args) => handleSelfTest(parseArgs(args)) },
178
+ // Installs/prints the bundled zsh + bash tab completion: shell tooling, no engine.
179
+ completions: {
180
+ run: (args) => spawnSync("bash", [path.join(packageRoot, "scripts", "install-completions.sh"), ...args], { stdio: "inherit" }).status ?? 1,
181
+ nudge: false
182
+ }
183
+ };
981
184
  const HELP_GROUPS = [
982
185
  {
983
186
  title: "DAILY LOOP",
@@ -1005,249 +208,64 @@ const HELP_GROUPS = [
1005
208
  commands: ["setup-base", "update", "pin"]
1006
209
  }
1007
210
  ];
1008
- function commandMeta(name) {
1009
- const real = REAL.find((command) => command.name === name);
1010
- if (real) return { summary: real.summary, example: real.example, planned: false };
1011
- return { summary: "", example: "", planned: false };
1012
- }
1013
- function adapterFromRuntimeContext(ctx) {
1014
- const raw = typeof ctx.platform === "string" ? ctx.platform : void 0;
1015
- if (!raw) return void 0;
1016
- if (raw === "mobile" || raw === "ios" || raw === "android") return "mobile";
1017
- if (raw === "extension" || raw === "chrome-extension") return "extension";
1018
- if (raw === "core") return "core";
1019
- if (raw === "terminal") return "terminal";
211
+ function slotAdapter(platform) {
212
+ if (platform === "mobile" || platform === "ios" || platform === "android") return "mobile";
213
+ if (platform === "extension" || platform === "chrome-extension") return "extension";
214
+ if (platform === "core") return "core";
215
+ if (platform === "terminal") return "terminal";
1020
216
  return void 0;
1021
217
  }
1022
- function detectedSlotLine(out) {
1023
- const ctxPath = path.join(
1024
- process.cwd(),
1025
- process.env.RECIPE_RUNTIME_DIR || "temp/recipe/runtime",
1026
- "agentic-runtime.json"
1027
- );
1028
- try {
1029
- const ctx = JSON.parse(fs.readFileSync(ctxPath, "utf8"));
1030
- const adapter = adapterFromRuntimeContext(ctx) ?? detectAdapter(process.cwd());
1031
- const parts = [];
1032
- if (ctx.slotId) parts.push(`slot ${out("ok", String(ctx.slotId))}`);
1033
- if (ctx.simulator) parts.push(`device ${out("ok", String(ctx.simulator))}`);
1034
- const devServerPort = ctx.watcherPort ?? ctx.devServerPort ?? ctx.metroPort;
1035
- const surface = adapter ? getAdapterSurface(adapter) : void 0;
1036
- if (surface && !surface.headless && devServerPort) {
1037
- parts.push(`${surface.devServer.label} :${out("ok", String(devServerPort))}`);
1038
- }
1039
- if (ctx.gitBranch) parts.push(`branch ${out("info", String(ctx.gitBranch))}`);
1040
- if (parts.length === 0) return null;
1041
- return `${out("label", "SLOT")} \u2014 this checkout is a prepared slot: ${parts.join(" \xB7 ")}`;
1042
- } catch {
1043
- return null;
1044
- }
1045
- }
1046
- function groupedHelp() {
1047
- const out = (style, text) => color(style, text, { stream: process.stdout });
1048
- const lines = [];
1049
- lines.push(`${out("bold", "mm-harness")} \u2014 one front door for the MetaMask recipe loop: launch the app, prove behavior, manage the runtime overlay.`);
1050
- lines.push("Run it from inside a MetaMask checkout; the platform (mobile | extension | core | terminal) is auto-detected.");
1051
- lines.push(`Grammar: ${out("cmd", "mm-harness <command> [target] [flags]")} (target is a positional: ios | android; flags add agent depth; --json is the agent contract)`);
1052
- if (process.env.MM_HARNESS_BIN) {
1053
- lines.push("");
1054
- lines.push(`${out("warn", "DEV OVERRIDE ACTIVE")} \u2014 this run is served by MM_HARNESS_BIN=${out("path", process.env.MM_HARNESS_BIN)} (unset it to return to the installed/global bin).`);
1055
- }
1056
- if (process.env.MM_HARNESS_BIN && process.env.MM_HARNESS_RUN_MODE) {
1057
- lines.push(`${out("label", "running from:")} ${out("path", process.env.MM_HARNESS_RUN_MODE)} (source checkout; dist shadows src when both exist)`);
1058
- }
1059
- const slotLine = detectedSlotLine(out);
1060
- if (slotLine) {
1061
- lines.push("");
1062
- lines.push(slotLine);
1063
- }
1064
- for (const group of HELP_GROUPS) {
1065
- lines.push("");
1066
- lines.push(`${out("label", group.title)} \u2014 ${group.blurb}:`);
1067
- for (const name of group.commands) {
1068
- const meta = commandMeta(name);
1069
- const planned = meta.planned ? " (planned)" : "";
1070
- lines.push(` ${out("cmd", name.padEnd(10))} ${meta.summary}${planned}`);
1071
- lines.push(` ${out("comment", meta.example)}`);
1072
- }
1073
- }
1074
- lines.push("");
1075
- lines.push("ONE bin: mm-harness is the only command. Platform is auto-detected; the positional target forces it");
1076
- lines.push("(mm-harness launch ios); platform-specific needs are FLAGS on the same command. Human happy path = the bare");
1077
- lines.push("command; agents add depth via flags (--json, --target, ports).");
1078
- lines.push("");
1079
- lines.push("SETUP \u2014 tab-completion (zsh + bash), sourced from the bundled scripts:");
1080
- lines.push(" mm-harness completions install # print the lines to add to ~/.zshrc / ~/.bashrc");
1081
- lines.push("");
1082
- lines.push("DEV/PROD \u2014 the global npm install is prod; set MM_HARNESS_BIN to a dev checkout to override:");
1083
- lines.push(" MM_HARNESS_BIN=/path/to/checkout/bin/mm-harness # unset = the installed/global bin");
1084
- lines.push("");
1085
- lines.push("See README.md for the workflow and docs/RECIPES.md for proof authoring.");
1086
- return `${lines.join("\n")}
1087
- `;
1088
- }
1089
- async function delegate(argv) {
1090
- try {
1091
- return await recipeMain(argv);
1092
- } catch (error) {
1093
- const own = argv.includes("--") ? argv.slice(0, argv.indexOf("--")) : argv;
1094
- if (error instanceof ActionManifestError && own.includes("--json")) {
1095
- console.log(JSON.stringify({
1096
- schemaVersion: 1,
1097
- command: argv[0],
1098
- status: "fail",
1099
- exitCode: 1,
1100
- error: { code: error.code, message: error.message, userAction: error.userAction }
1101
- }, null, 2));
1102
- return 1;
1103
- }
1104
- console.error(error instanceof Error ? error.message : String(error));
1105
- return error !== null && typeof error === "object" && "exitCode" in error && typeof error.exitCode === "number" ? error.exitCode : 1;
1106
- }
1107
- }
1108
- const pkgVersion = (() => {
1109
- try {
1110
- return JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8")).version ?? "unknown";
1111
- } catch {
1112
- return "unknown";
1113
- }
1114
- })();
1115
- const program = new Command();
1116
- program.name("mm-harness").description("the MetaMask recipe harness: launch the app, prove behavior, manage the runtime overlay").version(pkgVersion, "-v, --version", "Print the mm-harness version").addHelpCommand(false).helpOption("-h, --help", "Show grouped help").showHelpAfterError("(run `mm-harness --help` for the full surface)").configureHelp({ formatHelp: () => groupedHelp() });
1117
- for (const command of REAL) {
1118
- const registered = program.command(command.name).description(command.summary).allowUnknownOption().helpOption("-h, --help", "Show command help");
1119
- if (command.aliases?.length) registered.aliases(command.aliases);
1120
- registered.configureHelp({ formatHelp: () => `${command.helpText}
1121
- ` }).argument("[args...]").action(async () => {
1122
- if (command.name === "update") {
1123
- process.exit(await handleUpdate(rawArgv.slice(1)));
1124
- }
1125
- if (command.name === "pin") {
1126
- process.exit(handlePin(rawArgv.slice(1)));
1127
- }
1128
- if (command.name === "help") {
1129
- process.exitCode = handleHelp(rawArgv.slice(1), { packageRoot, harnessVersion: pkgVersion });
1130
- return;
1131
- }
1132
- if (command.name === "tutorial") {
1133
- process.exit(handleTutorial(rawArgv.slice(1), { harnessVersion: pkgVersion }));
1134
- }
1135
- process.exitCode = await withCommandJournal(command.name, rawArgv, () => delegate(rawArgv));
1136
- });
1137
- }
1138
- const HIDDEN = [
1139
- "completion-candidates",
1140
- "runtime-health",
1141
- "runtime-decision",
1142
- "resolve-extension",
1143
- "ensure-ready",
1144
- "self-test"
1145
- ];
1146
- for (const name of HIDDEN) {
1147
- program.command(name, { hidden: true }).allowUnknownOption().helpOption(false).argument("[args...]").action(async () => {
1148
- process.exit(await delegate(rawArgv));
1149
- });
1150
- }
1151
- program.command("completions").description("Install/print bundled shell tab-completion (zsh + bash)").allowUnknownOption().helpOption(false).argument("[args...]").action(() => {
1152
- const script = path.join(packageRoot, "scripts", "install-completions.sh");
1153
- const result = spawnSync("bash", [script, ...rawArgv.slice(1)], { stdio: "inherit" });
1154
- process.exit(result.status ?? 1);
1155
- });
1156
- const NUDGE_SKIP = ["help", "tutorial", "setup-base", "update", "pin", "completions", "completion-candidates"];
1157
- if (rawArgv.length > 0 && !NUDGE_SKIP.includes(rawArgv[0])) {
1158
- maybeNudge();
1159
- }
1160
- function targetFromArgv(argv) {
1161
- const index = argv.indexOf("--target");
1162
- const separate = index !== -1 ? argv[index + 1] : void 0;
1163
- const inline = argv.find((argument) => argument.startsWith("--target="))?.slice("--target=".length);
1164
- return path.resolve(separate ?? inline ?? process.cwd());
1165
- }
1166
- function hasPassthroughHelp(argv) {
1167
- const divider = argv.indexOf("--");
1168
- if (divider === -1) return false;
1169
- return argv.slice(divider + 1).some((arg) => arg === "-h" || arg === "--help");
1170
- }
1171
- function isCallActionHelp(argv) {
1172
- if (argv[0] !== "call") return false;
1173
- if (!argv[1] || argv[1].startsWith("-")) return false;
1174
- const divider = argv.indexOf("--");
1175
- const scope = divider === -1 ? argv : argv.slice(0, divider);
1176
- return scope.includes("--help") || scope.includes("-h");
1177
- }
1178
- function jsonRequestedBeforePassthrough(argv) {
1179
- const divider = argv.indexOf("--");
1180
- const scope = divider === -1 ? argv : argv.slice(0, divider);
1181
- return scope.some((argument) => argument === "--json" || argument.startsWith("--json="));
1182
- }
1183
- function jsonStreamRequestedBeforePassthrough(argv) {
1184
- const divider = argv.indexOf("--");
1185
- const scope = divider === -1 ? argv : argv.slice(0, divider);
1186
- return scope.includes("--json-stream");
1187
- }
1188
- function emitUsageError(error, json, jsonStream) {
1189
- if (jsonStream) {
1190
- const stream = new JsonStreamWriter(error.command, true);
1191
- stream.error({ code: error.code, message: error.message, userAction: error.userAction });
1192
- stream.complete("fail", 2);
1193
- } else if (json) {
1194
- process.stdout.write(
1195
- `${JSON.stringify(
1196
- {
1197
- schemaVersion: 1,
1198
- command: error.command,
1199
- status: "fail",
1200
- error: {
1201
- code: error.code,
1202
- message: error.message,
1203
- userAction: error.userAction
1204
- },
1205
- exitCode: 2
1206
- },
1207
- null,
1208
- 2
1209
- )}
1210
- `
1211
- );
1212
- } else {
1213
- const scope = error.command === "mm-harness" ? "" : ` ${error.command}`;
1214
- process.stderr.write(`\u2717 mm-harness${scope}: ${error.message}
1215
- Next: ${error.userAction}
1216
- `);
1217
- }
1218
- process.exit(2);
1219
- }
1220
- if (rawArgv.length === 0) {
1221
- process.stdout.write(groupedHelp());
1222
- process.exit(0);
1223
- }
1224
- if (rawArgv[0] === "setup-base") {
1225
- process.exit(handleSetupBase(rawArgv.slice(1)));
1226
- }
1227
- const preflightBypass = /* @__PURE__ */ new Set([
1228
- ...HIDDEN,
1229
- "completions"
1230
- ]);
1231
- if (!preflightBypass.has(rawArgv[0] ?? "")) {
1232
- const usageError = validatePublicInvocation(rawArgv, PUBLIC_COMMAND_EXAMPLES);
1233
- if (usageError) {
1234
- emitUsageError(
1235
- usageError,
1236
- jsonRequestedBeforePassthrough(rawArgv),
1237
- jsonStreamRequestedBeforePassthrough(rawArgv)
1238
- );
1239
- }
1240
- }
1241
- await hydrateLibraryPathEnv({ target: targetFromArgv(rawArgv), config: readConfig(configPath()) });
1242
- {
1243
- const refused = await loadSelectedAdapter(rawArgv[0] ?? "", rawArgv.slice(1));
1244
- if (refused !== void 0) process.exit(refused);
1245
- }
1246
- if (hasPassthroughHelp(rawArgv)) {
1247
- process.exit(await delegate(rawArgv));
1248
- }
1249
- if (isCallActionHelp(rawArgv)) {
1250
- const callHelp = REAL.find((command) => command.name === "call")?.helpText ?? "";
1251
- process.exit(await handleCallHelp(rawArgv.slice(1), callHelp, { catalog: metaMaskEngine }));
1252
- }
1253
- await program.parseAsync(process.argv);
218
+ await createHarnessCli({
219
+ host: harnessHost(),
220
+ adapters: adapterRegistry,
221
+ // Every location this machine resolves a library from reaches recipe, action
222
+ // and template discovery, which only reads RECIPE_LIBRARY_PATH, so no command
223
+ // sees a narrower set than `mm-harness domain` reports.
224
+ libraries: { hydrate: (target) => hydrateLibraryPathEnv({ target, config: readConfig(configPath()) }) },
225
+ adopt: adoptPluginAdapter,
226
+ // A library plugin's venue policy is fenced once it loads, before any help or dispatch.
227
+ afterAdapterLoad,
228
+ // `mm-harness config set libraries.<name>` libraries, read before hydration.
229
+ configuredLibraries: configuredLibrarySources,
230
+ commands: [
231
+ ...PUBLIC_COMMANDS.map((command) => ({
232
+ ...command,
233
+ ...PUBLIC_RUN[command.name],
234
+ run: withManifestEnvelope(command.name, PUBLIC_RUN[command.name].run)
235
+ })),
236
+ ...HIDDEN_COMMANDS.map((name) => ({
237
+ name,
238
+ hidden: true,
239
+ ...HIDDEN_RUN[name],
240
+ run: withManifestEnvelope(name, HIDDEN_RUN[name].run)
241
+ }))
242
+ ],
243
+ help: {
244
+ description: "the MetaMask recipe harness: launch the app, prove behavior, manage the runtime overlay",
245
+ intro: (out) => [
246
+ `${out("bold", "mm-harness")} \u2014 one front door for the MetaMask recipe loop: launch the app, prove behavior, manage the runtime overlay.`,
247
+ "Run it from inside a MetaMask checkout; the platform (mobile | extension | core | terminal) is auto-detected.",
248
+ `Grammar: ${out("cmd", "mm-harness <command> [target] [flags]")} (target is a positional: ios | android; flags add agent depth; --json is the agent contract)`
249
+ ],
250
+ groups: HELP_GROUPS,
251
+ footer: () => [
252
+ "ONE bin: mm-harness is the only command. Platform is auto-detected; the positional target forces it",
253
+ "(mm-harness launch ios); platform-specific needs are FLAGS on the same command. Human happy path = the bare",
254
+ "command; agents add depth via flags (--json, --target, ports).",
255
+ "",
256
+ "SETUP \u2014 tab-completion (zsh + bash), sourced from the bundled scripts:",
257
+ " mm-harness completions install # print the lines to add to ~/.zshrc / ~/.bashrc",
258
+ "",
259
+ "DEV/PROD \u2014 the global npm install is prod; set MM_HARNESS_BIN to a dev checkout to override:",
260
+ " MM_HARNESS_BIN=/path/to/checkout/bin/mm-harness # unset = the installed/global bin",
261
+ "",
262
+ "See README.md for the workflow and docs/RECIPES.md for proof authoring."
263
+ ],
264
+ slotAdapter
265
+ },
266
+ // Passive update nudge: one cached stderr line if a newer version is published.
267
+ beforeDispatch: () => maybeNudge(),
268
+ catalog: metaMaskEngine,
269
+ replacedOptions: REPLACED_OPTIONS,
270
+ explainInvalidChoice: explainMissingAdapterLibrary
271
+ }).run();