@deeeed/metamask-harness 0.4.0 → 0.5.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.
- package/CHANGELOG.md +19 -0
- package/dist/adapters/core/surface.js +53 -0
- package/dist/adapters/extension/ensure-ready.js +109 -0
- package/dist/adapters/extension/extension-id.js +62 -0
- package/dist/adapters/extension/runtime-decision.js +305 -0
- package/dist/adapters/extension/runtime.js +324 -0
- package/dist/adapters/extension/surface.js +69 -0
- package/dist/adapters/mobile/deps-markers.js +22 -0
- package/dist/adapters/mobile/prepare.js +146 -0
- package/dist/adapters/mobile/provision.js +465 -0
- package/dist/adapters/mobile/runtime-decision.js +315 -0
- package/dist/adapters/mobile/surface.js +54 -0
- package/dist/adapters/slot-ports.js +146 -0
- package/dist/adapters/surface.js +14 -0
- package/dist/adapters.js +485 -0
- package/dist/cli-color.js +79 -0
- package/dist/cli-commands.js +224 -0
- package/dist/cli-version.js +111 -0
- package/dist/cli.js +1571 -0
- package/dist/commands/debug.js +56 -0
- package/dist/commands/fixtures.js +153 -0
- package/dist/commands/launch.js +325 -0
- package/dist/commands/logs.js +73 -0
- package/dist/commands/shared.js +157 -0
- package/dist/commands/update.js +243 -0
- package/dist/completions-cache.js +53 -0
- package/dist/doctor.js +169 -0
- package/dist/harness.js +627 -0
- package/dist/heal-bounds.js +120 -0
- package/dist/index.js +25 -0
- package/dist/leaf-invoke.js +19 -0
- package/dist/live-adapter-contract.js +240 -0
- package/dist/manifest.js +37 -0
- package/dist/mm-harness-cli.js +521 -0
- package/dist/paths.js +179 -0
- package/dist/progress.js +94 -0
- package/dist/recording-target.js +133 -0
- package/dist/run-recording.js +271 -0
- package/dist/runner.js +88 -0
- package/dist/types.js +0 -0
- package/docs/CLI-SPEC.md +26 -3
- package/package.json +5 -1
- package/src/adapters/core/surface.ts +15 -0
- package/src/adapters/extension/surface.ts +20 -3
- package/src/adapters/mobile/provision.ts +594 -0
- package/src/adapters/mobile/surface.ts +16 -4
- package/src/adapters/slot-ports.ts +1 -1
- package/src/adapters/surface.ts +35 -0
- package/src/cli-commands.ts +1 -1
- package/src/cli.ts +149 -6
- package/src/harness.ts +140 -3
- package/src/mm-harness-cli.ts +52 -5
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { color } from "./cli-color.js";
|
|
7
|
+
import { handleUpdate, maybeNudge } from "./commands/update.js";
|
|
8
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
|
+
globalThis.__MM_HARNESS_WRAPPER__ = true;
|
|
10
|
+
const { main: recipeMain } = await import("./cli.js");
|
|
11
|
+
const rawArgv = process.argv.slice(2);
|
|
12
|
+
const REAL = [
|
|
13
|
+
{
|
|
14
|
+
name: "actions",
|
|
15
|
+
summary: "List the action vocabulary + field schemas (--raw dumps the raw action registry JSON).",
|
|
16
|
+
example: "mm-harness actions --adapter mobile",
|
|
17
|
+
helpText: `mm-harness actions [flags]
|
|
18
|
+
|
|
19
|
+
List the action vocabulary + field schemas for the checkout adapter.
|
|
20
|
+
|
|
21
|
+
--adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
|
|
22
|
+
--target <path> Checkout path (default: cwd)
|
|
23
|
+
--raw Dump raw action registry JSON
|
|
24
|
+
--json Machine-readable output
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
mm-harness actions --adapter mobile
|
|
28
|
+
mm-harness actions --adapter extension --raw`
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "stop",
|
|
32
|
+
summary: "Stop the dev server this checkout owns (mobile Metro / extension webpack watcher) and close its log window.",
|
|
33
|
+
example: "mm-harness stop",
|
|
34
|
+
helpText: `mm-harness stop [flags]
|
|
35
|
+
|
|
36
|
+
Stop the dev server this checkout owns and close its tmux log-tail window,
|
|
37
|
+
scoped to this checkout so concurrent slots are untouched. Idempotent \u2014
|
|
38
|
+
nothing running is success, not an error. Behavior is per platform:
|
|
39
|
+
mobile stop the port-scoped Metro dev server
|
|
40
|
+
extension stop the checkout's webpack watcher (pid file + orphan scan)
|
|
41
|
+
core headless \u2014 no dev server to stop (teaching error)
|
|
42
|
+
|
|
43
|
+
--port <port> Dev-server port (default: the checkout's slot context)
|
|
44
|
+
--target <path> Checkout path (default: cwd)
|
|
45
|
+
--json Machine-readable output
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
mm-harness stop
|
|
49
|
+
mm-harness stop --port 8061`
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "call",
|
|
53
|
+
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).",
|
|
54
|
+
example: "mm-harness call ensure_unlocked",
|
|
55
|
+
helpText: `mm-harness call <action> [--arg k=v ...] [flags]
|
|
56
|
+
|
|
57
|
+
Run one action in isolation as a one-node recipe through the real engine path.
|
|
58
|
+
Fuzzy short-name: 'ensure_unlocked' resolves to 'metamask.wallet.ensure_unlocked'
|
|
59
|
+
if unique; ambiguous = exit 2. Actions differ per adapter \u2014 list this checkout's
|
|
60
|
+
with: mm-harness actions.
|
|
61
|
+
|
|
62
|
+
--arg k=v Action field value (repeatable)
|
|
63
|
+
--adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
|
|
64
|
+
--target <path> Checkout path (default: cwd)
|
|
65
|
+
--artifacts-dir <dir> Where to write evidence (default: temp dir)
|
|
66
|
+
--action-manifest <path> Override the action manifest
|
|
67
|
+
--heal <off|infra-only|auto> Healing policy (default: infra-only); auto-ensures the overlay
|
|
68
|
+
--json Machine-readable output
|
|
69
|
+
|
|
70
|
+
Example (real actions; run mm-harness actions for this checkout's full set):
|
|
71
|
+
mm-harness call ensure_unlocked --adapter extension # a wallet action (extension/mobile)
|
|
72
|
+
mm-harness call command --arg cmd="echo hi" --adapter core # the universal action (all adapters)`
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "flows",
|
|
76
|
+
summary: "Browse the reusable flow library (bare = list; `flows promote` publishes a flow up a tier). Resolves across libraries \u2014 personal > team > canonical, highest tier wins.",
|
|
77
|
+
example: "mm-harness flows",
|
|
78
|
+
helpText: `mm-harness flows [flags]
|
|
79
|
+
|
|
80
|
+
Browse the reusable flow library. Flows resolve across libraries by precedence
|
|
81
|
+
(personal > team > canonical); the highest-tier copy wins and shadows lower ones.
|
|
82
|
+
Flow resolution is adapter-global, so there is no --adapter flag here.
|
|
83
|
+
|
|
84
|
+
--library <name=path> Add/override a library source (repeatable)
|
|
85
|
+
--target <path> Checkout path (default: cwd)
|
|
86
|
+
--json Machine-readable output
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
mm-harness flows`
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "run",
|
|
93
|
+
summary: "Validate + run a recipe and write evidence (summary/trace/artifacts). --plan validates + prints the plan, touching nothing.",
|
|
94
|
+
example: "mm-harness run recipe.json",
|
|
95
|
+
helpText: `mm-harness run <recipe.json> [flags]
|
|
96
|
+
|
|
97
|
+
Validate + run a recipe and write evidence (summary / trace / artifacts).
|
|
98
|
+
|
|
99
|
+
--plan Validate + print execution plan, touching nothing. Exit 5 if invalid.
|
|
100
|
+
--adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
|
|
101
|
+
--target <path> Checkout path (default: cwd)
|
|
102
|
+
--artifacts-dir <dir> Where to write evidence (required unless --plan)
|
|
103
|
+
--heal <off|infra-only|auto> Healing policy (default: infra-only); auto-ensures the overlay
|
|
104
|
+
--json Machine-readable output
|
|
105
|
+
--record-video=full-run Record a video of the run
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
mm-harness run recipe.json --plan --adapter mobile
|
|
109
|
+
mm-harness run recipe.json --adapter extension --artifacts-dir ./out`
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "doctor",
|
|
113
|
+
summary: "Readiness check for a checkout \u2014 no app launch. --fix repairs the overlay/runtime without launching.",
|
|
114
|
+
example: "mm-harness doctor",
|
|
115
|
+
helpText: `mm-harness doctor [flags]
|
|
116
|
+
|
|
117
|
+
Readiness check for a checkout \u2014 no app launch. Reports the fixture and runtime-context
|
|
118
|
+
sections so there is no hunting for files.
|
|
119
|
+
|
|
120
|
+
--fix Repair the overlay/runtime-context WITHOUT launching (no fixture reseed); --json adds fixed[]/failed[]
|
|
121
|
+
--adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
|
|
122
|
+
--target <path> Checkout path (default: cwd)
|
|
123
|
+
--json Machine-readable output
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
mm-harness doctor
|
|
127
|
+
mm-harness doctor --fix --json
|
|
128
|
+
mm-harness doctor --adapter mobile --target /path/to/checkout`
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "provision",
|
|
132
|
+
summary: "Install the cached Runway iOS dev client on a prepared mobile slot (no deps, no Metro).",
|
|
133
|
+
example: "mm-harness provision runway ios --adapter mobile",
|
|
134
|
+
helpText: `mm-harness provision [runway ios] [flags]
|
|
135
|
+
|
|
136
|
+
Install the cached Runway iOS dev client on the slot simulator. This is a thin
|
|
137
|
+
provisioning path only: artifact cache + simulator create + simctl install.
|
|
138
|
+
JavaScript dependencies and Metro remain dispatch-time launch concerns.
|
|
139
|
+
|
|
140
|
+
--adapter <mobile|extension|core> Target adapter (mobile supported; extension/core teach)
|
|
141
|
+
--target <path> Slot checkout path (default: cwd)
|
|
142
|
+
--platform <ios> Platform (default ios)
|
|
143
|
+
--simulator <name|udid> Override agentic-runtime.json simulator (alias: --device)
|
|
144
|
+
--device <name|udid> Alias for --simulator
|
|
145
|
+
--slot <id> Farm slot id recorded in the provision baseline
|
|
146
|
+
--watcher-port <port> Farm Metro/watcher port carried through context and Next:
|
|
147
|
+
--runtime-dir <dir> Runtime dir containing agentic-runtime.json (relative to target)
|
|
148
|
+
--runtime <id> iOS runtime id used if simulator must be created
|
|
149
|
+
--device-type <id> Device type id used if simulator must be created
|
|
150
|
+
--branch <ref> Probe this ref before default branch
|
|
151
|
+
--default-branch <ref> Fallback ref (default main)
|
|
152
|
+
--run <id> Exact GitHub Actions run id
|
|
153
|
+
--cache-root <dir> Override shared runway cache root
|
|
154
|
+
--force Reinstall even when the app is already present
|
|
155
|
+
--resolve-only Resolve artifact metadata only; no simulator/cache/install
|
|
156
|
+
--json Machine-readable envelope; progress stays stderr
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
mm-harness provision runway ios --adapter mobile --target /path/to/slot
|
|
160
|
+
mm-harness provision runway ios --adapter mobile --slot scratch-1 --runtime-dir temp/recipe/runtime-8081
|
|
161
|
+
mm-harness provision runway ios --run 28676856835 --resolve-only --json`
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "install",
|
|
165
|
+
summary: "Install the per-checkout runtime overlay, or --runway to install a cached mobile dev client.",
|
|
166
|
+
example: "mm-harness install",
|
|
167
|
+
helpText: `mm-harness install [flags]
|
|
168
|
+
|
|
169
|
+
Install the per-checkout runtime overlay (for CI / agents).
|
|
170
|
+
Add --runway on a mobile slot to install the cached Runway iOS dev client only:
|
|
171
|
+
artifact cache + simulator create + simctl install. JavaScript dependencies
|
|
172
|
+
and Metro remain dispatch-time launch concerns.
|
|
173
|
+
|
|
174
|
+
--adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
|
|
175
|
+
--target <path> Checkout path (default: cwd)
|
|
176
|
+
--runway Thin mobile Runway artifact install instead of overlay install
|
|
177
|
+
--platform <ios> Runway platform (default ios)
|
|
178
|
+
--simulator <name|udid> Override agentic-runtime.json simulator
|
|
179
|
+
--runtime <id> iOS runtime id used if simulator must be created
|
|
180
|
+
--device-type <id> Device type id used if simulator must be created
|
|
181
|
+
--branch <ref> Probe this ref before default branch
|
|
182
|
+
--default-branch <ref> Fallback ref (default main)
|
|
183
|
+
--run <id> Exact GitHub Actions run id
|
|
184
|
+
--cache-root <dir> Override shared runway cache root
|
|
185
|
+
--force Reinstall even when the app is already present
|
|
186
|
+
--resolve-only Runway metadata only; no simulator/cache/install
|
|
187
|
+
|
|
188
|
+
Example:
|
|
189
|
+
mm-harness install
|
|
190
|
+
mm-harness install --adapter extension --target /path/to/checkout
|
|
191
|
+
mm-harness install --runway --adapter mobile --target /path/to/slot`
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: "verify",
|
|
195
|
+
summary: "Check the overlay/runtime is present and healthy (no launch).",
|
|
196
|
+
example: "mm-harness verify",
|
|
197
|
+
helpText: `mm-harness verify [flags]
|
|
198
|
+
|
|
199
|
+
Check the runtime overlay is present and healthy (no app launch).
|
|
200
|
+
|
|
201
|
+
--adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
|
|
202
|
+
--target <path> Checkout path (default: cwd)
|
|
203
|
+
--json Machine-readable output
|
|
204
|
+
|
|
205
|
+
Example:
|
|
206
|
+
mm-harness verify
|
|
207
|
+
mm-harness verify --adapter core --target /path/to/checkout`
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: "cleanup",
|
|
211
|
+
summary: "Remove the installed overlay and restore the checkout.",
|
|
212
|
+
example: "mm-harness cleanup",
|
|
213
|
+
helpText: `mm-harness cleanup [flags]
|
|
214
|
+
|
|
215
|
+
Remove the installed overlay and restore the checkout.
|
|
216
|
+
|
|
217
|
+
--adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
|
|
218
|
+
--target <path> Checkout path (default: cwd)
|
|
219
|
+
|
|
220
|
+
Example:
|
|
221
|
+
mm-harness cleanup
|
|
222
|
+
mm-harness cleanup --adapter mobile --target /path/to/checkout`
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: "launch",
|
|
226
|
+
summary: "Launch the app (Metro/build + boot), auto-ensuring the runtime overlay first. Mobile: ios|android required.",
|
|
227
|
+
example: "mm-harness launch ios",
|
|
228
|
+
helpText: `mm-harness launch [ios|android] [flags]
|
|
229
|
+
|
|
230
|
+
Launch the app \u2014 quick relaunch by default; --build for a full native/webpack build.
|
|
231
|
+
Mobile: target is MANDATORY (ios | android). Extension: no target (--fullscreen default;
|
|
232
|
+
--sidepanel to override). core is headless (teaching error \u2014 use verify/run).
|
|
233
|
+
|
|
234
|
+
--build Full native/webpack build tier (default = quick relaunch)
|
|
235
|
+
--verify Launch then poll CDP/bridge until ready (absorbs the old \`live\`)
|
|
236
|
+
--runway Post-launch runway check (mobile only; teaching error elsewhere)
|
|
237
|
+
--watch Persistent webpack watcher then relaunch (extension only)
|
|
238
|
+
--sidepanel | --fullscreen Extension display mode (default --fullscreen)
|
|
239
|
+
--heal <off|infra-only|auto> Healing policy (default auto); bounds always enforced
|
|
240
|
+
--device <udid|name> Target simulator/device (env: IOS_SIMULATOR / ADB_SERIAL)
|
|
241
|
+
--cdp-port <port> Extension CDP port (env: CDP_PORT / RECIPE_CDP_PORT)
|
|
242
|
+
--watcher-port <port> Metro/webpack port (env: WATCHER_PORT / METRO_PORT)
|
|
243
|
+
--adapter <mobile|extension> Target adapter (auto-detected inside a checkout)
|
|
244
|
+
--target <path> Checkout path (default: cwd)
|
|
245
|
+
--json Machine-readable summary (recovered[] / mutations[] / phase)
|
|
246
|
+
|
|
247
|
+
Example:
|
|
248
|
+
mm-harness launch ios
|
|
249
|
+
mm-harness launch ios --build
|
|
250
|
+
mm-harness launch --verify --json`
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: "logs",
|
|
254
|
+
summary: "Tail Metro/webpack + app logs for the active checkout.",
|
|
255
|
+
example: "mm-harness logs",
|
|
256
|
+
helpText: `mm-harness logs [flags]
|
|
257
|
+
|
|
258
|
+
Tail Metro/webpack + app logs for the active checkout.
|
|
259
|
+
Teaching error if nothing is running (points at launch).
|
|
260
|
+
|
|
261
|
+
--full Raw log tail (default = compact) (env: RECIPE_LOG_UI)
|
|
262
|
+
--events <n> Compact event count (default 10) (env: RECIPE_LOG_EVENTS)
|
|
263
|
+
--source <label> Log source per adapter \u2014 mobile: metro|app (default metro);
|
|
264
|
+
extension: webpack|watcher|rebuild|app (default webpack).
|
|
265
|
+
Core is headless (teaching error).
|
|
266
|
+
--adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
|
|
267
|
+
--target <path> Checkout path (default: cwd)
|
|
268
|
+
--json Machine-readable output
|
|
269
|
+
|
|
270
|
+
Example:
|
|
271
|
+
mm-harness logs
|
|
272
|
+
mm-harness logs --full`
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: "debug",
|
|
276
|
+
summary: "Open the debug console \u2014 extension: Chrome DevTools via CDP; mobile: RN DevTools.",
|
|
277
|
+
example: "mm-harness debug",
|
|
278
|
+
helpText: `mm-harness debug [flags]
|
|
279
|
+
|
|
280
|
+
Open the debug console \u2014 extension: Chrome DevTools via CDP; mobile: RN DevTools.
|
|
281
|
+
|
|
282
|
+
--worker Extension service-worker DevTools
|
|
283
|
+
--dev-menu Mobile RN developer menu
|
|
284
|
+
--adapter <mobile|extension> Target adapter (auto-detected inside a checkout)
|
|
285
|
+
--target <path> Checkout path (default: cwd)
|
|
286
|
+
--json Machine-readable output
|
|
287
|
+
|
|
288
|
+
Example:
|
|
289
|
+
mm-harness debug
|
|
290
|
+
mm-harness debug --worker`
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: "update",
|
|
294
|
+
summary: "Update the installed mm-harness to the published latest (--check reports only; --json = {current, latest, updateAvailable}).",
|
|
295
|
+
example: "mm-harness update",
|
|
296
|
+
helpText: `mm-harness update [flags]
|
|
297
|
+
|
|
298
|
+
Update the globally-installed mm-harness to the npm registry's latest.
|
|
299
|
+
Bare form upgrades (npm i -g @deeeed/metamask-harness@latest) and prints old \u2192 new;
|
|
300
|
+
already-current exits 0.
|
|
301
|
+
|
|
302
|
+
--check Report only \u2014 exit 0 up-to-date / exit 1 update available (no install)
|
|
303
|
+
--json Machine-readable { current, latest, updateAvailable }
|
|
304
|
+
|
|
305
|
+
A passive once-a-day nudge also prints one stderr line when a newer version exists.
|
|
306
|
+
Silence it with MM_HARNESS_NO_UPDATE_CHECK=1 (auto-off in CI).
|
|
307
|
+
|
|
308
|
+
Example:
|
|
309
|
+
mm-harness update
|
|
310
|
+
mm-harness update --check --json`
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: "fixtures",
|
|
314
|
+
summary: "Manage the canonical wallet fixture (wallet DATA only) \u2014 sync files / set the wallet.",
|
|
315
|
+
example: "mm-harness fixtures set",
|
|
316
|
+
helpText: `mm-harness fixtures <sync|set> [flags]
|
|
317
|
+
|
|
318
|
+
Manage the ONE canonical wallet fixture per checkout \u2014 wallet DATA only.
|
|
319
|
+
sync Refresh the wallet fixture files on the target.
|
|
320
|
+
set Apply the canonical fixture (SRP/password/accounts); the password is read
|
|
321
|
+
FROM the fixture, never typed.
|
|
322
|
+
Want different accounts? Edit the fixture file directly:
|
|
323
|
+
<checkout>/temp/recipe/runtime/wallet-fixture.json
|
|
324
|
+
|
|
325
|
+
--fixture <path> Override the fixture path (agent form; env: RECIPE_WALLET_FIXTURE)
|
|
326
|
+
--adapter <mobile|extension> Target adapter (auto-detected inside a checkout)
|
|
327
|
+
--target <path> Checkout path (default: cwd)
|
|
328
|
+
--json Machine-readable output
|
|
329
|
+
|
|
330
|
+
Example:
|
|
331
|
+
mm-harness fixtures sync
|
|
332
|
+
mm-harness fixtures set`
|
|
333
|
+
}
|
|
334
|
+
];
|
|
335
|
+
const RETIRED_INTERNAL = [
|
|
336
|
+
"runtime-health",
|
|
337
|
+
"runtime-decision",
|
|
338
|
+
"runtime-launch",
|
|
339
|
+
"resolve-extension",
|
|
340
|
+
"ensure-ready",
|
|
341
|
+
"self-test"
|
|
342
|
+
];
|
|
343
|
+
const RETIRED = [
|
|
344
|
+
{
|
|
345
|
+
name: "live",
|
|
346
|
+
message: `mm-harness live is retired (exit 2).
|
|
347
|
+
|
|
348
|
+
Replacement: mm-harness launch --verify (install overlay \u2192 launch \u2192 CDP/bridge poll \u2192 smoke verify).`
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: "manifest",
|
|
352
|
+
message: `mm-harness manifest is retired (exit 2).
|
|
353
|
+
|
|
354
|
+
Replacement: mm-harness actions --raw (works now \u2014 dumps the raw action registry JSON,
|
|
355
|
+
identical to the old \`manifest --json\`). Manifest validation moved into doctor / run --plan.`
|
|
356
|
+
},
|
|
357
|
+
...RETIRED_INTERNAL.map((name) => ({
|
|
358
|
+
name,
|
|
359
|
+
message: `mm-harness ${name} is retired (exit 2).
|
|
360
|
+
|
|
361
|
+
It is internal now \u2014 its logic lives inside doctor / launch / verify self-healing.
|
|
362
|
+
Use: mm-harness doctor`
|
|
363
|
+
}))
|
|
364
|
+
];
|
|
365
|
+
const HELP_GROUPS = [
|
|
366
|
+
{
|
|
367
|
+
title: "DAILY LOOP",
|
|
368
|
+
blurb: "what a teammate runs many times a day (auto-ensures the overlay; --heal owns recovery)",
|
|
369
|
+
commands: ["launch", "stop", "logs", "debug", "fixtures"]
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
title: "DISCOVER",
|
|
373
|
+
blurb: "compose recipes from the action vocabulary + flow library (--json is the agent-primary form)",
|
|
374
|
+
commands: ["actions", "call", "flows"]
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
title: "PROVE",
|
|
378
|
+
blurb: "run recipes and inspect readiness",
|
|
379
|
+
commands: ["run", "doctor"]
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
title: "RUNTIME OVERLAY",
|
|
383
|
+
blurb: "install/verify/clean the overlay, plus thin mobile slot provisioning",
|
|
384
|
+
commands: ["provision", "install", "verify", "cleanup"]
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
title: "MAINTAIN",
|
|
388
|
+
blurb: "keep the installed harness current with the npm registry",
|
|
389
|
+
commands: ["update"]
|
|
390
|
+
}
|
|
391
|
+
];
|
|
392
|
+
function commandMeta(name) {
|
|
393
|
+
const real = REAL.find((command) => command.name === name);
|
|
394
|
+
if (real) return { summary: real.summary, example: real.example, planned: false };
|
|
395
|
+
return { summary: "", example: "", planned: false };
|
|
396
|
+
}
|
|
397
|
+
function detectedSlotLine(out) {
|
|
398
|
+
const ctxPath = path.join(
|
|
399
|
+
process.cwd(),
|
|
400
|
+
process.env.RECIPE_RUNTIME_DIR || "temp/recipe/runtime",
|
|
401
|
+
"agentic-runtime.json"
|
|
402
|
+
);
|
|
403
|
+
try {
|
|
404
|
+
const ctx = JSON.parse(fs.readFileSync(ctxPath, "utf8"));
|
|
405
|
+
const parts = [];
|
|
406
|
+
if (ctx.slotId) parts.push(`slot ${out("ok", String(ctx.slotId))}`);
|
|
407
|
+
if (ctx.simulator) parts.push(`device ${out("ok", String(ctx.simulator))}`);
|
|
408
|
+
if (ctx.metroPort) parts.push(`metro :${out("ok", String(ctx.metroPort))}`);
|
|
409
|
+
if (ctx.gitBranch) parts.push(`branch ${out("info", String(ctx.gitBranch))}`);
|
|
410
|
+
if (parts.length === 0) return null;
|
|
411
|
+
return `${out("label", "SLOT")} \u2014 this checkout is a prepared slot: ${parts.join(" \xB7 ")}`;
|
|
412
|
+
} catch {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function groupedHelp() {
|
|
417
|
+
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
418
|
+
const lines = [];
|
|
419
|
+
lines.push(`${out("bold", "mm-harness")} \u2014 one front door for the MetaMask recipe loop: launch the app, prove behavior, manage the runtime overlay.`);
|
|
420
|
+
lines.push("Run it from inside a MetaMask checkout; the platform (mobile | extension | core) is auto-detected.");
|
|
421
|
+
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)`);
|
|
422
|
+
if (process.env.MM_HARNESS_BIN) {
|
|
423
|
+
lines.push("");
|
|
424
|
+
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).`);
|
|
425
|
+
}
|
|
426
|
+
const slotLine = detectedSlotLine(out);
|
|
427
|
+
if (slotLine) {
|
|
428
|
+
lines.push("");
|
|
429
|
+
lines.push(slotLine);
|
|
430
|
+
}
|
|
431
|
+
for (const group of HELP_GROUPS) {
|
|
432
|
+
lines.push("");
|
|
433
|
+
lines.push(`${out("label", group.title)} \u2014 ${group.blurb}:`);
|
|
434
|
+
for (const name of group.commands) {
|
|
435
|
+
const meta = commandMeta(name);
|
|
436
|
+
const planned = meta.planned ? " (planned)" : "";
|
|
437
|
+
lines.push(` ${out("cmd", name.padEnd(10))} ${meta.summary}${planned}`);
|
|
438
|
+
lines.push(` ${out("comment", meta.example)}`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
lines.push("");
|
|
442
|
+
lines.push("ONE bin: mm-harness is the only command. Platform is auto-detected; the positional target forces it");
|
|
443
|
+
lines.push("(mm-harness launch ios); platform-specific needs are FLAGS on the same command. Human happy path = the bare");
|
|
444
|
+
lines.push("command; agents add depth via flags (--json, --target, ports).");
|
|
445
|
+
lines.push("");
|
|
446
|
+
lines.push("SETUP \u2014 tab-completion (zsh + bash), sourced from the bundled scripts:");
|
|
447
|
+
lines.push(" mm-harness completions install # print the lines to add to ~/.zshrc / ~/.bashrc");
|
|
448
|
+
lines.push("");
|
|
449
|
+
lines.push("DEV/PROD \u2014 the global npm install is prod; set MM_HARNESS_BIN to a dev checkout to override:");
|
|
450
|
+
lines.push(" MM_HARNESS_BIN=/path/to/checkout/bin/mm-harness # unset = the installed/global bin");
|
|
451
|
+
lines.push("");
|
|
452
|
+
lines.push("See docs/MENTAL-MODEL.md (overview) and docs/CLI-SPEC.md (full contract).");
|
|
453
|
+
return `${lines.join("\n")}
|
|
454
|
+
`;
|
|
455
|
+
}
|
|
456
|
+
async function delegate(argv) {
|
|
457
|
+
try {
|
|
458
|
+
return await recipeMain(argv);
|
|
459
|
+
} catch (error) {
|
|
460
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
461
|
+
return error !== null && typeof error === "object" && "exitCode" in error && typeof error.exitCode === "number" ? error.exitCode : 1;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function translateActionsRaw(argv) {
|
|
465
|
+
const rest = argv.slice(1).filter((arg) => arg !== "--raw");
|
|
466
|
+
const withJson = rest.includes("--json") ? rest : [...rest, "--json"];
|
|
467
|
+
return ["manifest", ...withJson];
|
|
468
|
+
}
|
|
469
|
+
const program = new Command();
|
|
470
|
+
program.name("mm-harness").description("the MetaMask recipe harness: launch the app, prove behavior, manage the runtime overlay").helpOption("-h, --help", "Show grouped help").showHelpAfterError("(run `mm-harness --help` for the full surface)").configureHelp({ formatHelp: () => groupedHelp() });
|
|
471
|
+
for (const command of REAL) {
|
|
472
|
+
program.command(command.name).description(command.summary).allowUnknownOption().helpOption(false).argument("[args...]").action(async () => {
|
|
473
|
+
const ddIdx = rawArgv.indexOf("--");
|
|
474
|
+
const flagsBeforeSep = ddIdx === -1 ? rawArgv : rawArgv.slice(0, ddIdx);
|
|
475
|
+
if (flagsBeforeSep.includes("--help") || flagsBeforeSep.includes("-h")) {
|
|
476
|
+
process.stdout.write(`${command.helpText}
|
|
477
|
+
`);
|
|
478
|
+
process.exit(0);
|
|
479
|
+
}
|
|
480
|
+
if (command.name === "update") {
|
|
481
|
+
process.exit(await handleUpdate(rawArgv.slice(1)));
|
|
482
|
+
}
|
|
483
|
+
const argv = command.name === "actions" && rawArgv.includes("--raw") ? translateActionsRaw(rawArgv) : rawArgv;
|
|
484
|
+
process.exit(await delegate(argv));
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
const HIDDEN = ["completion-candidates"];
|
|
488
|
+
for (const name of HIDDEN) {
|
|
489
|
+
program.command(name, { hidden: true }).allowUnknownOption().helpOption(false).argument("[args...]").action(async () => {
|
|
490
|
+
process.exit(await delegate(rawArgv));
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
program.command("completions").description("Install/print bundled shell tab-completion (zsh + bash)").allowUnknownOption().helpOption(false).argument("[args...]").action(() => {
|
|
494
|
+
const script = path.join(packageRoot, "scripts", "install-completions.sh");
|
|
495
|
+
const result = spawnSync("bash", [script, ...rawArgv.slice(1)], { stdio: "inherit" });
|
|
496
|
+
process.exit(result.status ?? 1);
|
|
497
|
+
});
|
|
498
|
+
for (const retired of RETIRED) {
|
|
499
|
+
program.command(retired.name).description(`${retired.name} is retired (see teaching error)`).allowUnknownOption().helpOption(false).argument("[args...]").action(async () => {
|
|
500
|
+
if (retired.name === "live") {
|
|
501
|
+
const adapterIdx = rawArgv.indexOf("--adapter");
|
|
502
|
+
const aliasIdx = rawArgv.indexOf("-a");
|
|
503
|
+
const adapterVal = adapterIdx !== -1 ? rawArgv[adapterIdx + 1] : aliasIdx !== -1 ? rawArgv[aliasIdx + 1] : void 0;
|
|
504
|
+
if (adapterVal === "mobile" || adapterVal === "android") {
|
|
505
|
+
process.exit(await delegate(rawArgv));
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
console.error(retired.message);
|
|
510
|
+
process.exit(2);
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
const NUDGE_SKIP = ["update", "completions", "completion-candidates"];
|
|
514
|
+
if (rawArgv.length > 0 && !NUDGE_SKIP.includes(rawArgv[0])) {
|
|
515
|
+
setImmediate(() => void maybeNudge());
|
|
516
|
+
}
|
|
517
|
+
if (rawArgv.length === 0) {
|
|
518
|
+
process.stdout.write(groupedHelp());
|
|
519
|
+
process.exit(0);
|
|
520
|
+
}
|
|
521
|
+
await program.parseAsync(process.argv);
|