@deeeed/metamask-harness 0.17.5 → 0.19.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 (169) hide show
  1. package/CHANGELOG.md +1059 -0
  2. package/README.md +91 -140
  3. package/adapters/core/inject.sh +1 -6
  4. package/adapters/extension/console-tail.mjs +55 -24
  5. package/adapters/extension/ensure-browser.sh +6 -1
  6. package/adapters/extension/inject.mjs +7 -13
  7. package/adapters/extension/launch-browser.cjs +7 -1
  8. package/adapters/extension/launch-webpack.cjs +83 -0
  9. package/adapters/extension/launch.sh +8 -11
  10. package/adapters/extension/lib/chrome-args.cjs +8 -1
  11. package/adapters/extension/lib/macos-focus.cjs +32 -0
  12. package/adapters/extension/live.sh +12 -21
  13. package/adapters/extension/reattach.sh +1 -0
  14. package/adapters/extension/seed-fixture.sh +4 -12
  15. package/adapters/extension/sidepanel-toggle.sh +4 -1
  16. package/adapters/extension/stamp-runtime-title.cjs +58 -0
  17. package/adapters/extension/start-watch.sh +17 -4
  18. package/adapters/extension/stop-viewers.sh +1 -1
  19. package/adapters/extension/sync-webpack-dist.cjs +107 -0
  20. package/adapters/extension/verify.sh +2 -2
  21. package/adapters/manifest.json +53 -37
  22. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +16 -0
  23. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
  24. package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +3 -1
  25. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
  26. package/adapters/mobile/cleanup.sh +16 -176
  27. package/adapters/mobile/inject.sh +91 -671
  28. package/adapters/mobile/launch-console-forwarder.cjs +39 -0
  29. package/adapters/mobile/open-device.sh +45 -7
  30. package/adapters/mobile/start-console-forwarder.sh +70 -0
  31. package/adapters/mobile/start-metro.sh +0 -41
  32. package/adapters/mobile/verify.sh +34 -83
  33. package/adapters/mobile/wait-for-bridge.sh +22 -8
  34. package/adapters/shared/harness-source-fingerprint.mjs +14 -12
  35. package/adapters/shared/resolve-slot-ports-core.mjs +17 -6
  36. package/adapters/shared/resolve-slot-ports.sh +17 -5
  37. package/adapters/shared/update-check-worker.mjs +43 -0
  38. package/bin/mm-harness +21 -2
  39. package/dist/adapters/core/surface.js +4 -1
  40. package/dist/adapters/extension/console-capture.js +95 -0
  41. package/dist/adapters/extension/product-config.js +110 -0
  42. package/dist/adapters/extension/runtime-decision.js +20 -2
  43. package/dist/adapters/extension/surface.js +22 -3
  44. package/dist/adapters/harness-freshness.js +49 -0
  45. package/dist/adapters/mobile/perps-env.js +43 -3
  46. package/dist/adapters/mobile/prepare.js +39 -7
  47. package/dist/adapters/mobile/surface.js +5 -2
  48. package/dist/adapters/resolve-slot-ports.js +2 -2
  49. package/dist/adapters/slot-ports.js +13 -16
  50. package/dist/adapters.js +56 -16
  51. package/dist/checkout-lock.js +27 -2
  52. package/dist/cli-color.js +19 -0
  53. package/dist/cli-commands.js +1 -1
  54. package/dist/cli.js +7 -18
  55. package/dist/command-contract.js +462 -0
  56. package/dist/command-journal.js +263 -0
  57. package/dist/commands/call.js +252 -57
  58. package/dist/commands/check.js +10 -4
  59. package/dist/commands/completion-candidates.js +17 -22
  60. package/dist/commands/device-target.js +27 -12
  61. package/dist/commands/doctor.js +106 -25
  62. package/dist/commands/fixtures.js +142 -35
  63. package/dist/commands/last.js +60 -0
  64. package/dist/commands/launch/extension.js +40 -17
  65. package/dist/commands/launch/index.js +207 -63
  66. package/dist/commands/launch/mobile.js +2 -0
  67. package/dist/commands/list-executables.js +187 -29
  68. package/dist/commands/logs.js +8 -6
  69. package/dist/commands/manifest.js +286 -35
  70. package/dist/commands/parse-args.js +68 -4
  71. package/dist/commands/provision.js +10 -4
  72. package/dist/commands/run-engine.js +772 -264
  73. package/dist/commands/run-report.js +12 -3
  74. package/dist/commands/run.js +378 -85
  75. package/dist/commands/shared.js +76 -4
  76. package/dist/commands/status-probe.js +4 -1
  77. package/dist/commands/status.js +2 -1
  78. package/dist/commands/stop.js +7 -2
  79. package/dist/commands/update.js +16 -28
  80. package/dist/completions-cache.js +1 -1
  81. package/dist/doctor.js +57 -10
  82. package/dist/harness.js +47 -137
  83. package/dist/heal-bounds.js +7 -2
  84. package/dist/json-stream.js +57 -0
  85. package/dist/live-adapter-contract.js +138 -32
  86. package/dist/manifest.js +161 -1
  87. package/dist/mm-harness-cli.js +146 -73
  88. package/dist/paths.js +2 -5
  89. package/dist/recipe-security.js +178 -0
  90. package/dist/run-diagnostics.js +261 -0
  91. package/dist/run-recording.js +1 -1
  92. package/dist/runner.js +140 -9
  93. package/docs/CONTRIBUTING.md +136 -0
  94. package/docs/QA.md +184 -0
  95. package/docs/RECIPES.md +122 -0
  96. package/docs/SECURITY.md +88 -0
  97. package/library/README.md +7 -3
  98. package/library/actions/core/perps/_controller.mjs +43 -56
  99. package/library/actions/core/perps/assert_orders.mjs +6 -7
  100. package/library/actions/core/perps/assert_positions.mjs +6 -7
  101. package/library/actions/core/perps/close_orders.mjs +2 -0
  102. package/library/actions/core/perps/close_positions.mjs +2 -0
  103. package/library/actions/core/perps/ensure_orders.mjs +4 -2
  104. package/library/actions/core/perps/ensure_positions.mjs +4 -2
  105. package/library/actions/core/perps/place_order.mjs +7 -3
  106. package/library/actions/core/perps/read_account.mjs +2 -2
  107. package/library/actions/core/perps/read_orders.mjs +2 -1
  108. package/library/actions/core/perps/read_positions.mjs +2 -1
  109. package/library/actions/core/wallet/list_accounts.mjs +95 -0
  110. package/library/actions/extension/perps/assert_orders.mjs +2 -1
  111. package/library/actions/extension/perps/assert_positions.mjs +2 -1
  112. package/library/actions/extension/perps/perps.mjs +43 -14
  113. package/library/actions/extension/platform/cdp.mjs +1 -0
  114. package/library/actions/extension/wallet/list_accounts.mjs +41 -0
  115. package/library/actions/mobile/perps/assert_orders.mjs +2 -1
  116. package/library/actions/mobile/perps/assert_positions.mjs +2 -1
  117. package/library/actions/mobile/perps/perps.mjs +40 -12
  118. package/library/actions/mobile/wallet/list_accounts.mjs +37 -0
  119. package/library/library.json +1 -1
  120. package/library/manifests/core.action-manifest.json +1214 -396
  121. package/library/manifests/extension.action-manifest.json +1635 -728
  122. package/library/manifests/mobile.action-manifest.json +1740 -727
  123. package/library/recipes/app/lifecycle.android-smoke.mobile.recipe.json +63 -81
  124. package/library/recipes/perps/clean-market-testnet.core.recipe.json +44 -0
  125. package/library/recipes/perps/clean-market-testnet.recipe.json +49 -0
  126. package/library/recipes/perps/lifecycle.recipe.json +136 -180
  127. package/library/recipes/perps/order-lifecycle.core.recipe.json +71 -67
  128. package/library/recipes/perps/performance.background-resume.mobile.recipe.json +51 -67
  129. package/library/recipes/perps/performance.cold-start.mobile.recipe.json +51 -67
  130. package/library/recipes/perps/performance.mobile.recipe.json +37 -51
  131. package/library/recipes/perps/performance.warm-start.mobile.recipe.json +44 -59
  132. package/library/recipes/perps/read-markets.core.recipe.json +29 -31
  133. package/library/recipes/perps/smoke.core.recipe.json +29 -32
  134. package/library/recipes/perps/smoke.extension.recipe.json +41 -44
  135. package/library/recipes/perps/smoke.mobile.recipe.json +42 -44
  136. package/library/recipes/perps/trading-lifecycle.core.recipe.json +69 -65
  137. package/library/recipes/runner/action-validation.extension.recipe.json +312 -398
  138. package/library/recipes/runner/action-validation.mobile.recipe.json +316 -402
  139. package/library/recipes/runner/smoke.core.recipe.json +25 -0
  140. package/library/recipes/runner/smoke.extension.recipe.json +23 -24
  141. package/library/recipes/runner/smoke.mobile.recipe.json +23 -24
  142. package/library/recipes/wallet/smoke.extension.recipe.json +40 -0
  143. package/library/recipes/wallet/smoke.mobile.recipe.json +40 -0
  144. package/package.json +12 -5
  145. package/scripts/completions.sh +8 -11
  146. package/scripts/validate-human-outcomes.mjs +169 -0
  147. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -185
  148. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -1662
  149. package/adapters/mobile/overlay/compat/README.md +0 -28
  150. package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +0 -42
  151. package/adapters/shared/resolve-farmslot-ports-core.mjs +0 -3
  152. package/adapters/shared/resolve-farmslot-ports.mjs +0 -5
  153. package/adapters/shared/resolve-farmslot-ports.sh +0 -7
  154. package/dist/adapters/extension/harness-freshness.js +0 -39
  155. package/dist/commands/flows.js +0 -62
  156. package/docs/ADAPTER-SURFACE.md +0 -119
  157. package/docs/CHEATSHEET.md +0 -61
  158. package/docs/CLI-SPEC.md +0 -1098
  159. package/docs/CODE-MAP.md +0 -62
  160. package/docs/DEBUG-HANDOVER.md +0 -36
  161. package/docs/MENTAL-MODEL.md +0 -295
  162. package/docs/UX-PRINCIPLES.md +0 -64
  163. package/docs/architecture.md +0 -398
  164. package/docs/live-adapter-contract.md +0 -188
  165. package/docs/package-boundaries.md +0 -47
  166. package/docs/perps-flow-catalog.md +0 -235
  167. package/docs/recipe-libraries.md +0 -301
  168. package/docs/runtime-file-conventions.md +0 -36
  169. package/library/flows/perps.flows.json +0 -64
@@ -0,0 +1,263 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { recipeRuntimeDir } from "./paths.js";
5
+ const COMMAND_JOURNAL_FILE = "last-command.json";
6
+ const JOURNALED_COMMANDS = /* @__PURE__ */ new Set([
7
+ "call",
8
+ "check",
9
+ "checklist",
10
+ "cleanup",
11
+ "doctor",
12
+ "fixtures",
13
+ "install",
14
+ "launch",
15
+ "provision",
16
+ "recipe-quality",
17
+ "run",
18
+ "stop",
19
+ "verify"
20
+ ]);
21
+ const SENSITIVE_KEY = /(?:api[-_]?key|auth|credential|mnemonic|pass(?:word)?|private[-_]?key|secret|seed|srp|token|vault)/iu;
22
+ const EVIDENCE_OPTIONS = /* @__PURE__ */ new Set(["--artifacts-dir", "--out", "--output"]);
23
+ let activeCommandJournal;
24
+ async function withCommandJournal(command, argv, execute) {
25
+ if (!JOURNALED_COMMANDS.has(command)) return execute();
26
+ const handle = tryBeginCommandJournal(command, argv);
27
+ const previous = activeCommandJournal;
28
+ activeCommandJournal = handle;
29
+ try {
30
+ const exitCode = await execute();
31
+ tryFinishCommandJournal(handle, exitCode);
32
+ return exitCode;
33
+ } catch (error) {
34
+ tryFinishCommandJournal(handle, 1);
35
+ throw error;
36
+ } finally {
37
+ activeCommandJournal = previous;
38
+ }
39
+ }
40
+ function recordCommandEvidence(...evidence) {
41
+ const handle = activeCommandJournal;
42
+ if (!handle || evidence.length === 0) return;
43
+ try {
44
+ const paths = new Set(handle.record.evidencePaths);
45
+ for (const entry of evidence) paths.add(path.resolve(entry));
46
+ handle.record = { ...handle.record, evidencePaths: [...paths] };
47
+ writeAtomic(handle.file, handle.record);
48
+ } catch (error) {
49
+ process.stderr.write(
50
+ `mm-harness: resumability journal could not record evidence: ${errorMessage(error)}
51
+ `
52
+ );
53
+ }
54
+ }
55
+ function commandJournalPath(target, runtimeDir) {
56
+ const relative = runtimeDir ?? recipeRuntimeDir();
57
+ assertRelativeRuntimeDir(relative);
58
+ return path.join(path.resolve(target), relative, COMMAND_JOURNAL_FILE);
59
+ }
60
+ function readCommandJournal(target, runtimeDir) {
61
+ const file = commandJournalPath(target, runtimeDir);
62
+ let descriptor;
63
+ try {
64
+ const noFollow = typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0;
65
+ descriptor = fs.openSync(file, fs.constants.O_RDONLY | noFollow);
66
+ const descriptorStat = fs.fstatSync(descriptor, { bigint: true });
67
+ const pathStat = fs.lstatSync(file, { bigint: true });
68
+ if (!descriptorStat.isFile() || !pathStat.isFile() || pathStat.isSymbolicLink() || pathStat.dev === 0n && pathStat.ino === 0n || descriptorStat.dev !== pathStat.dev || descriptorStat.ino !== pathStat.ino) {
69
+ return { file, record: null };
70
+ }
71
+ const parsed = JSON.parse(
72
+ fs.readFileSync(descriptor, "utf8")
73
+ );
74
+ return isCommandJournalRecord(parsed) ? { file, record: parsed } : { file, record: null };
75
+ } catch {
76
+ return { file, record: null };
77
+ } finally {
78
+ if (descriptor !== void 0) fs.closeSync(descriptor);
79
+ }
80
+ }
81
+ function redactCommandArgs(argv) {
82
+ const redacted = [];
83
+ for (let index = 0; index < argv.length; index += 1) {
84
+ const token = argv[index] ?? "";
85
+ const equals = token.indexOf("=");
86
+ if (token.startsWith("--") && equals !== -1) {
87
+ const option = token.slice(0, equals);
88
+ const value = token.slice(equals + 1);
89
+ redacted.push(
90
+ `${option}=${option === "--arg" ? redactAssignment(value) : SENSITIVE_KEY.test(option) ? "<redacted>" : redactUrl(value)}`
91
+ );
92
+ continue;
93
+ }
94
+ if (token.startsWith("--") && SENSITIVE_KEY.test(token)) {
95
+ redacted.push(token);
96
+ const value = argv[index + 1];
97
+ if (value !== void 0 && !value.startsWith("--")) {
98
+ redacted.push("<redacted>");
99
+ index += 1;
100
+ }
101
+ continue;
102
+ }
103
+ if (token === "--arg") {
104
+ redacted.push(token);
105
+ const value = argv[index + 1];
106
+ if (value !== void 0) {
107
+ redacted.push(redactAssignment(value));
108
+ index += 1;
109
+ }
110
+ continue;
111
+ }
112
+ redacted.push(redactAssignment(redactUrl(token)));
113
+ }
114
+ return redacted;
115
+ }
116
+ function tryBeginCommandJournal(command, argv) {
117
+ try {
118
+ const target = invocationTarget(argv);
119
+ if (!fs.existsSync(target)) return void 0;
120
+ const runtimeDir = optionValue(argv, "--runtime-dir");
121
+ const file = commandJournalPath(target, runtimeDir);
122
+ const now = (/* @__PURE__ */ new Date()).toISOString();
123
+ const record = {
124
+ schemaVersion: 1,
125
+ command,
126
+ args: redactCommandArgs(argv.slice(1)),
127
+ target,
128
+ verdict: "running",
129
+ exitCode: null,
130
+ evidencePaths: evidencePaths(argv),
131
+ startedAt: now,
132
+ finishedAt: null
133
+ };
134
+ writeAtomic(file, record);
135
+ return { file, record };
136
+ } catch (error) {
137
+ process.stderr.write(
138
+ `mm-harness: resumability journal unavailable: ${errorMessage(error)}
139
+ `
140
+ );
141
+ return void 0;
142
+ }
143
+ }
144
+ function tryFinishCommandJournal(handle, exitCode) {
145
+ if (!handle) return;
146
+ try {
147
+ writeAtomic(handle.file, {
148
+ ...handle.record,
149
+ verdict: exitCode === 0 ? "pass" : "fail",
150
+ exitCode,
151
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString()
152
+ });
153
+ } catch (error) {
154
+ process.stderr.write(
155
+ `mm-harness: resumability journal could not finalize: ${errorMessage(error)}
156
+ `
157
+ );
158
+ }
159
+ }
160
+ function writeAtomic(file, record) {
161
+ fs.mkdirSync(path.dirname(file), { recursive: true });
162
+ const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
163
+ let descriptor;
164
+ try {
165
+ descriptor = fs.openSync(temporary, "wx", 384);
166
+ fs.writeFileSync(descriptor, `${JSON.stringify(record, null, 2)}
167
+ `);
168
+ fs.fsyncSync(descriptor);
169
+ fs.closeSync(descriptor);
170
+ descriptor = void 0;
171
+ fs.renameSync(temporary, file);
172
+ } finally {
173
+ if (descriptor !== void 0) fs.closeSync(descriptor);
174
+ fs.rmSync(temporary, { force: true });
175
+ }
176
+ }
177
+ function invocationTarget(argv) {
178
+ return path.resolve(
179
+ optionValue(argv, "--target") ?? process.cwd()
180
+ );
181
+ }
182
+ function evidencePaths(argv) {
183
+ const values = /* @__PURE__ */ new Set();
184
+ for (const option of EVIDENCE_OPTIONS) {
185
+ const value = optionValue(argv, option);
186
+ if (value) values.add(path.resolve(value));
187
+ }
188
+ return [...values];
189
+ }
190
+ function optionValue(argv, option) {
191
+ const divider = argv.indexOf("--");
192
+ const end = divider === -1 ? argv.length : divider;
193
+ for (let index = 1; index < end; index += 1) {
194
+ const token = argv[index] ?? "";
195
+ if (token === option) {
196
+ const value = argv[index + 1];
197
+ return value && !value.startsWith("--") ? value : void 0;
198
+ }
199
+ if (token.startsWith(`${option}=`)) return token.slice(option.length + 1);
200
+ }
201
+ return void 0;
202
+ }
203
+ function redactAssignment(token) {
204
+ const equals = token.indexOf("=");
205
+ if (equals <= 0) return redactValue(token);
206
+ const key = token.slice(0, equals);
207
+ const value = token.slice(equals + 1);
208
+ return `${key}=${SENSITIVE_KEY.test(key) ? "<redacted>" : redactValue(value)}`;
209
+ }
210
+ function redactUrl(token) {
211
+ return token.replace(/(\w+:\/\/)[^/@\s:]+:[^/@\s]+@/gu, "$1<redacted>@");
212
+ }
213
+ function redactValue(value) {
214
+ try {
215
+ return JSON.stringify(redactStructuredValue(JSON.parse(value)));
216
+ } catch {
217
+ return value.replace(
218
+ /((?:api[-_]?key|auth|credential|mnemonic|pass(?:word)?|private[-_]?key|secret|seed|srp|token|vault)=)[^\s]+/giu,
219
+ "$1<redacted>"
220
+ );
221
+ }
222
+ }
223
+ function isSensitiveKey(key) {
224
+ return SENSITIVE_KEY.test(key);
225
+ }
226
+ function redactStructuredValue(value) {
227
+ if (Array.isArray(value)) return value.map(redactStructuredValue);
228
+ if (!value || typeof value !== "object") return value;
229
+ return Object.fromEntries(
230
+ Object.entries(value).map(([key, entry]) => [
231
+ key,
232
+ isSensitiveKey(key) ? "<redacted>" : redactStructuredValue(entry)
233
+ ])
234
+ );
235
+ }
236
+ function assertRelativeRuntimeDir(value) {
237
+ if (!value || path.isAbsolute(value) || !/^[A-Za-z0-9._/-]+$/u.test(value)) {
238
+ throw new Error(`--runtime-dir must be a safe relative path: ${value}`);
239
+ }
240
+ if (value.split("/").some((part) => !part || part === "." || part === "..")) {
241
+ throw new Error(
242
+ `--runtime-dir contains an unsafe path component: ${value}`
243
+ );
244
+ }
245
+ }
246
+ function isCommandJournalRecord(value) {
247
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
248
+ const record = value;
249
+ return record.schemaVersion === 1 && typeof record.command === "string" && Array.isArray(record.args) && record.args.every((entry) => typeof entry === "string") && typeof record.target === "string" && (record.verdict === "running" || record.verdict === "pass" || record.verdict === "fail") && (record.exitCode === null || typeof record.exitCode === "number") && Array.isArray(record.evidencePaths) && record.evidencePaths.every((entry) => typeof entry === "string") && typeof record.startedAt === "string" && (record.finishedAt === null || typeof record.finishedAt === "string");
250
+ }
251
+ function errorMessage(error) {
252
+ return error instanceof Error ? error.message : String(error);
253
+ }
254
+ export {
255
+ COMMAND_JOURNAL_FILE,
256
+ commandJournalPath,
257
+ isSensitiveKey,
258
+ readCommandJournal,
259
+ recordCommandEvidence,
260
+ redactCommandArgs,
261
+ redactStructuredValue,
262
+ withCommandJournal
263
+ };