@deeeed/metamask-harness 0.14.0 → 0.14.2

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 (40) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/adapters/extension/inject.mjs +1 -0
  3. package/adapters/extension/launch-browser.cjs +15 -3
  4. package/adapters/extension/lib/extension-id.cjs +36 -0
  5. package/adapters/extension/live.sh +5 -3
  6. package/adapters/extension/readiness.mjs +48 -0
  7. package/adapters/extension/reattach.sh +163 -55
  8. package/adapters/extension/sidepanel-toggle.sh +96 -24
  9. package/adapters/extension/verify.sh +11 -0
  10. package/adapters/extension/wallet-fixture-state.cjs +6 -11
  11. package/adapters/mobile/open-device.sh +32 -5
  12. package/adapters/shared/resolve-slot-ports-core.mjs +14 -4
  13. package/dist/adapters/core/surface.js +1 -0
  14. package/dist/adapters/extension/runtime.js +14 -5
  15. package/dist/adapters/extension/surface.js +1 -0
  16. package/dist/adapters/mobile/provision.js +49 -4
  17. package/dist/adapters/mobile/surface.js +1 -0
  18. package/dist/adapters/slot-ports.js +11 -4
  19. package/dist/cli.js +4 -0
  20. package/dist/commands/call.js +19 -2
  21. package/dist/commands/check.js +326 -0
  22. package/dist/commands/core-readiness.js +75 -0
  23. package/dist/commands/device-target.js +113 -10
  24. package/dist/commands/doctor.js +30 -18
  25. package/dist/commands/launch/extension.js +105 -9
  26. package/dist/commands/launch/index.js +116 -15
  27. package/dist/commands/mobile-device-view.js +140 -0
  28. package/dist/commands/parse-args.js +4 -1
  29. package/dist/commands/recipe-quality.js +6 -2
  30. package/dist/commands/run-engine.js +28 -2
  31. package/dist/commands/run-report.js +115 -0
  32. package/dist/commands/run.js +113 -8
  33. package/dist/commands/shared.js +2 -1
  34. package/dist/commands/status-probe.js +9 -3
  35. package/dist/commands/status.js +40 -60
  36. package/dist/live-adapter-contract.js +5 -1
  37. package/dist/mm-harness-cli.js +61 -22
  38. package/docs/CLI-SPEC.md +25 -0
  39. package/library/actions/extension/platform/cdp.mjs +7 -4
  40. package/package.json +4 -4
@@ -6,6 +6,8 @@ import { Command } from "commander";
6
6
  import { color } from "./cli-color.js";
7
7
  import { handleUpdate, maybeNudge } from "./commands/update.js";
8
8
  import { handleCallHelp } from "./commands/call.js";
9
+ import { getAdapterSurface } from "./adapters/surface.js";
10
+ import { detectAdapter } from "./harness.js";
9
11
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
10
12
  globalThis.__MM_HARNESS_WRAPPER__ = true;
11
13
  const { main: recipeMain } = await import("./cli.js");
@@ -24,9 +26,13 @@ const REAL = [
24
26
  --target <path> Checkout path (default: cwd)
25
27
  --json Machine-readable envelope { adapter, target, devices[], next }
26
28
  --fast Skip all live-state probes (instant output, safe for scripts)
29
+ --all-devices Mobile only: show every connected device instead of the slot-scoped target
27
30
 
28
31
  Human output: static device list prints immediately; live-state lines append
29
32
  after the ~2s probe window (screen, wallet, account, fixture per device).
33
+ By default mobile status shows the configured target only; --device <id>
34
+ switches the active target for that command. Other devices that answer on the
35
+ same Metro are reported as additional reachable targets.
30
36
 
31
37
  The devices[] section carries per device:
32
38
  { platform, id, name, state, selected } always present
@@ -168,6 +174,7 @@ Example:
168
174
  --expect-live Exit 0 iff the runtime is live (extension: watcher+CDP; mobile: Metro+bridge; core: deps), non-zero + teaching escape otherwise
169
175
  --cdp-port <port> Extension CDP port for the liveness probe (env: CDP_PORT / RECIPE_CDP_PORT)
170
176
  --device <udid|serial|name> Mobile only: target this device (env: IOS_SIMULATOR / ADB_SERIAL). doctor reports the connected devices; it never gates on ambiguity.
177
+ --all-devices Mobile only: show every connected device instead of the slot-scoped target
171
178
  --adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
172
179
  --target <path> Checkout path (default: cwd)
173
180
  --runtime-dir <dir> Runtime dir containing agentic-runtime.json (relative to target)
@@ -179,6 +186,27 @@ Example:
179
186
  mm-harness doctor --adapter extension --target /path/to/checkout --cdp-port 6662 --expect-live
180
187
  mm-harness doctor --adapter mobile --target /path/to/checkout`
181
188
  },
189
+ {
190
+ name: "check",
191
+ summary: "Run bounded repo-local checks for an active git diff and write validation artifacts.",
192
+ example: "mm-harness check diff --profile fast",
193
+ helpText: `mm-harness check diff [flags]
194
+
195
+ Run bounded repo-local checks for the active git diff. Fast profile runs changed-file
196
+ ESLint, Prettier, and changed test files. Full profile also runs repo typecheck
197
+ when available. Does not launch an app or run a recipe.
198
+
199
+ --target <path> Checkout path (default: cwd)
200
+ --adapter <mobile|extension|core> Adapter label (auto-detected when possible)
201
+ --base <ref> Diff base (default: PR base, then remote HEAD, then repo fallback)
202
+ --profile <fast|full> Validation depth (default: fast)
203
+ --artifacts-dir <dir> Write validation-summary.json/.md and per-check logs
204
+ --json Machine-readable envelope
205
+
206
+ Example:
207
+ mm-harness check diff --profile fast --artifacts-dir artifacts/validation
208
+ mm-harness check diff --profile full --json`
209
+ },
182
210
  {
183
211
  name: "recipe-quality",
184
212
  summary: "Build the recipe-quality artifact from a compact verdict JSON (validates before writing).",
@@ -188,7 +216,12 @@ Example:
188
216
  Build artifacts/recipe-quality.json from the compact fields a recipe-quality pass
189
217
  produces (verdict + reasons + optional guidance/dimensions/findings/delta/training).
190
218
  The artifact is validated against the RecipeQualityArtifact schema before it is
191
- written \u2014 an invalid input never reaches disk.
219
+ written \u2014 an invalid input never reaches disk. Shorthand without "build" is
220
+ also accepted when --input/--output are present.
221
+
222
+ This CLI does not run the recipe critique itself; use the fs-recipe-quality
223
+ skill to judge recipe/evidence quality, then use this command to build the
224
+ canonical artifact from that compact verdict.
192
225
 
193
226
  --input <compact.json> Compact verdict JSON: { "verdict": "pass|warn|fail", "reasons": [..],
194
227
  "betterVersionGuidance"?: [..], "dimensions"?: {..}, "trainingFields"?: {..}, \u2026 }
@@ -199,6 +232,7 @@ Example:
199
232
 
200
233
  Example:
201
234
  mm-harness recipe-quality build --input compact.json --output artifacts/recipe-quality.json
235
+ mm-harness recipe-quality --input compact.json --output artifacts/recipe-quality.json
202
236
  mm-harness recipe-quality build --input compact.json --output artifacts/recipe-quality.json --json`
203
237
  },
204
238
  {
@@ -297,7 +331,7 @@ Example:
297
331
  },
298
332
  {
299
333
  name: "launch",
300
- summary: "Launch the app (Metro/build + boot), auto-ensuring the runtime overlay first. Mobile: ios|android required.",
334
+ summary: "Launch the app and adapter dev server, auto-ensuring the runtime overlay first. Mobile: ios|android required.",
301
335
  example: "mm-harness launch ios",
302
336
  helpText: `mm-harness launch [ios|android] [flags]
303
337
 
@@ -312,7 +346,7 @@ Example:
312
346
  --runway Post-launch runway check (mobile only; teaching error elsewhere)
313
347
  --watch Persistent webpack watcher then relaunch (extension only)
314
348
  --sidepanel | --fullscreen Extension display mode (default --fullscreen)
315
- --url <dapp-url> Open a dapp in the main tab beside the sidepanel (extension only)
349
+ --url <dapp-url> Open a dapp beside the sidepanel (default: MetaMask test dapp)
316
350
  --heal <off|infra-only|auto> Healing policy (default auto); bounds always enforced
317
351
  --device <udid|name> Target simulator/device (env: IOS_SIMULATOR / ADB_SERIAL)
318
352
  --cdp-port <port> Extension CDP port (env: CDP_PORT / RECIPE_CDP_PORT)
@@ -424,14 +458,6 @@ Example:
424
458
  mm-harness fixtures finalize --fixture wallet-fixture.json --state fixture-state.json --cdp-port 6661 --extension-dir dist/chrome`
425
459
  }
426
460
  ];
427
- const RETIRED_INTERNAL = [
428
- "runtime-health",
429
- "runtime-decision",
430
- "runtime-launch",
431
- "resolve-extension",
432
- "ensure-ready",
433
- "self-test"
434
- ];
435
461
  const RETIRED = [
436
462
  {
437
463
  name: "live",
@@ -445,14 +471,7 @@ Replacement: mm-harness launch --verify (install overlay \u2192 launch \u2192 CD
445
471
 
446
472
  Replacement: mm-harness actions --raw (works now \u2014 dumps the raw action registry JSON,
447
473
  identical to the old \`manifest --json\`). Manifest validation moved into doctor / run --plan.`
448
- },
449
- ...RETIRED_INTERNAL.map((name) => ({
450
- name,
451
- message: `mm-harness ${name} is retired (exit 2).
452
-
453
- It is internal now \u2014 its logic lives inside doctor / launch / verify self-healing.
454
- Use: mm-harness doctor`
455
- }))
474
+ }
456
475
  ];
457
476
  const HELP_GROUPS = [
458
477
  {
@@ -468,7 +487,7 @@ const HELP_GROUPS = [
468
487
  {
469
488
  title: "PROVE",
470
489
  blurb: "run recipes and inspect readiness",
471
- commands: ["run", "doctor", "recipe-quality"]
490
+ commands: ["run", "doctor", "check", "recipe-quality"]
472
491
  },
473
492
  {
474
493
  title: "RUNTIME OVERLAY",
@@ -486,6 +505,14 @@ function commandMeta(name) {
486
505
  if (real) return { summary: real.summary, example: real.example, planned: false };
487
506
  return { summary: "", example: "", planned: false };
488
507
  }
508
+ function adapterFromRuntimeContext(ctx) {
509
+ const raw = typeof ctx.platform === "string" ? ctx.platform : void 0;
510
+ if (!raw) return void 0;
511
+ if (raw === "mobile" || raw === "ios" || raw === "android") return "mobile";
512
+ if (raw === "extension" || raw === "chrome-extension") return "extension";
513
+ if (raw === "core") return "core";
514
+ return void 0;
515
+ }
489
516
  function detectedSlotLine(out) {
490
517
  const ctxPath = path.join(
491
518
  process.cwd(),
@@ -494,10 +521,14 @@ function detectedSlotLine(out) {
494
521
  );
495
522
  try {
496
523
  const ctx = JSON.parse(fs.readFileSync(ctxPath, "utf8"));
524
+ const adapter = adapterFromRuntimeContext(ctx) ?? detectAdapter(process.cwd());
497
525
  const parts = [];
498
526
  if (ctx.slotId) parts.push(`slot ${out("ok", String(ctx.slotId))}`);
499
527
  if (ctx.simulator) parts.push(`device ${out("ok", String(ctx.simulator))}`);
500
- if (ctx.metroPort) parts.push(`metro :${out("ok", String(ctx.metroPort))}`);
528
+ const devServerPort = ctx.watcherPort ?? ctx.devServerPort ?? ctx.metroPort;
529
+ if (adapter && adapter !== "core" && devServerPort) {
530
+ parts.push(`${getAdapterSurface(adapter).devServer.label} :${out("ok", String(devServerPort))}`);
531
+ }
501
532
  if (ctx.gitBranch) parts.push(`branch ${out("info", String(ctx.gitBranch))}`);
502
533
  if (parts.length === 0) return null;
503
534
  return `${out("label", "SLOT")} \u2014 this checkout is a prepared slot: ${parts.join(" \xB7 ")}`;
@@ -582,7 +613,15 @@ for (const command of REAL) {
582
613
  process.exit(await delegate(argv));
583
614
  });
584
615
  }
585
- const HIDDEN = ["completion-candidates"];
616
+ const HIDDEN = [
617
+ "completion-candidates",
618
+ "runtime-health",
619
+ "runtime-decision",
620
+ "runtime-launch",
621
+ "resolve-extension",
622
+ "ensure-ready",
623
+ "self-test"
624
+ ];
586
625
  for (const name of HIDDEN) {
587
626
  program.command(name, { hidden: true }).allowUnknownOption().helpOption(false).argument("[args...]").action(async () => {
588
627
  process.exit(await delegate(rawArgv));
package/docs/CLI-SPEC.md CHANGED
@@ -54,6 +54,7 @@ Name `metamask-recipe` → **REMOVE** (becomes `mm-harness`; no alias). Capabili
54
54
  | `resolve-extension` | Resolve the extension id | extension | advanced | **KEEP-INTERNAL** — internal plumbing; called inside `launch`/`launch --verify` |
55
55
  | `ensure-ready` | Ensure extension ready (close home tabs) | extension | advanced | **KEEP-INTERNAL** — becomes internal function inside `launch`/`verify` self-healing path |
56
56
  | `run` | Run a recipe, write evidence | all | PROVE | ROUTES-NOW |
57
+ | `check diff` | Run bounded repo-local validation for the active git diff | all | PROVE | ROUTES-NOW |
57
58
  | `self-test` | Package-wiring smoke (no device) | mobile+extension | advanced | **KEEP-INTERNAL** — CI smoke test; callable via `package.json` scripts; hidden from `--help` |
58
59
  | `flows` | List/promote library flows | all | DISCOVER | ROUTES-NOW |
59
60
  | `harness install\|verify\|cleanup\|live` | Runtime overlay lifecycle | all (core: no live) | OVERLAY | ROUTES-NOW (`harness` prefix → top-level); `live` **REMOVE** — dissolved into `launch --verify` |
@@ -437,6 +438,30 @@ Readiness check for a checkout without launching the app. Doctor is the single p
437
438
 
438
439
  ---
439
440
 
441
+ ## `check diff` (REAL → bounded local validation)
442
+
443
+ **Synopsis:** `mm-harness check diff [flags]`
444
+
445
+ Runs bounded validation against the target checkout's active git diff. This is
446
+ for local PR readiness loops and agent templates; it does not launch an app or
447
+ run a recipe.
448
+
449
+ | Flag | Values | Default | Purpose |
450
+ | --- | --- | --- | --- |
451
+ | `--target <repo>` | path | cwd | MetaMask checkout to inspect |
452
+ | `--adapter <adapter>` / `--platform <adapter>` | mobile\|extension\|core | auto-detect | Adapter label for the report |
453
+ | `--base <ref>` | git ref | GitHub PR base, then remote HEAD, then `origin/develop`/`origin/main`/local fallback | Diff base |
454
+ | `--profile <profile>` | `fast`\|`full` | `fast` | `fast` runs changed-file ESLint/Prettier/Jest; `full` also runs repo typecheck when available |
455
+ | `--artifacts-dir <dir>` | path | `temp/recipe/check-diff/<timestamp>` | Writes summary and per-check logs |
456
+ | `--json` | bool | false | Print the summary envelope to stdout |
457
+
458
+ **Artifacts:** `validation-summary.json`, `validation-summary.md`, and one log
459
+ per executed check.
460
+
461
+ **Exit:** 0 pass · 2 bad args / not a git checkout · 5 validation failure.
462
+
463
+ ---
464
+
440
465
  ## `--device <id>` — first-class mobile device targeting (REAL)
441
466
 
442
467
  `--device` is the uniform mobile device selector on `run`, `call`, `doctor`, and `fixtures`. Pass the **adb serial** for Android (from `adb devices`) or the **UDID / simulator name** for iOS. The harness resolves the adb serial to the Metro CDP target identity internally — users never need to know or set `ANDROID_DEVICE='Pixel 6 - 16 - API 36'`.
@@ -46,14 +46,17 @@ function cdpPort(input) {
46
46
  return port;
47
47
  }
48
48
 
49
- function extensionTarget(targets, extensionId = null) {
50
- return (Array.isArray(targets) ? targets : []).find((target) => {
49
+ export function extensionTarget(targets, extensionId = null) {
50
+ const candidates = (Array.isArray(targets) ? targets : []).filter((target) => {
51
51
  if (target?.type !== 'page') return false;
52
52
  if (!String(target?.url ?? '').startsWith('chrome-extension://')) return false;
53
- if (!String(target.url).includes('/home.html')) return false;
53
+ if (!String(target.url).includes('/home.html') && !String(target.url).includes('/sidepanel.html')) return false;
54
54
  if (extensionId && extensionIdFromTarget(target) !== extensionId) return false;
55
55
  return Boolean(target.webSocketDebuggerUrl);
56
- }) ?? null;
56
+ });
57
+ return candidates.find((target) => String(target.url).includes('/home.html')) ??
58
+ candidates.find((target) => String(target.url).includes('/sidepanel.html')) ??
59
+ null;
57
60
  }
58
61
 
59
62
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"
@@ -16,9 +16,9 @@
16
16
  "check:syntax": "find . -name '*.mjs' -print0 | xargs -0 -n1 node --check"
17
17
  },
18
18
  "dependencies": {
19
- "@farmslot/agent-runtime": "^0.1.0",
20
- "@farmslot/protocol": "^0.7.3",
21
- "@farmslot/recipe-harness": "^0.4.0",
19
+ "@farmslot/agent-runtime": "^0.1.1",
20
+ "@farmslot/protocol": "^0.7.6",
21
+ "@farmslot/recipe-harness": "^0.4.3",
22
22
  "commander": "^12.0.0",
23
23
  "viem": "^2.54.3"
24
24
  },