@deeeed/metamask-harness 0.79.0 → 0.80.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 (210) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +1 -1
  3. package/adapters/extension/ensure-browser.sh +2 -2
  4. package/adapters/extension/inject.mjs +0 -1
  5. package/adapters/extension/launch-browser.cjs +39 -613
  6. package/adapters/extension/readiness.mjs +2 -5
  7. package/adapters/extension/reattach.sh +2 -2
  8. package/adapters/extension/stamp-runtime-title.cjs +1 -1
  9. package/adapters/manifest.json +2 -34
  10. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +57 -1358
  11. package/adapters/mobile/bridge-runtime/teaching.cjs +20 -0
  12. package/adapters/mobile/inject.sh +16 -0
  13. package/adapters/mobile/launch-console-forwarder.cjs +1 -2
  14. package/adapters/mobile/open-device.sh +2 -2
  15. package/adapters/mobile/reload-app.mjs +4 -4
  16. package/adapters/mobile/reset-app-data.sh +1 -1
  17. package/adapters/mobile/start-console-forwarder.sh +3 -2
  18. package/adapters/mobile/start-metro.sh +17 -12
  19. package/adapters/mobile/wait-for-bridge.cjs +2 -2
  20. package/adapters/shared/harness-source-fingerprint.mjs +13 -8
  21. package/adapters/shared/open-debug.mjs +2 -2
  22. package/adapters/shared/sync-wallet-fixture.sh +8 -5
  23. package/adapters/terminal/cleanup.mjs +2 -1
  24. package/adapters/terminal/inject.mjs +2 -1
  25. package/bin/mm-harness +9 -28
  26. package/dist/adapters/checkout-files.js +16 -0
  27. package/dist/adapters/console-capture.js +14 -1
  28. package/dist/adapters/core/actions.js +4 -3
  29. package/dist/adapters/core/overlay.js +1 -1
  30. package/dist/adapters/core/readiness.js +28 -1
  31. package/dist/adapters/core/run.js +2 -2
  32. package/dist/adapters/core/surface.js +16 -1
  33. package/dist/adapters/extension/browser-pid.js +56 -0
  34. package/dist/adapters/extension/console-capture.js +1 -1
  35. package/dist/adapters/extension/doctor-checks.js +1 -1
  36. package/dist/adapters/extension/ensure-ready.js +5 -4
  37. package/dist/adapters/extension/extension-id.js +1 -1
  38. package/dist/adapters/extension/feature-flags.js +1 -1
  39. package/dist/adapters/extension/fixtures.js +1 -2
  40. package/dist/adapters/extension/launch.js +4 -5
  41. package/dist/adapters/extension/launched-browser.js +1 -2
  42. package/dist/adapters/extension/network-observer.js +1 -1
  43. package/dist/adapters/extension/overlay.js +1 -1
  44. package/dist/adapters/extension/product-config.js +1 -1
  45. package/dist/adapters/extension/readiness.js +1 -1
  46. package/dist/adapters/extension/reload.js +1 -1
  47. package/dist/adapters/extension/run.js +24 -10
  48. package/dist/adapters/extension/runtime-decision.js +2 -1
  49. package/dist/adapters/extension/runtime.js +9 -5
  50. package/dist/adapters/extension/surface.js +15 -2
  51. package/dist/adapters/extension/verify.js +2 -2
  52. package/dist/adapters/harness-freshness.js +2 -1
  53. package/dist/adapters/mobile/android-gfxinfo-performance.js +1 -3
  54. package/dist/adapters/mobile/app-identity.js +10 -0
  55. package/dist/adapters/mobile/doctor-checks.js +1 -1
  56. package/dist/adapters/mobile/fixtures.js +1 -1
  57. package/dist/adapters/mobile/launch.js +27 -28
  58. package/dist/adapters/mobile/lifecycle.js +1 -1
  59. package/dist/adapters/mobile/metro-env.js +9 -59
  60. package/dist/adapters/mobile/performance-observer.js +1 -1
  61. package/dist/adapters/mobile/prepare-target.js +1 -1
  62. package/dist/adapters/mobile/prepare.js +3 -4
  63. package/dist/adapters/mobile/provision.js +1 -1
  64. package/dist/adapters/mobile/readiness.js +1 -1
  65. package/dist/adapters/mobile/recording-target.js +15 -0
  66. package/dist/adapters/mobile/release-artifact-state.js +1 -1
  67. package/dist/adapters/mobile/reload.js +1 -1
  68. package/dist/adapters/mobile/run.js +8 -7
  69. package/dist/adapters/mobile/runtime-decision.js +2 -4
  70. package/dist/adapters/mobile/source-freshness.js +22 -126
  71. package/dist/adapters/mobile/surface.js +26 -3
  72. package/dist/adapters/mobile/ui.js +16 -5
  73. package/dist/adapters/mobile/verify.js +2 -3
  74. package/dist/adapters/performance/cdp-trace.js +1 -1
  75. package/dist/adapters/performance/device-platform.js +1 -1
  76. package/dist/adapters/slot-ports.js +1 -1
  77. package/dist/adapters/surface.js +1 -0
  78. package/dist/adapters/terminal/console-capture.js +1 -1
  79. package/dist/adapters/terminal/launch.js +1 -1
  80. package/dist/adapters/terminal/run.js +25 -2
  81. package/dist/adapters/terminal/surface.js +7 -1
  82. package/dist/adapters.js +8 -4
  83. package/dist/advisor-policy.js +1 -1
  84. package/dist/checkout-view.js +1 -2
  85. package/dist/cli.js +28 -15
  86. package/dist/command-contract.js +1 -26
  87. package/dist/commands/call.js +10 -24
  88. package/dist/commands/check.js +3 -4
  89. package/dist/commands/checklist.js +1 -1
  90. package/dist/commands/completion-candidates.js +2 -10
  91. package/dist/commands/config.js +2 -2
  92. package/dist/commands/core-readiness.js +12 -7
  93. package/dist/commands/debug.js +3 -3
  94. package/dist/commands/device-target.js +5 -5
  95. package/dist/commands/doctor.js +2 -14
  96. package/dist/commands/domain.js +2 -2
  97. package/dist/commands/ensure-ready.js +1 -7
  98. package/dist/commands/fixtures.js +5 -6
  99. package/dist/commands/help.js +3 -4
  100. package/dist/commands/launch/device.js +10 -17
  101. package/dist/commands/launch/extension.js +2 -8
  102. package/dist/commands/launch/mobile.js +1 -1
  103. package/dist/commands/list-executables.js +1 -3
  104. package/dist/commands/logs.js +3 -3
  105. package/dist/commands/manifest.js +1 -12
  106. package/dist/commands/mobile-device-view.js +4 -2
  107. package/dist/commands/overlay.js +110 -0
  108. package/dist/commands/pin.js +1 -1
  109. package/dist/commands/pr-body.js +2 -2
  110. package/dist/commands/prepare.js +4 -9
  111. package/dist/commands/provision.js +3 -12
  112. package/dist/commands/recipe-advice.js +1 -2
  113. package/dist/commands/recipe-quality.js +2 -3
  114. package/dist/commands/resolve-extension.js +1 -7
  115. package/dist/commands/review.js +4 -4
  116. package/dist/commands/run-engine.js +12 -51
  117. package/dist/commands/run.js +4 -25
  118. package/dist/commands/runtime-decision.js +1 -6
  119. package/dist/commands/runtime-health.js +1 -7
  120. package/dist/commands/runtime-launch.js +1 -13
  121. package/dist/commands/self-test.js +2 -6
  122. package/dist/commands/status-watch.js +1 -2
  123. package/dist/commands/status.js +2 -5
  124. package/dist/commands/task-init.js +2 -3
  125. package/dist/commands/tutorial.js +2 -2
  126. package/dist/commands/update.js +1 -1
  127. package/dist/completions-cache.js +1 -1
  128. package/dist/console-classifier.js +19 -0
  129. package/dist/doctor.js +2 -3
  130. package/dist/funding-execution-context.js +15 -1
  131. package/dist/host.js +32 -0
  132. package/dist/index.js +1 -6
  133. package/dist/live-tsx.js +49 -0
  134. package/dist/mm-harness-cli.js +2 -4
  135. package/dist/network-observation.js +4 -4
  136. package/dist/paths.js +34 -64
  137. package/dist/performance-observation.js +2 -2
  138. package/dist/progress.js +1 -1
  139. package/dist/recipe-assistance-context.js +1 -3
  140. package/dist/recipe-security.js +2 -59
  141. package/dist/review/knowledge.js +2 -2
  142. package/dist/runner.js +5 -3
  143. package/dist/runtime-context.js +1 -1
  144. package/dist/task-view.js +2 -3
  145. package/docs/CONTRIBUTING.md +1 -1
  146. package/docs/cli-contract.md +2 -2
  147. package/docs/feature-guard.md +90 -0
  148. package/library/README.md +3 -1
  149. package/library/actions/core/perps/_controller.mjs +153 -130
  150. package/library/actions/core/perps/quote_fees.mjs +58 -0
  151. package/library/actions/core/wallet/_fixture-accounts.mjs +69 -0
  152. package/library/actions/core/wallet/list_accounts.mjs +14 -57
  153. package/library/actions/extension/feature_flags/state.mjs +1 -1
  154. package/library/actions/extension/platform/cdp.mjs +2 -8
  155. package/library/actions/harness-exports.mjs +1 -6
  156. package/library/actions/mobile/app/network-control.mjs +6 -8
  157. package/library/actions/mobile/perps/prepare_local_snapshot_endpoint.mjs +1 -1
  158. package/library/actions/mobile/platform/bridge.mjs +8 -8
  159. package/library/actions/mobile/platform/native-session-name.mjs +21 -1
  160. package/library/actions/mobile/platform/native-session.mjs +3 -4
  161. package/library/actions/mobile/platform/observe-ui.mjs +12 -6
  162. package/library/actions/mobile/ui/native-navigation.mjs +4 -3
  163. package/library/actions/mobile/wallet/native-ui.mjs +3 -2
  164. package/library/actions/shared/analytics/_adapter.mjs +1 -1
  165. package/library/actions/shared/console-findings.mjs +1 -1
  166. package/library/actions/terminal/platform/runtime.mjs +2 -1
  167. package/library/manifests/core.action-manifest.json +83 -13
  168. package/package.json +8 -9
  169. package/adapters/extension/lib/slot-title.cjs +0 -292
  170. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +0 -472
  171. package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +0 -125
  172. package/adapters/mobile/bridge-runtime/lib/cdp-broker.cjs +0 -1055
  173. package/adapters/mobile/bridge-runtime/lib/cdp-eval.cjs +0 -110
  174. package/adapters/mobile/bridge-runtime/lib/config.cjs +0 -61
  175. package/adapters/mobile/bridge-runtime/lib/console-format.cjs +0 -53
  176. package/adapters/mobile/bridge-runtime/lib/devtools-proxy.cjs +0 -177
  177. package/adapters/mobile/bridge-runtime/lib/issue-capture.cjs +0 -446
  178. package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +0 -84
  179. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +0 -403
  180. package/adapters/mobile/bridge-runtime/lib/ws-client.cjs +0 -116
  181. package/adapters/mobile/coalesce-metro-log.cjs +0 -24
  182. package/adapters/mobile/launch-metro.cjs +0 -77
  183. package/adapters/mobile/metro-config.cjs +0 -98
  184. package/adapters/mobile/metro-log-generation.cjs +0 -106
  185. package/dist/adapters/mobile/frame-metrics.js +0 -118
  186. package/dist/adapters/mobile/video-recorder.js +0 -289
  187. package/dist/artifact-files.js +0 -92
  188. package/dist/checkout-lock.js +0 -149
  189. package/dist/cli-color.js +0 -98
  190. package/dist/command-journal.js +0 -308
  191. package/dist/commands/last.js +0 -60
  192. package/dist/commands/launch/index.js +0 -109
  193. package/dist/commands/parse-args.js +0 -279
  194. package/dist/commands/reload.js +0 -41
  195. package/dist/commands/run-report.js +0 -241
  196. package/dist/commands/shared.js +0 -323
  197. package/dist/commands/stop.js +0 -60
  198. package/dist/devices.js +0 -100
  199. package/dist/execution-provenance.js +0 -332
  200. package/dist/harness.js +0 -451
  201. package/dist/heal-bounds.js +0 -137
  202. package/dist/json-stream.js +0 -60
  203. package/dist/leaf-invoke.js +0 -19
  204. package/dist/library-provenance.js +0 -33
  205. package/dist/live-adapter-contract.js +0 -380
  206. package/dist/recording-target.js +0 -120
  207. package/dist/run-diagnostics.js +0 -364
  208. package/dist/run-recording.js +0 -481
  209. package/dist/runtime-proof.js +0 -90
  210. package/library/actions/mobile/platform/tool-paths.mjs +0 -123
package/dist/harness.js DELETED
@@ -1,451 +0,0 @@
1
- import { spawnScriptStreaming } from "./commands/shared.js";
2
- import { execFileSync } from "node:child_process";
3
- import fs from "node:fs";
4
- import path from "node:path";
5
- import { shellLeafMissing, missingShellLeafMessage } from "./leaf-invoke.js";
6
- import { adapterRegistry } from "./adapters/registry.js";
7
- import { recipeHarnessPath, recipeRuntimeDir, runnerDir } from "./paths.js";
8
- import { colorHumanMessage } from "./cli-color.js";
9
- const HARNESS_ACTIONS = ["install", "verify", "cleanup"];
10
- const ADAPTERS = ["mobile", "extension", "core", "terminal"];
11
- function harnessUsage() {
12
- console.error(`mm-harness \u2014 install and validate the MetaMask recipe runtime in a checkout.
13
-
14
- Run it from inside a MetaMask checkout; the platform (mobile | extension | core | terminal)
15
- is auto-detected from the repo. Pass --platform only to override.
16
-
17
- Commands (one copy-pasteable example each):
18
- install Install the recipe harness runtime overlay into the checkout.
19
- mm-harness install
20
- verify Check the harness/runtime is present and healthy (no app launch).
21
- mm-harness verify
22
- cleanup Remove the installed harness overlay and restore the checkout.
23
- mm-harness cleanup
24
-
25
- Options:
26
- --platform <mobile|extension|core|terminal> Override auto-detection (alias: --adapter).
27
- --target <repo> Checkout to operate on (default: current dir).
28
- --json Machine-readable summary for agents/scripts.
29
- -- <args> Forward the rest verbatim to the underlying
30
- script, e.g. -- --platform ios --preflight-mode fast
31
- (mobile) or -- --cdp-port 6665 (extension).
32
-
33
- Examples:
34
- mm-harness install # inside a checkout, auto-detected
35
- mm-harness verify --platform extension
36
- mm-harness cleanup --target ../metamask-mobile
37
-
38
- core supports install, verify, and cleanup (headless; no live).`);
39
- }
40
- function failureHint(surface, action) {
41
- const restart = surface.harness.restart;
42
- if (!restart) {
43
- if (action === "verify") {
44
- return `Install the ${surface.id} harness first: mm-harness install --platform ${surface.id}`;
45
- }
46
- return "Read the error above for the specific cause, then re-run this command.";
47
- }
48
- return `Read the error above for the specific cause. To (re)start the runtime, run: ${restart}`;
49
- }
50
- function successNext(adapter, action, target) {
51
- const base = `--adapter ${adapter} --target ${shellQuote(target)}`;
52
- if (action === "install") return `mm-harness verify ${base}`;
53
- if (action === "cleanup") return `mm-harness install ${base}`;
54
- return `mm-harness status --target ${shellQuote(target)} --json`;
55
- }
56
- function hasArg(args, needle) {
57
- return args.some((arg) => arg === needle || arg.startsWith(`${needle}=`));
58
- }
59
- function argValue(args, needle) {
60
- for (let i = 0; i < args.length; i += 1) {
61
- if (args[i] === needle) return args[i + 1];
62
- if (args[i].startsWith(`${needle}=`)) return args[i].slice(needle.length + 1);
63
- }
64
- return void 0;
65
- }
66
- function shellQuote(value) {
67
- return /^[A-Za-z0-9_./:=@+-]+$/u.test(value) ? value : `'${value.replace(/'/gu, `'\\''`)}'`;
68
- }
69
- function isAdapter(value) {
70
- return value !== void 0 && adapterRegistry.has(value);
71
- }
72
- function parseHarnessArgs(args) {
73
- const forward = [];
74
- let adapter;
75
- let json = false;
76
- let quiet = false;
77
- let separator = false;
78
- for (let i = 0; i < args.length; i += 1) {
79
- const arg = args[i];
80
- if (separator) {
81
- forward.push(arg);
82
- continue;
83
- }
84
- if (arg === "--") {
85
- separator = true;
86
- continue;
87
- }
88
- if (arg === "--json") {
89
- json = true;
90
- continue;
91
- }
92
- if (arg === "--quiet") {
93
- quiet = true;
94
- continue;
95
- }
96
- if (arg === "--adapter" || arg === "--platform") {
97
- const value = args[i + 1];
98
- if (arg === "--adapter") {
99
- adapter = value;
100
- i += 1;
101
- continue;
102
- }
103
- if (adapter === void 0 && isAdapter(value)) {
104
- adapter = value;
105
- i += 1;
106
- continue;
107
- }
108
- forward.push(arg);
109
- continue;
110
- }
111
- if (arg.startsWith("--adapter=")) {
112
- adapter = arg.slice("--adapter=".length);
113
- continue;
114
- }
115
- if (arg.startsWith("--platform=")) {
116
- const value = arg.slice("--platform=".length);
117
- if (adapter === void 0 && isAdapter(value)) {
118
- adapter = value;
119
- continue;
120
- }
121
- forward.push(arg);
122
- continue;
123
- }
124
- forward.push(arg);
125
- }
126
- return { adapter, json, quiet, forward };
127
- }
128
- const ADAPTER_DETECT_NEXT = "cd into a MetaMask checkout or pass --target <path>, or force it with --adapter <mobile|extension|core|terminal>";
129
- function detectAdapter(target) {
130
- let remote = "";
131
- try {
132
- remote = execFileSync("git", ["-C", target, "config", "--get", "remote.origin.url"], {
133
- encoding: "utf8",
134
- stdio: ["ignore", "pipe", "ignore"]
135
- }).trim();
136
- } catch {
137
- remote = "";
138
- }
139
- if (remote.includes("metamask-extension")) return "extension";
140
- if (remote.includes("metamask-mobile")) return "mobile";
141
- const exists = (rel) => fs.existsSync(path.join(target, rel));
142
- const isDir = (rel) => {
143
- try {
144
- return fs.statSync(path.join(target, rel)).isDirectory();
145
- } catch {
146
- return false;
147
- }
148
- };
149
- if (exists("development/skills-sync.ts") || isDir("ui") && isDir("app/scripts")) return "extension";
150
- if (exists("scripts/skills-sync.mts") || isDir("ios") && isDir("android") && isDir("app/core")) return "mobile";
151
- if (isDir("packages/perps-controller") && exists("yarn.lock")) return "core";
152
- if (remote.includes("va-mmcx-terminal") || isDir("src/features/perpetuals") && (exists("next.config.ts") || exists("next.config.mjs") || exists("next.config.js"))) return "terminal";
153
- return void 0;
154
- }
155
- function resolveRuntimeContextPath(target) {
156
- return process.env.RECIPE_RUNTIME_CONTEXT ?? path.join(target, recipeRuntimeDir(), "agentic-runtime.json");
157
- }
158
- function readRuntimeContextField(contextPath, field) {
159
- let data;
160
- try {
161
- data = JSON.parse(fs.readFileSync(contextPath, "utf8"));
162
- } catch {
163
- return void 0;
164
- }
165
- let node = data;
166
- for (const key of field.split(".")) {
167
- if (node === void 0 || node === null || typeof node !== "object") return void 0;
168
- node = node[key];
169
- }
170
- if (node === void 0 || node === null || node === "") return void 0;
171
- return String(node);
172
- }
173
- function resolveEntry(base, candidates, mode) {
174
- for (const candidate of candidates) {
175
- const full = path.resolve(base, candidate);
176
- try {
177
- const stat = fs.statSync(full);
178
- if (!stat.isFile()) continue;
179
- if (mode === "file" || (stat.mode & 73) !== 0) return full;
180
- } catch {
181
- }
182
- }
183
- return path.resolve(base, candidates[0]);
184
- }
185
- function installedRunnerSource(target, adapter) {
186
- const pointer = path.join(recipeHarnessPath(target, adapter), "runner", ".runner-source");
187
- if (!fs.existsSync(pointer)) return void 0;
188
- const value = fs.readFileSync(pointer, "utf8").trim();
189
- if (!value || !fs.existsSync(value)) return void 0;
190
- return value;
191
- }
192
- function resolveHarnessDispatch(surface, action, target) {
193
- if (action === "verify") return surface.harness.verify(target);
194
- const leaf = action === "install" ? surface.harness.install : surface.harness.cleanup;
195
- const base = action === "cleanup" && surface.harness.cleanup.fromInstalledRunner ? installedRunnerSource(target, surface.id) ?? runnerDir : runnerDir;
196
- const entry = resolveEntry(base, [leaf.entry, leaf.fallback], "file");
197
- return leaf.node ? { command: process.execPath, prefixArgs: [entry] } : { command: entry, prefixArgs: [] };
198
- }
199
- async function handleHarness(argv) {
200
- const action = argv[0];
201
- if (!action || action === "-h" || action === "--help") {
202
- harnessUsage();
203
- return action ? 0 : 2;
204
- }
205
- if (!HARNESS_ACTIONS.includes(action)) {
206
- harnessUsage();
207
- console.error(`unsupported harness subcommand: ${action}`);
208
- return 2;
209
- }
210
- const harnessAction = action;
211
- const { getAdapterSurface } = await import("./adapters/surface.js");
212
- const { adapter: parsedAdapter, json, quiet, forward } = parseHarnessArgs(argv.slice(1));
213
- const rawTarget = argValue(forward, "--target");
214
- const target = path.resolve(rawTarget ?? process.cwd());
215
- const adapter = parsedAdapter ?? detectAdapter(target);
216
- if (!adapter || !isAdapter(adapter)) {
217
- if (quiet) {
218
- return 2;
219
- } else if (json) {
220
- const detectError = adapter ? { code: "UNSUPPORTED_PLATFORM", message: `unsupported platform: ${adapter}`, userAction: "pass --adapter <mobile|extension|core|terminal> to specify a supported adapter" } : { code: "ADAPTER_DETECTION_FAILED", message: `could not detect the MetaMask repo type for ${target}`, userAction: ADAPTER_DETECT_NEXT };
221
- console.log(harnessSummary(harnessAction, void 0, target, "fail", 2, false, detectError));
222
- } else {
223
- harnessUsage();
224
- console.error(
225
- adapter ? `
226
- \u2717 unsupported platform: ${adapter}` : `
227
- \u2717 could not detect the MetaMask repo type for ${target}
228
- Next: ${ADAPTER_DETECT_NEXT}`
229
- );
230
- }
231
- return 2;
232
- }
233
- const autoDetected = parsedAdapter === void 0;
234
- let forwardArgs = hasArg(forward, "--target") ? [...forward] : ["--target", target, ...forward];
235
- if (harnessAction === "install" && hasArg(forward, "--runway")) {
236
- return handleRunwayInstall(adapter, target, forward, json);
237
- }
238
- const surface = getAdapterSurface(adapter);
239
- if (harnessAction === "verify" && surface.harness.verifyArgs) {
240
- forwardArgs = surface.harness.verifyArgs(target, forwardArgs);
241
- }
242
- const dispatch = resolveHarnessDispatch(surface, harnessAction, target);
243
- if ("error" in dispatch) {
244
- if (quiet) {
245
- return 1;
246
- } else if (json) {
247
- console.log(
248
- harnessSummary(harnessAction, adapter, target, "fail", 1, autoDetected, {
249
- code: "DISPATCH_UNAVAILABLE",
250
- message: dispatch.error,
251
- // The dispatch error embeds "Next: <hint>" — extract it so --json consumers
252
- // get a clean programmatic escape without parsing the human message.
253
- userAction: dispatch.error.split("\nNext: ")[1] ?? "run mm-harness install to complete setup, then retry"
254
- })
255
- );
256
- } else {
257
- console.error(`\u2717 ${harnessAction} ${adapter} failed
258
- ${dispatch.error.replace(/\n/gu, "\n ")}`);
259
- }
260
- return 1;
261
- }
262
- if (!json && !quiet) {
263
- const detected = autoDetected ? ", auto-detected" : "";
264
- console.error(colorHumanMessage(`\u2192 ${harnessAction} (${adapter}${detected}) \u2014 target: ${target}`));
265
- }
266
- const start = Date.now();
267
- if ("inProcess" in dispatch) {
268
- const runVerify = await dispatch.inProcess();
269
- let exitCode;
270
- try {
271
- exitCode = await runVerify(forwardArgs, {
272
- out: quiet ? () => {
273
- } : (line) => {
274
- (json ? process.stderr : process.stdout).write(`${line}
275
- `);
276
- },
277
- err: quiet ? () => {
278
- } : (line) => {
279
- process.stderr.write(`${line}
280
- `);
281
- }
282
- });
283
- } catch (error) {
284
- if (!quiet) process.stderr.write(`${error instanceof Error ? error.message : String(error)}
285
- `);
286
- exitCode = 1;
287
- }
288
- return reportHarnessOutcome(harnessAction, surface, target, exitCode, autoDetected, json, quiet, start);
289
- }
290
- if (shellLeafMissing(dispatch.command)) {
291
- const message = missingShellLeafMessage(dispatch.command);
292
- if (quiet) {
293
- return 1;
294
- } else if (json) {
295
- console.log(
296
- harnessSummary(harnessAction, adapter, target, "fail", 1, autoDetected, {
297
- code: "HARNESS_SPAWN_FAILED",
298
- message,
299
- userAction: failureHint(surface, harnessAction)
300
- })
301
- );
302
- } else {
303
- console.error(`\u2717 ${message}`);
304
- }
305
- return 1;
306
- }
307
- const result = await spawnScriptStreaming(dispatch.command, [...dispatch.prefixArgs, ...forwardArgs], target, {
308
- forward: quiet ? "none" : json ? "stderr" : "original",
309
- stdin: quiet ? "ignore" : "inherit",
310
- capture: false
311
- });
312
- if (result.error) {
313
- if (quiet) {
314
- return 1;
315
- } else if (json) {
316
- console.log(
317
- harnessSummary(harnessAction, adapter, target, "fail", 1, autoDetected, {
318
- code: "HARNESS_SPAWN_FAILED",
319
- message: `${harnessAction} ${adapter} could not start: ${result.error.message}`,
320
- userAction: failureHint(surface, harnessAction)
321
- })
322
- );
323
- } else {
324
- console.error(`\u2717 ${harnessAction} ${adapter} could not start: ${result.error.message}
325
- ${failureHint(surface, harnessAction)}`);
326
- }
327
- return 1;
328
- }
329
- return reportHarnessOutcome(harnessAction, surface, target, result.status ?? 1, autoDetected, json, quiet, start);
330
- }
331
- function reportHarnessOutcome(action, surface, target, exitCode, autoDetected, json, quiet, start) {
332
- const seconds = ((Date.now() - start) / 1e3).toFixed(1);
333
- const adapter = surface.id;
334
- if (quiet) {
335
- return exitCode;
336
- } else if (json) {
337
- console.log(
338
- harnessSummary(
339
- action,
340
- adapter,
341
- target,
342
- exitCode === 0 ? "pass" : "fail",
343
- exitCode,
344
- autoDetected,
345
- exitCode === 0 ? void 0 : { code: "HARNESS_FAILED", message: `${action} ${adapter} failed (exit ${exitCode})`, userAction: failureHint(surface, action) }
346
- )
347
- );
348
- } else if (exitCode === 0) {
349
- console.error(colorHumanMessage(`\u2713 ${action} ${adapter} passed (${seconds}s)
350
- Next: ${successNext(adapter, action, target)}`));
351
- } else {
352
- console.error(colorHumanMessage(`\u2717 ${action} ${adapter} failed (exit ${exitCode}, ${seconds}s)
353
- ${failureHint(surface, action)}`));
354
- }
355
- return exitCode;
356
- }
357
- async function handleRunwayInstall(adapter, target, forward, json) {
358
- const { getAdapterSurface } = await import("./adapters/surface.js");
359
- const rerunCommand = runwayInstallRerunCommand(adapter, target, forward, json);
360
- const surface = getAdapterSurface(adapter);
361
- const runtimeDir = argValue(forward, "--runtime-dir");
362
- const watcherPort = argValue(forward, "--watcher-port");
363
- const result = await surface.runwayProvision.run(target, {
364
- json,
365
- platform: argValue(forward, "--platform") ?? "ios",
366
- branch: argValue(forward, "--branch"),
367
- defaultBranch: argValue(forward, "--default-branch"),
368
- run: argValue(forward, "--run"),
369
- cacheRoot: argValue(forward, "--cache-root"),
370
- simulator: argValue(forward, "--simulator") ?? argValue(forward, "--device"),
371
- runtime: argValue(forward, "--runtime"),
372
- deviceType: argValue(forward, "--device-type"),
373
- slot: argValue(forward, "--slot"),
374
- watcherPort,
375
- runtimeDir,
376
- force: hasArg(forward, "--force"),
377
- resolveOnly: hasArg(forward, "--resolve-only"),
378
- rerunCommand
379
- });
380
- const provisionedSimulator = typeof result.simulator === "object" && result.simulator ? result.simulator : void 0;
381
- const provisionedDevice = typeof provisionedSimulator?.udid === "string" ? provisionedSimulator.udid : typeof provisionedSimulator?.name === "string" ? provisionedSimulator.name : void 0;
382
- const next = result.status === "pass" && surface.runwayProvision.launchNext && result.resolveOnly !== true ? `mm-harness launch ${String(result.platform ?? "ios")} --adapter ${adapter} --target ${shellQuote(target)}` + (provisionedDevice ? ` --device ${shellQuote(provisionedDevice)}` : "") : void 0;
383
- const output = next ? { ...result, next } : result;
384
- if (json) {
385
- console.log(JSON.stringify(output, null, 2));
386
- } else if (result.status === "pass") {
387
- const cache = typeof result.cache === "object" && result.cache ? result.cache : void 0;
388
- const simulator = provisionedSimulator;
389
- const artifact = typeof result.artifact === "object" && result.artifact ? result.artifact : void 0;
390
- if (result.resolveOnly) {
391
- console.error(`\u2713 resolved Runway app for ${adapter} ${result.platform ?? ""} run=${artifact?.runId ?? "unknown"} revision=${artifact?.revision ?? "unknown"} artifact=${artifact?.artifactName ?? "unknown"}`);
392
- } else {
393
- const action = result.skipped ? "already provisioned" : "installed Runway app";
394
- console.error(`\u2713 ${action} for ${adapter} ${result.platform ?? ""} simulator=${simulator?.name ?? "unknown"} udid=${simulator?.udid ?? "unknown"} cache=${cache?.status ?? "skip"}`);
395
- }
396
- if (next) console.error(` Next: ${next}`);
397
- } else {
398
- console.error(`\u2717 mm-harness install --runway: ${result.error?.message ?? "runway install failed"}
399
- Next: ${result.error?.userAction ?? rerunCommand}`);
400
- }
401
- return result.exitCode;
402
- }
403
- function runwayInstallRerunCommand(adapter, target, forward, json) {
404
- const parts = ["mm-harness", "install", "--runway", "--adapter", adapter, "--target", shellQuote(target)];
405
- const valueFlags = [
406
- "--platform",
407
- "--branch",
408
- "--default-branch",
409
- "--run",
410
- "--cache-root",
411
- "--simulator",
412
- "--device",
413
- "--slot",
414
- "--watcher-port",
415
- "--runtime-dir",
416
- "--runtime",
417
- "--device-type"
418
- ];
419
- for (const flag of valueFlags) {
420
- const value = argValue(forward, flag);
421
- if (value) parts.push(flag, shellQuote(value));
422
- }
423
- if (hasArg(forward, "--force")) parts.push("--force");
424
- if (hasArg(forward, "--resolve-only")) parts.push("--resolve-only");
425
- if (json) parts.push("--json");
426
- return parts.join(" ");
427
- }
428
- function harnessSummary(action, adapter, target, status, exitCode, autoDetected, error) {
429
- return JSON.stringify({
430
- schemaVersion: 1,
431
- command: "harness",
432
- action,
433
- adapter: adapter ?? null,
434
- target,
435
- autoDetected,
436
- status,
437
- exitCode,
438
- // Error contract: every --json failure carries a stable machine code + human
439
- // message. userAction is included when present so callers
440
- // can surface the reachable escape without parsing the human message.
441
- ...status === "fail" && error ? { error } : {},
442
- ...status === "pass" && adapter ? { next: successNext(adapter, action, target) } : {}
443
- });
444
- }
445
- export {
446
- ADAPTER_DETECT_NEXT,
447
- detectAdapter,
448
- handleHarness,
449
- readRuntimeContextField,
450
- resolveRuntimeContextPath
451
- };
@@ -1,137 +0,0 @@
1
- import { spawnSync } from "node:child_process";
2
- import fs from "node:fs";
3
- import path from "node:path";
4
- import { getAdapterSurface } from "./adapters/surface.js";
5
- import { handleHarness } from "./harness.js";
6
- import { recipeHarnessPath, recipeRuntimePath } from "./paths.js";
7
- import { EXIT } from "./commands/shared.js";
8
- function newHealState() {
9
- return { recovered: [], mutations: [], attemptedRecoveries: [] };
10
- }
11
- function conciseFailureForHuman(output) {
12
- const errors = output.split(/\r?\n/u).map((line) => /^(?:[A-Za-z]*Error):\s*(.+)$/u.exec(line.trim())?.[1]).filter((line) => Boolean(line));
13
- return errors.at(-1) ?? output.trim();
14
- }
15
- function parseHeal(options, fallback) {
16
- const value = options.heal;
17
- if (value === void 0) return fallback;
18
- if (value === "off" || value === "infra-only" || value === "auto") return value;
19
- return { error: `--heal must be off, infra-only, or auto (got "${String(value)}").` };
20
- }
21
- function overlayDir(target, adapter) {
22
- return recipeHarnessPath(target, adapter);
23
- }
24
- function overlayPresent(target, adapter) {
25
- try {
26
- return fs.statSync(overlayDir(target, adapter)).isDirectory();
27
- } catch {
28
- return false;
29
- }
30
- }
31
- function overlayDelegateValid(target, adapter) {
32
- if (getAdapterSurface(adapter).headless) return true;
33
- const pointer = path.join(recipeHarnessPath(target, adapter), "runner", ".runner-source");
34
- if (!fs.existsSync(pointer)) return true;
35
- const runnerPath = fs.readFileSync(pointer, "utf8").trim();
36
- if (!Boolean(runnerPath) || !fs.existsSync(runnerPath)) return false;
37
- return fs.existsSync(path.join(runnerPath, "bin", "mm-harness"));
38
- }
39
- async function ensureOverlay(adapter, target, heal, state, json) {
40
- if (getAdapterSurface(adapter).headless) return { ok: true };
41
- if (overlayPresent(target, adapter) && overlayDelegateValid(target, adapter)) return { ok: true };
42
- if (heal === "off") return { ok: true };
43
- const installBin = process.env.MM_HARNESS_INSTALL_BIN;
44
- let code;
45
- if (installBin) {
46
- const result = spawnSync(installBin, ["install", "--platform", adapter, "--target", target], {
47
- cwd: target,
48
- stdio: ["ignore", 2, "inherit"],
49
- env: process.env
50
- });
51
- code = result.status ?? 1;
52
- } else {
53
- code = await handleHarness(["install", "--platform", adapter, "--target", target, "--quiet"]);
54
- }
55
- if (code !== 0 || !overlayPresent(target, adapter)) {
56
- return { ok: false, error: `runtime overlay install failed (exit ${code})` };
57
- }
58
- const dir = overlayDir(target, adapter);
59
- state.mutations.push({ type: "file", action: "created", path: dir });
60
- if (!json) process.stderr.write(`installed mm-harness overlay \u2192 ${dir}
61
- `);
62
- return { ok: true };
63
- }
64
- function classifyFailure(output) {
65
- if (/SCREENSHOT_PROTECTED|screenshot is protected by FLAG_SECURE/iu.test(output)) return "screenshot-protected";
66
- if (/CDP_TIMEOUT|Mobile CDP bridge command timed out|NO_TARGET|WS_CLOSED/iu.test(output)) return "infra";
67
- if (/fixture|keyring|not seeded|\bsrp\b|password|onboard/iu.test(output)) return "wallet";
68
- if (/metro|cdp|chrome|bridge|bundle|packager|port\b|econnrefused|not reachable|watcher|dev client|websocket/iu.test(output)) {
69
- return "infra";
70
- }
71
- return "app";
72
- }
73
- function recipeRunning(target) {
74
- if (process.env.MM_HARNESS_RECIPE_RUNNING === "1") return true;
75
- return fs.existsSync(recipeRuntimePath(target, "recipe.lock"));
76
- }
77
- const RECOVERY_CODE = {
78
- mobile: "metro.restarted",
79
- extension: "chrome.reopened"
80
- };
81
- function checkHealBounds(target, output, state) {
82
- const originalError = output.trim() || void 0;
83
- if (recipeRunning(target)) {
84
- return {
85
- code: "RECIPE_RUNNING",
86
- exitCode: EXIT.bounded,
87
- message: "a recipe is currently running \u2014 refusing recovery to avoid corrupting mid-run state.",
88
- originalError
89
- };
90
- }
91
- const failureClass = classifyFailure(conciseFailureForHuman(output));
92
- if (failureClass === "screenshot-protected") {
93
- return {
94
- code: "SCREENSHOT_PROTECTED",
95
- exitCode: EXIT.runtime,
96
- message: "the focused Android window intentionally blocks screenshots; a black capture cannot be used as evidence.",
97
- userAction: "unlock or navigate to a non-protected screen, then rerun the screenshot action",
98
- originalError
99
- };
100
- }
101
- if (failureClass === "wallet") {
102
- return {
103
- code: "WALLET_STATE_REQUIRED",
104
- exitCode: EXIT.bounded,
105
- message: "wallet prerequisites need attention; healing never changes wallet state.",
106
- userAction: /(?:^|\n)\s*Next:\s*([^\r\n]+)/u.exec(output)?.[1] ?? "run mm-harness doctor --json",
107
- originalError
108
- };
109
- }
110
- if (failureClass === "app") {
111
- return {
112
- code: "APP_LOGIC_FAILURE",
113
- exitCode: EXIT.runtime,
114
- message: "failure looks like app-logic \u2014 healing cannot help; surface verbatim.",
115
- originalError
116
- };
117
- }
118
- if (state.attemptedRecoveries.length > 0) {
119
- return {
120
- code: "SAME_RECOVERY_TWICE",
121
- exitCode: EXIT.bounded,
122
- message: state.recovered.length > 0 ? "one recovery already succeeded this invocation \u2014 bounded policy refuses a second recovery." : "one recovery was already attempted this invocation \u2014 refusing to loop.",
123
- originalError
124
- };
125
- }
126
- return null;
127
- }
128
- export {
129
- RECOVERY_CODE,
130
- checkHealBounds,
131
- classifyFailure,
132
- conciseFailureForHuman,
133
- ensureOverlay,
134
- newHealState,
135
- parseHeal,
136
- recipeRunning
137
- };
@@ -1,60 +0,0 @@
1
- import { recordCommandStage } from "./command-journal.js";
2
- class JsonStreamWriter {
3
- enabled;
4
- command;
5
- completed = false;
6
- writeLine;
7
- constructor(command, enabled, output = process.stdout) {
8
- this.command = command;
9
- this.enabled = enabled;
10
- const write = output.write.bind(output);
11
- this.writeLine = (line) => {
12
- write(`${line}
13
- `);
14
- };
15
- }
16
- isolateStdout() {
17
- if (!this.enabled) return () => void 0;
18
- const original = process.stdout.write;
19
- const redirect = ((chunk, encoding, callback) => process.stderr.write(chunk, encoding, callback));
20
- process.stdout.write = redirect;
21
- return () => {
22
- if (process.stdout.write === redirect) process.stdout.write = original;
23
- };
24
- }
25
- emit(event, fields = {}) {
26
- if (!this.enabled || this.completed) return;
27
- this.writeLine(JSON.stringify({
28
- schemaVersion: 1,
29
- command: this.command,
30
- event,
31
- ...fields,
32
- ts: (/* @__PURE__ */ new Date()).toISOString()
33
- }));
34
- }
35
- phase(phase, fields = {}) {
36
- recordCommandStage(phase);
37
- this.emit("phase", { phase, ...fields });
38
- }
39
- node(nodeId, action, status) {
40
- if (status === "running") recordCommandStage(`${nodeId}: ${action}`);
41
- this.emit("node", { nodeId, action, status });
42
- }
43
- mutation(mutation) {
44
- this.emit("mutation", { mutation });
45
- }
46
- recovery(code) {
47
- this.emit("recovery", { code });
48
- }
49
- error(error) {
50
- this.emit("error", { error });
51
- }
52
- complete(status, exitCode, fields = {}) {
53
- if (!this.enabled || this.completed) return;
54
- this.emit("complete", { status, exitCode, ...fields });
55
- this.completed = true;
56
- }
57
- }
58
- export {
59
- JsonStreamWriter
60
- };
@@ -1,19 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- function resolveLeafInvoke(command, args) {
4
- if (command.endsWith(".sh")) return { bin: "bash", args: [command, ...args] };
5
- return { bin: command, args };
6
- }
7
- function missingShellLeafMessage(leafPath) {
8
- const leaf = path.basename(leafPath);
9
- return `leaf could not start: ${leaf} (ENOENT)
10
- Next: reinstall mm-harness (npm i -g @deeeed/metamask-harness) \u2014 the shell leaf is missing or not executable`;
11
- }
12
- function shellLeafMissing(leafPath) {
13
- return leafPath.endsWith(".sh") && !fs.existsSync(leafPath);
14
- }
15
- export {
16
- missingShellLeafMessage,
17
- resolveLeafInvoke,
18
- shellLeafMissing
19
- };