@deeeed/metamask-harness 0.17.5 → 0.18.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 (132) hide show
  1. package/CHANGELOG.md +872 -0
  2. package/README.md +94 -140
  3. package/adapters/extension/console-tail.mjs +55 -24
  4. package/adapters/extension/ensure-browser.sh +6 -1
  5. package/adapters/extension/inject.mjs +6 -9
  6. package/adapters/extension/launch-browser.cjs +7 -1
  7. package/adapters/extension/launch-webpack.cjs +83 -0
  8. package/adapters/extension/launch.sh +8 -11
  9. package/adapters/extension/lib/chrome-args.cjs +8 -1
  10. package/adapters/extension/lib/macos-focus.cjs +32 -0
  11. package/adapters/extension/live.sh +12 -21
  12. package/adapters/extension/reattach.sh +1 -0
  13. package/adapters/extension/seed-fixture.sh +4 -12
  14. package/adapters/extension/sidepanel-toggle.sh +4 -1
  15. package/adapters/extension/stamp-runtime-title.cjs +58 -0
  16. package/adapters/extension/start-watch.sh +17 -4
  17. package/adapters/extension/stop-viewers.sh +1 -1
  18. package/adapters/extension/sync-webpack-dist.cjs +107 -0
  19. package/adapters/extension/verify.sh +1 -1
  20. package/adapters/manifest.json +53 -37
  21. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +16 -0
  22. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
  23. package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +3 -1
  24. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
  25. package/adapters/mobile/cleanup.sh +16 -176
  26. package/adapters/mobile/inject.sh +90 -671
  27. package/adapters/mobile/launch-console-forwarder.cjs +39 -0
  28. package/adapters/mobile/open-device.sh +45 -7
  29. package/adapters/mobile/start-console-forwarder.sh +70 -0
  30. package/adapters/mobile/start-metro.sh +0 -41
  31. package/adapters/mobile/verify.sh +19 -64
  32. package/adapters/mobile/wait-for-bridge.sh +22 -8
  33. package/adapters/shared/resolve-slot-ports-core.mjs +17 -6
  34. package/adapters/shared/resolve-slot-ports.sh +17 -5
  35. package/adapters/shared/update-check-worker.mjs +43 -0
  36. package/bin/mm-harness +21 -2
  37. package/dist/adapters/core/surface.js +4 -1
  38. package/dist/adapters/extension/console-capture.js +95 -0
  39. package/dist/adapters/extension/product-config.js +110 -0
  40. package/dist/adapters/extension/runtime-decision.js +20 -2
  41. package/dist/adapters/extension/surface.js +22 -3
  42. package/dist/adapters/mobile/perps-env.js +43 -3
  43. package/dist/adapters/mobile/prepare.js +39 -7
  44. package/dist/adapters/mobile/surface.js +5 -2
  45. package/dist/adapters/resolve-slot-ports.js +2 -2
  46. package/dist/adapters/slot-ports.js +13 -16
  47. package/dist/adapters.js +56 -16
  48. package/dist/checkout-lock.js +27 -2
  49. package/dist/cli-color.js +19 -0
  50. package/dist/cli-commands.js +1 -1
  51. package/dist/cli.js +7 -14
  52. package/dist/command-contract.js +451 -0
  53. package/dist/command-journal.js +225 -0
  54. package/dist/commands/call.js +170 -50
  55. package/dist/commands/check.js +9 -3
  56. package/dist/commands/completion-candidates.js +20 -13
  57. package/dist/commands/device-target.js +27 -12
  58. package/dist/commands/doctor.js +106 -25
  59. package/dist/commands/fixtures.js +92 -34
  60. package/dist/commands/flows.js +39 -10
  61. package/dist/commands/last.js +52 -0
  62. package/dist/commands/launch/extension.js +38 -15
  63. package/dist/commands/launch/index.js +207 -63
  64. package/dist/commands/list-executables.js +151 -29
  65. package/dist/commands/logs.js +8 -6
  66. package/dist/commands/manifest.js +270 -35
  67. package/dist/commands/parse-args.js +13 -1
  68. package/dist/commands/provision.js +10 -3
  69. package/dist/commands/run-engine.js +435 -105
  70. package/dist/commands/run-report.js +12 -3
  71. package/dist/commands/run.js +355 -50
  72. package/dist/commands/shared.js +75 -3
  73. package/dist/commands/status-probe.js +4 -1
  74. package/dist/commands/status.js +2 -1
  75. package/dist/commands/stop.js +7 -2
  76. package/dist/commands/update.js +16 -28
  77. package/dist/completions-cache.js +1 -1
  78. package/dist/doctor.js +57 -10
  79. package/dist/harness.js +47 -137
  80. package/dist/heal-bounds.js +2 -2
  81. package/dist/json-stream.js +57 -0
  82. package/dist/live-adapter-contract.js +138 -32
  83. package/dist/manifest.js +161 -1
  84. package/dist/mm-harness-cli.js +135 -54
  85. package/dist/paths.js +2 -5
  86. package/dist/recipe-security.js +178 -0
  87. package/dist/run-diagnostics.js +261 -0
  88. package/dist/runner.js +117 -8
  89. package/docs/CONTRIBUTING.md +137 -0
  90. package/docs/QA.md +185 -0
  91. package/docs/RECIPES.md +161 -0
  92. package/docs/SECURITY.md +88 -0
  93. package/library/README.md +4 -0
  94. package/library/actions/core/perps/_controller.mjs +10 -55
  95. package/library/actions/core/perps/read_account.mjs +2 -2
  96. package/library/actions/core/perps/read_orders.mjs +2 -1
  97. package/library/actions/core/perps/read_positions.mjs +2 -1
  98. package/library/actions/core/wallet/list_accounts.mjs +95 -0
  99. package/library/actions/extension/platform/cdp.mjs +1 -0
  100. package/library/actions/extension/wallet/list_accounts.mjs +41 -0
  101. package/library/actions/mobile/wallet/list_accounts.mjs +37 -0
  102. package/library/manifests/core.action-manifest.json +68 -7
  103. package/library/manifests/extension.action-manifest.json +53 -0
  104. package/library/manifests/mobile.action-manifest.json +81 -3
  105. package/library/recipes/runner/action-validation.extension.recipe.json +8 -1
  106. package/library/recipes/runner/action-validation.mobile.recipe.json +8 -1
  107. package/library/recipes/runner/smoke.core.recipe.json +27 -0
  108. package/library/recipes/wallet/smoke.extension.recipe.json +42 -0
  109. package/library/recipes/wallet/smoke.mobile.recipe.json +42 -0
  110. package/package.json +12 -5
  111. package/scripts/completions.sh +7 -7
  112. package/scripts/validate-human-outcomes.mjs +169 -0
  113. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -185
  114. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -1662
  115. package/adapters/mobile/overlay/compat/README.md +0 -28
  116. package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +0 -42
  117. package/adapters/shared/resolve-farmslot-ports-core.mjs +0 -3
  118. package/adapters/shared/resolve-farmslot-ports.mjs +0 -5
  119. package/adapters/shared/resolve-farmslot-ports.sh +0 -7
  120. package/docs/ADAPTER-SURFACE.md +0 -119
  121. package/docs/CHEATSHEET.md +0 -61
  122. package/docs/CLI-SPEC.md +0 -1098
  123. package/docs/CODE-MAP.md +0 -62
  124. package/docs/DEBUG-HANDOVER.md +0 -36
  125. package/docs/MENTAL-MODEL.md +0 -295
  126. package/docs/UX-PRINCIPLES.md +0 -64
  127. package/docs/architecture.md +0 -398
  128. package/docs/live-adapter-contract.md +0 -188
  129. package/docs/package-boundaries.md +0 -47
  130. package/docs/perps-flow-catalog.md +0 -235
  131. package/docs/recipe-libraries.md +0 -301
  132. package/docs/runtime-file-conventions.md +0 -36
package/dist/adapters.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import http from "node:http";
2
2
  import { compatibilityMode, fixtureSummary, repoShape } from "./doctor.js";
3
- import { runLiveAdapterScript } from "./live-adapter-contract.js";
3
+ import {
4
+ runLiveAdapterScript
5
+ } from "./live-adapter-contract.js";
4
6
  import { withExtensionPage } from "../library/actions/extension/platform/cdp.mjs";
5
7
  import { bridgeCommand, evalSync, MOBILE_BRIDGE_ERROR_CODES, selectBridgeStatusEntry, simulatorScreenshot } from "../library/actions/mobile/platform/bridge.mjs";
6
8
  function sleep(ms) {
@@ -32,6 +34,7 @@ const LIVE_ONLY_WALLET_ACTIONS = /* @__PURE__ */ new Set([
32
34
  "metamask.wallet.setup",
33
35
  "metamask.wallet.ensure_unlocked",
34
36
  "metamask.wallet.select_account",
37
+ "metamask.wallet.list_accounts",
35
38
  "metamask.wallet.read_state"
36
39
  ]);
37
40
  const LIVE_ONLY_APP_ACTIONS = /* @__PURE__ */ new Set(["ui.navigate"]);
@@ -42,14 +45,20 @@ function liveRuntimeConfigured(platform, node) {
42
45
  if (platform === "mobile" || platform === "core") return true;
43
46
  return Boolean(node.cdp_port ?? process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT);
44
47
  }
45
- async function runLiveFirst(platform, action, node, context) {
48
+ async function runLiveFirst(platform, action, node, context, forceLive = false, preparedLiveAdapters) {
46
49
  if (node.allow_static_placeholder === true) {
47
50
  throw new Error(
48
51
  `${action} refused allow_static_placeholder. MetaMask runner proof runs must use live adapters and real artifacts.`
49
52
  );
50
53
  }
51
- if (!requiresLiveAdapter(platform, action) && !liveRuntimeConfigured(platform, node)) return null;
52
- const live = await runLiveAdapterScript({ platform, action, node, context });
54
+ if (!forceLive && !requiresLiveAdapter(platform, action) && !liveRuntimeConfigured(platform, node)) return null;
55
+ const live = await runLiveAdapterScript({
56
+ platform,
57
+ action,
58
+ node,
59
+ context,
60
+ prepared: preparedLiveAdapters?.get(action)
61
+ });
53
62
  if (!live) return null;
54
63
  const liveResult = isRecord(live.result) ? live.result : { result: live.result };
55
64
  const output = { ...liveResult, liveAdapter: live.script };
@@ -64,10 +73,17 @@ function liveAdapterPathHint(platform, action) {
64
73
  }
65
74
  return `library/actions/${platform}/${action.replaceAll(".", "/")}.mjs`;
66
75
  }
67
- async function semanticResult(platform, action, node, context) {
68
- const live = await runLiveFirst(platform, action, node, context);
76
+ async function semanticResult(platform, action, node, context, forceLive = false, preparedLiveAdapters) {
77
+ const live = await runLiveFirst(
78
+ platform,
79
+ action,
80
+ node,
81
+ context,
82
+ forceLive,
83
+ preparedLiveAdapters
84
+ );
69
85
  if (live) return live;
70
- if (requiresLiveAdapter(platform, action)) {
86
+ if (forceLive || requiresLiveAdapter(platform, action)) {
71
87
  const expected = liveAdapterPathHint(platform, action);
72
88
  throw new Error(
73
89
  `${action} requires a live ${platform} adapter that drives a real supported app/API path; no adapter script was found or no live runtime is configured (for example ${expected}). Static placeholders are refused to avoid fabricated proof. Set METAMASK_RECIPE_LIVE_ADAPTER_DIR or add a runner library/actions script.`
@@ -145,15 +161,16 @@ function probeHttpJson(url, timeoutMs = 1e3) {
145
161
  });
146
162
  });
147
163
  }
148
- function createMetaMaskSemanticAdapters(platform) {
164
+ function createMetaMaskSemanticAdapters(platform, declaredCustomActions = [], preparedLiveAdapters) {
149
165
  const walletActions = [
150
166
  "metamask.wallet.fixture_status",
151
167
  "metamask.wallet.setup",
152
168
  "metamask.wallet.ensure_unlocked",
153
169
  "metamask.wallet.select_account",
170
+ "metamask.wallet.list_accounts",
154
171
  "metamask.wallet.read_state"
155
172
  ];
156
- const actions = [
173
+ const bundledActions = [
157
174
  ...walletActions,
158
175
  "metamask.perps.read_positions",
159
176
  "metamask.perps.ensure_positions",
@@ -169,10 +186,19 @@ function createMetaMaskSemanticAdapters(platform) {
169
186
  // read_account is core-only: only the headless core adapter implements it.
170
187
  ...platform === "core" ? ["metamask.perps.read_account"] : []
171
188
  ];
189
+ const bundled = new Set(bundledActions);
190
+ const actions = [.../* @__PURE__ */ new Set([...bundledActions, ...declaredCustomActions])];
172
191
  return actions.map(
173
192
  (action) => simpleAdapter(
174
193
  action,
175
- async (node, context) => semanticResult(platform, action, node, context)
194
+ async (node, context) => semanticResult(
195
+ platform,
196
+ action,
197
+ node,
198
+ context,
199
+ !bundled.has(action),
200
+ preparedLiveAdapters
201
+ )
176
202
  )
177
203
  );
178
204
  }
@@ -287,8 +313,15 @@ async function handleMobileNavigate(payload, context) {
287
313
  return isRecord(live.result) ? { ...live.result, liveAdapter: live.script } : live.result;
288
314
  }
289
315
  async function handleMobilePress(payload, context) {
290
- const target = firstScalarText(payload, ["test_id", "testID", "selector", "text"], "ui.press");
291
- return bridgeCommand(mobileUiInput(context, "press", payload), ["press-test-id", target]);
316
+ const identifiers = ["test_id", "testID", "selector"].map((field) => optionalScalarText(payload[field], `ui.press.${field}`)).filter((value) => value !== void 0);
317
+ const text = optionalScalarText(payload.text, "ui.press.text");
318
+ if (identifiers.length + Number(text !== void 0) !== 1) {
319
+ throw new Error("ui.press requires exactly one of: test_id, testID, selector, text.");
320
+ }
321
+ const target = text ?? identifiers[0];
322
+ if (target.trim() === "") throw new Error("ui.press target must be non-empty.");
323
+ const input = mobileUiInput(context, "press", payload);
324
+ return text !== void 0 ? bridgeCommand(input, ["press-text", target]) : bridgeCommand(input, ["press-test-id", target]);
292
325
  }
293
326
  async function handleMobileSetInput(payload, context) {
294
327
  const input = mobileUiInput(context, "set_input", payload);
@@ -432,7 +465,7 @@ function mobileHudStatusLabel(status) {
432
465
  if (status === "pass") return "pass";
433
466
  return "run";
434
467
  }
435
- function createMetaMaskUiTransport(platform, harness) {
468
+ function createMetaMaskUiTransport(platform, harness, preparedLiveAdapters) {
436
469
  if (platform === "core") {
437
470
  return {
438
471
  async execute(action) {
@@ -461,7 +494,14 @@ function createMetaMaskUiTransport(platform, harness) {
461
494
  return {
462
495
  async execute(action, node, context) {
463
496
  if (action === "ui.navigate") {
464
- const live = await runLiveFirst(platform, action, node, context);
497
+ const live = await runLiveFirst(
498
+ platform,
499
+ action,
500
+ node,
501
+ context,
502
+ false,
503
+ preparedLiveAdapters
504
+ );
465
505
  if (live) return live.output;
466
506
  throw new Error(`ui.navigate requires library/actions/${platform}/ui/navigate.mjs.`);
467
507
  }
@@ -473,7 +513,7 @@ function normalizeUiWaitNode(node) {
473
513
  if (node.expected !== void 0 || typeof node.visible !== "boolean") return node;
474
514
  return { ...node, expected: node.visible ? "visible" : "absent" };
475
515
  }
476
- function createMetaMaskAdapters(adapter) {
516
+ function createMetaMaskAdapters(adapter, declaredCustomActions = [], preparedLiveAdapters) {
477
517
  const platform = adapter;
478
518
  const adapters = [
479
519
  simpleAdapter("app.status", async (_node, context) => ({
@@ -525,7 +565,7 @@ function createMetaMaskAdapters(adapter) {
525
565
  }
526
566
  };
527
567
  }),
528
- ...createMetaMaskSemanticAdapters(platform)
568
+ ...createMetaMaskSemanticAdapters(platform, declaredCustomActions, preparedLiveAdapters)
529
569
  ];
530
570
  return adapters;
531
571
  }
@@ -7,7 +7,7 @@ function acquireCheckoutLock(target, operation) {
7
7
  const lockPath = path.join(repoRoot, recipeRuntimeDir(), "sandbox.lock");
8
8
  fs.mkdirSync(path.dirname(lockPath), { recursive: true });
9
9
  const inheritedToken = process.env.MM_HARNESS_CHECKOUT_LOCK_TOKEN;
10
- for (let attempt = 0; attempt < 2; attempt += 1) {
10
+ for (let attempt = 0; attempt < 4; attempt += 1) {
11
11
  try {
12
12
  const fd = fs.openSync(lockPath, "wx", 384);
13
13
  const token = inheritedToken || randomUUID();
@@ -46,11 +46,36 @@ function acquireCheckoutLock(target, operation) {
46
46
  message: `checkout sandbox is busy with ${String(owner.operation ?? "another operation")} (pid ${owner.pid}).`
47
47
  };
48
48
  }
49
- fs.rmSync(lockPath, { force: true });
49
+ claimStaleLock(lockPath, owner);
50
50
  }
51
51
  }
52
52
  return { path: lockPath, owner: readOwner(lockPath), message: "checkout sandbox lock could not be acquired." };
53
53
  }
54
+ function claimStaleLock(lockPath, expectedOwner) {
55
+ const claimedPath = `${lockPath}.stale-${process.pid}-${randomUUID()}`;
56
+ try {
57
+ fs.renameSync(lockPath, claimedPath);
58
+ } catch (error) {
59
+ if (error.code === "ENOENT") return;
60
+ throw error;
61
+ }
62
+ try {
63
+ const claimedOwner = readOwner(claimedPath);
64
+ if (!sameOwner(claimedOwner, expectedOwner) || claimedOwner && typeof claimedOwner.pid === "number" && processAlive(claimedOwner.pid)) {
65
+ try {
66
+ fs.linkSync(claimedPath, lockPath);
67
+ } catch (error) {
68
+ if (error.code !== "EEXIST") throw error;
69
+ }
70
+ }
71
+ } finally {
72
+ fs.rmSync(claimedPath, { force: true });
73
+ }
74
+ }
75
+ function sameOwner(left, right) {
76
+ if (!left || !right) return left === right;
77
+ return left.pid === right.pid && left.token === right.token && left.startedAt === right.startedAt && left.repoRoot === right.repoRoot;
78
+ }
54
79
  function readOwner(lockPath) {
55
80
  try {
56
81
  const value = JSON.parse(fs.readFileSync(lockPath, "utf8"));
package/dist/cli-color.js CHANGED
@@ -47,6 +47,24 @@ function colorStatusWord(word, { stream = process.stderr } = {}) {
47
47
  }
48
48
  return color("info", word, { stream });
49
49
  }
50
+ function colorHumanMessage(message, { stream = process.stderr } = {}) {
51
+ return String(message).split("\n").map((line) => {
52
+ const marker = /^(\s*)(→|✓|✗)\s?(.*)$/u.exec(line);
53
+ if (marker) {
54
+ const style = marker[2] === "\u2713" ? "ok" : marker[2] === "\u2717" ? "err" : "accent";
55
+ return `${marker[1]}${color(style, marker[2], { stream })}${marker[3] ? ` ${marker[3]}` : ""}`;
56
+ }
57
+ const next = /^(\s*)Next:\s*(.*)$/u.exec(line);
58
+ if (next) {
59
+ return `${next[1]}${color("label", "Next:", { stream })}${next[2] ? ` ${color("cmd", next[2], { stream })}` : ""}`;
60
+ }
61
+ const warning = /^(\s*)(WARN(?:ING)?)(:?)[ \t]*(.*)$/iu.exec(line);
62
+ if (warning) {
63
+ return `${warning[1]}${color("warn", `${warning[2]}${warning[3]}`, { stream })}${warning[4] ? ` ${warning[4]}` : ""}`;
64
+ }
65
+ return line;
66
+ }).join("\n");
67
+ }
50
68
  function classifyLogEvent(event) {
51
69
  const text = String(event || "");
52
70
  if (/Module build failed|^ERROR in |BUILD FAILED|compiled with [1-9][0-9]* error/iu.test(text)) {
@@ -72,6 +90,7 @@ export {
72
90
  classifyLogEvent,
73
91
  color,
74
92
  colorEnabled,
93
+ colorHumanMessage,
75
94
  colorKv,
76
95
  colorLogEvent,
77
96
  colorStatusWord,
@@ -15,7 +15,7 @@ const SPEC = {
15
15
  { name: "logs", aliases: ["tail"], desc: "Compact build events or full log", flags: ["--full", "-f", "--window", "--events", "--source", "--json"] },
16
16
  { name: "debug", aliases: ["devtools", "inspect"], desc: "Open DevTools UI", flags: ["--json", "--no-open"] },
17
17
  { name: "fixtures", desc: "Manage the canonical wallet fixture (sync/set/generate)", args: ["sync", "set", "generate"], flags: ["--fixture", "--out", "--adapter", "--target", "--device", "--json"] },
18
- { name: "actions", desc: "List runnable recipe actions", flags: ["--json"] },
18
+ { name: "actions", desc: "List runnable recipe actions", flags: ["--json", "--categories", "--category", "--action", "--library"] },
19
19
  { name: "doctor", desc: "Check harness/orchestration health", flags: ["--json", "--target", "--adapter", "--runtime-dir", "--expect-live", "--print-ready", "--cdp-port", "--device"] },
20
20
  { name: "run", desc: "Execute a proof recipe (path or library name, e.g. run perps.smoke)", args: ["recipe.json|name"], flags: ["--list", "--device"] },
21
21
  { name: "recipe-quality", desc: "Build the recipe-quality artifact from compact JSON", args: ["build"], flags: ["--input", "--output", "--json"] },
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { detectAdapter, handleHarness } from "./harness.js";
2
- import { handleActions, handleManifest } from "./commands/manifest.js";
2
+ import { handleActions } from "./commands/manifest.js";
3
3
  import { handleDoctor } from "./commands/doctor.js";
4
4
  import { handleProvision } from "./commands/provision.js";
5
5
  import { handleRuntimeHealth } from "./commands/runtime-health.js";
@@ -21,10 +21,9 @@ import { handleRecipeQuality } from "./commands/recipe-quality.js";
21
21
  import { handleStatus } from "./commands/status.js";
22
22
  import { handleCheck } from "./commands/check.js";
23
23
  import { handleChecklist } from "./commands/checklist.js";
24
+ import { handleLast } from "./commands/last.js";
24
25
  import { parseArgs, targetPath } from "./commands/parse-args.js";
25
- import { runOneNode } from "./commands/run-engine.js";
26
26
  const COMMANDS = {
27
- manifest: handleManifest,
28
27
  actions: handleActions,
29
28
  doctor: handleDoctor,
30
29
  "runtime-health": handleRuntimeHealth,
@@ -35,7 +34,7 @@ const COMMANDS = {
35
34
  run: handleRun,
36
35
  "self-test": handleSelfTest
37
36
  };
38
- const OVERLAY_COMMANDS = ["install", "verify", "cleanup", "live"];
37
+ const OVERLAY_COMMANDS = ["install", "verify", "cleanup"];
39
38
  function usage() {
40
39
  console.error(`mm-harness \u2014 the MetaMask recipe harness: launch the app, prove behavior, manage the runtime overlay.
41
40
  Run it from inside a MetaMask checkout; the platform (mobile | extension | core) is auto-detected.
@@ -62,9 +61,6 @@ PROVE \u2014 run recipes and inspect capabilities:
62
61
  mm-harness check diff --profile fast --artifacts-dir artifacts/validation
63
62
  actions Describe the actions a manifest declares.
64
63
  mm-harness actions --adapter mobile
65
- manifest Print/validate the action manifest for an adapter.
66
- mm-harness manifest --adapter extension --json
67
-
68
64
  RUNTIME OVERLAY \u2014 install/verify/clean the per-checkout runtime overlay:
69
65
  provision Install the cached Runway mobile dev client (artifact cache + sim install).
70
66
  mm-harness provision runway ios --adapter mobile
@@ -72,12 +68,8 @@ RUNTIME OVERLAY \u2014 install/verify/clean the per-checkout runtime overlay:
72
68
  mm-harness install # inside a checkout, auto-detected
73
69
  verify Check the overlay/runtime is present and healthy (no launch).
74
70
  mm-harness verify
75
- live Launch/reuse the app, then verify live control end-to-end.
76
- mm-harness live
77
71
  cleanup Remove the installed overlay and restore the checkout.
78
72
  mm-harness cleanup
79
- # Open sub-question (Arthur): keep these top-level (mm-harness install) or group
80
- # them under an \`overlay\` command (mm-harness overlay install)? Top-level for now.
81
73
 
82
74
  ADVANCED \u2014 internal runtime probes (rarely typed by hand):
83
75
  runtime-health runtime-decision runtime-launch
@@ -88,7 +80,7 @@ ONE bin: mm-harness is the only command. No per-platform binaries \u2014 platfor
88
80
  auto-detected, the positional target forces it (mm-harness launch ios), and
89
81
  platform-specific needs are FLAGS on the same command (e.g. --sidebar, --full-build).
90
82
  Human happy path = the bare command; agents add depth via flags (--json, --target, ports).
91
- See docs/MENTAL-MODEL.md (overview) and docs/CLI-SPEC.md (full contract).
83
+ See README.md for the workflow and docs/RECIPES.md for proof authoring.
92
84
  `);
93
85
  }
94
86
  async function main(argv) {
@@ -111,7 +103,7 @@ async function main(argv) {
111
103
  const target = targetPath(options);
112
104
  const adapterRaw = (typeof options["adapter"] === "string" ? options["adapter"] : void 0) ?? (typeof options["platform"] === "string" ? options["platform"] : void 0) ?? detectAdapter(target);
113
105
  if (adapterRaw === "mobile" || adapterRaw === "extension" || adapterRaw === "core") {
114
- await handleHarness(["install", "--platform", adapterRaw, "--target", target]);
106
+ await handleHarness(["install", "--platform", adapterRaw, "--target", target, "--quiet"]);
115
107
  }
116
108
  } catch {
117
109
  }
@@ -129,10 +121,11 @@ async function main(argv) {
129
121
  if (command === "stop") return handleStop(argv.slice(1));
130
122
  if (command === "logs") return handleLogs(argv.slice(1));
131
123
  if (command === "debug") return handleDebug(argv.slice(1));
132
- if (command === "fixtures") return handleFixtures(argv.slice(1), { runOneNode });
124
+ if (command === "fixtures") return handleFixtures(argv.slice(1));
133
125
  if (command === "recipe-quality") return handleRecipeQuality(argv.slice(1));
134
126
  if (command === "check") return handleCheck(argv.slice(1));
135
127
  if (command === "checklist") return handleChecklist(argv.slice(1));
128
+ if (command === "last") return handleLast(parseArgs(argv.slice(1), command));
136
129
  const handler = COMMANDS[command];
137
130
  if (!handler) throw new Error(`Unknown command: ${command}`);
138
131
  return handler(parseArgs(argv.slice(1), command));