@deeeed/metamask-harness 0.70.1 → 0.71.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 (73) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/adapters/extension/console-tail.mjs +38 -6
  3. package/adapters/manifest.json +128 -0
  4. package/adapters/terminal/cleanup.mjs +68 -0
  5. package/adapters/terminal/inject.mjs +74 -0
  6. package/adapters/terminal/launch.mjs +628 -0
  7. package/adapters/terminal/lib/cdp-client.mjs +84 -0
  8. package/adapters/terminal/lib/display.mjs +115 -0
  9. package/adapters/terminal/lib/hud.mjs +148 -0
  10. package/adapters/terminal/lib/mainnet-guard.mjs +177 -0
  11. package/adapters/terminal/lib/origin.mjs +30 -0
  12. package/adapters/terminal/lib/page-script.mjs +246 -0
  13. package/adapters/terminal/lib/processes.mjs +187 -0
  14. package/adapters/terminal/lib/readiness.mjs +274 -0
  15. package/adapters/terminal/lib/strict-wallet.mjs +138 -0
  16. package/adapters/terminal/stop.mjs +37 -0
  17. package/adapters/terminal/verify.mjs +48 -0
  18. package/adapters/terminal/wallet-host.mjs +703 -0
  19. package/dist/adapters/console-capture.js +122 -0
  20. package/dist/adapters/extension/console-capture.js +22 -85
  21. package/dist/adapters/surface.js +3 -1
  22. package/dist/adapters/terminal/console-capture.js +46 -0
  23. package/dist/adapters/terminal/stop.js +19 -0
  24. package/dist/adapters/terminal/surface.js +104 -0
  25. package/dist/adapters.js +27 -4
  26. package/dist/cli.js +2 -2
  27. package/dist/command-contract.js +12 -4
  28. package/dist/commands/check.js +1 -1
  29. package/dist/commands/config.js +1 -1
  30. package/dist/commands/doctor.js +24 -1
  31. package/dist/commands/domain.js +1 -1
  32. package/dist/commands/farmslot-ready.js +2 -0
  33. package/dist/commands/help.js +2 -2
  34. package/dist/commands/launch/index.js +97 -2
  35. package/dist/commands/manifest.js +3 -3
  36. package/dist/commands/recipe-advice.js +1 -1
  37. package/dist/commands/review.js +1 -1
  38. package/dist/commands/run-engine.js +1 -1
  39. package/dist/commands/shared.js +1 -1
  40. package/dist/doctor.js +3 -0
  41. package/dist/harness.js +17 -11
  42. package/dist/manifest.js +5 -0
  43. package/dist/mm-harness-cli.js +34 -20
  44. package/dist/paths.js +2 -2
  45. package/dist/recipe-security.js +12 -3
  46. package/dist/review/knowledge.js +1 -1
  47. package/dist/run-diagnostics.js +150 -33
  48. package/dist/runner.js +1 -1
  49. package/dist/runtime-context.js +3 -3
  50. package/docs/CONTRIBUTING.md +1 -1
  51. package/library/actions/shared/console-allowlist-add.mjs +137 -0
  52. package/library/actions/shared/console-collector.mjs +230 -0
  53. package/library/actions/shared/console-findings.mjs +511 -0
  54. package/library/actions/terminal/app/assert_no_console_errors.mjs +119 -0
  55. package/library/actions/terminal/app/launch.mjs +45 -0
  56. package/library/actions/terminal/perps/_venue.mjs +242 -0
  57. package/library/actions/terminal/perps/assert_orders.mjs +54 -0
  58. package/library/actions/terminal/perps/assert_positions.mjs +40 -0
  59. package/library/actions/terminal/perps/read_orders.mjs +26 -0
  60. package/library/actions/terminal/perps/read_positions.mjs +25 -0
  61. package/library/actions/terminal/perps/read_snapshot.mjs +40 -0
  62. package/library/actions/terminal/perps/revoke_agent.mjs +58 -0
  63. package/library/actions/terminal/perps/teardown_state.mjs +104 -0
  64. package/library/actions/terminal/platform/hud.mjs +71 -0
  65. package/library/actions/terminal/platform/page.mjs +247 -0
  66. package/library/actions/terminal/platform/runtime.mjs +200 -0
  67. package/library/actions/terminal/ui/navigate.mjs +31 -0
  68. package/library/actions/terminal/wallet/_signature-log.mjs +177 -0
  69. package/library/actions/terminal/wallet/assert_signatures.mjs +43 -0
  70. package/library/actions/terminal/wallet/list_accounts.mjs +27 -0
  71. package/library/actions/terminal/wallet/read_signatures.mjs +37 -0
  72. package/library/manifests/terminal.action-manifest.json +1786 -0
  73. package/package.json +1 -1
@@ -59,7 +59,10 @@ const LAUNCH_BOOLEANS = /* @__PURE__ */ new Set([
59
59
  "runway",
60
60
  "clearMetro",
61
61
  "json",
62
- "jsonStream"
62
+ "jsonStream",
63
+ "freshProfile",
64
+ "headless",
65
+ "headful"
63
66
  ]);
64
67
  const DEFAULT_EXTENSION_DAPP_URL = "https://metamask.github.io/test-dapp/";
65
68
  function metroLaunchEvidence(adapter, target) {
@@ -117,6 +120,83 @@ async function handleLaunch(argv) {
117
120
  restoreStdout();
118
121
  }
119
122
  }
123
+ const TERMINAL_ONLY_LAUNCH_OPTIONS = Object.freeze({
124
+ signer: "--signer",
125
+ account: "--account",
126
+ freshProfile: "--fresh-profile",
127
+ headless: "--headless",
128
+ headful: "--headful",
129
+ slowMo: "--slow-mo",
130
+ network: "--network",
131
+ mainnetConfirmation: "--mainnet-confirmation"
132
+ });
133
+ function terminalOnlyLaunchFlags(options) {
134
+ return Object.entries(TERMINAL_ONLY_LAUNCH_OPTIONS).filter(([key]) => options[key] !== void 0 && options[key] !== false).map(([, name]) => name);
135
+ }
136
+ function parseTerminalLauncherOutput(output) {
137
+ const text = output.trim();
138
+ if (!text) return { status: "fail", error: "the terminal launcher printed no summary" };
139
+ try {
140
+ return JSON.parse(text);
141
+ } catch {
142
+ const last = text.split("\n").filter((line) => line.trim()).pop() ?? "";
143
+ try {
144
+ return JSON.parse(last);
145
+ } catch {
146
+ return { status: "fail", error: `the terminal launcher printed no JSON summary: ${last.slice(0, 200)}` };
147
+ }
148
+ }
149
+ }
150
+ function launchTerminal(options, target, jsonOutput, stream) {
151
+ const cdpPort = str(options, "cdpPort") ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
152
+ const appPort = str(options, "watcherPort") ?? process.env.TERMINAL_APP_PORT ?? process.env.RECIPE_WATCHER_PORT ?? process.env.WATCHER_PORT;
153
+ if (!cdpPort || !appPort) {
154
+ return launchUsage(
155
+ jsonOutput,
156
+ stream,
157
+ "terminal launch needs the slot browser CDP port and the slot dev-server port.",
158
+ "mm-harness launch --adapter terminal --cdp-port <port> --watcher-port <app port>"
159
+ );
160
+ }
161
+ const args = [
162
+ path.join(runnerDir, "adapters", "terminal", "launch.mjs"),
163
+ "--target",
164
+ target,
165
+ "--cdp-port",
166
+ cdpPort,
167
+ "--app-port",
168
+ appPort,
169
+ "--signer",
170
+ str(options, "signer") ?? process.env.TERMINAL_SIGNER ?? "extension",
171
+ "--account",
172
+ str(options, "account") ?? process.env.TERMINAL_ACCOUNT ?? "dev1",
173
+ "--json"
174
+ ];
175
+ if (flag(options, "freshProfile")) args.push("--fresh-profile");
176
+ if (flag(options, "headless")) args.push("--headless");
177
+ if (flag(options, "headful")) args.push("--headful");
178
+ const slowMo = str(options, "slowMo");
179
+ if (slowMo) args.push("--slow-mo", slowMo);
180
+ const network = str(options, "network");
181
+ if (network) args.push("--network", network);
182
+ const mainnetConfirmation = str(options, "mainnetConfirmation");
183
+ if (mainnetConfirmation) args.push("--mainnet-confirmation", mainnetConfirmation);
184
+ stream.phase("launch", { adapter: "terminal", cdpPort, appPort });
185
+ let output = "";
186
+ let status = 0;
187
+ try {
188
+ output = execFileSync(process.execPath, args, { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
189
+ } catch (error) {
190
+ status = 1;
191
+ output = String(error.stdout ?? "");
192
+ }
193
+ const result = parseTerminalLauncherOutput(output);
194
+ if (jsonOutput) console.log(JSON.stringify({ schemaVersion: 1, command: "launch", adapter: "terminal", target, ...result }, null, 2));
195
+ else if (result.status === "pass") console.log(`\u2713 terminal browser ${result.reused ? "reused" : "started"} on CDP ${result.cdpPort} (${result.signer}, ${result.account?.name})`);
196
+ else console.error(`\u2717 terminal launch failed: ${result.error ?? "unknown error"}`);
197
+ stream.complete(result.status === "pass" ? "pass" : "fail", status);
198
+ return result.status === "pass" ? 0 : 1;
199
+ }
120
200
  async function handleLaunchLocked(argv, stream) {
121
201
  const { positional, options } = parseFlags(argv, LAUNCH_BOOLEANS);
122
202
  const json = flag(options, "json");
@@ -139,6 +219,18 @@ async function handleLaunchLocked(argv, stream) {
139
219
  if (adapter === "core") {
140
220
  return launchUsage(jsonOutput, stream, "core is headless; there is nothing to launch.", "mm-harness verify");
141
221
  }
222
+ if (adapter === "terminal") {
223
+ return launchTerminal(options, target, jsonOutput, stream);
224
+ }
225
+ const terminalOnly = terminalOnlyLaunchFlags(options);
226
+ if (terminalOnly.length > 0) {
227
+ return launchUsage(
228
+ jsonOutput,
229
+ stream,
230
+ `${terminalOnly.join(", ")} apply only to --adapter terminal; ${adapter} does not read them.`,
231
+ "drop the terminal flags, or pass --adapter terminal"
232
+ );
233
+ }
142
234
  if (adapter === "mobile" && !mobileTarget) {
143
235
  return launchUsage(
144
236
  jsonOutput,
@@ -943,5 +1035,8 @@ ${failure.originalError}` : "") + `
943
1035
  return failure.exitCode;
944
1036
  }
945
1037
  export {
946
- handleLaunch
1038
+ TERMINAL_ONLY_LAUNCH_OPTIONS,
1039
+ handleLaunch,
1040
+ parseTerminalLauncherOutput,
1041
+ terminalOnlyLaunchFlags
947
1042
  };
@@ -18,7 +18,7 @@ import {
18
18
  import { metaMaskActionExecutionCapabilities } from "../recipe-security.js";
19
19
  import { closest } from "../command-contract.js";
20
20
  const OFFICIAL_ACTIONS = new Set(OFFICIAL_RECIPE_ACTIONS);
21
- const ACTION_MATRIX_ADAPTERS = ["mobile", "extension", "core"];
21
+ const ACTION_MATRIX_ADAPTERS = ["mobile", "extension", "core", "terminal"];
22
22
  function actionLibraryContextArgs(librarySources) {
23
23
  return (librarySources ?? []).filter((source) => source.name !== "metamask").map((source) => {
24
24
  const entry = source.name ? `${source.name}=${source.root}` : source.root;
@@ -309,9 +309,9 @@ function renderHumanActionMatrix(actions) {
309
309
  return [
310
310
  out("bold", "action capability matrix"),
311
311
  out("comment", "Inspect one: mm-harness actions --matrix --action <name> --json"),
312
- `${"action capability".padEnd(width)} mobile extension core`,
312
+ `${"action capability".padEnd(width)} mobile extension core terminal`,
313
313
  ...actions.map(
314
- (action) => `${action.name.padEnd(width)} ${cell(action.support.mobile, 6)} ${cell(action.support.extension, 9)} ${cell(action.support.core, 4)}`
314
+ (action) => `${action.name.padEnd(width)} ${cell(action.support.mobile, 6)} ${cell(action.support.extension, 9)} ${cell(action.support.core, 4)} ${cell(action.support.terminal, 8)}`
315
315
  )
316
316
  ].join("\n");
317
317
  }
@@ -29,7 +29,7 @@ async function runRecipeAdvice(options, mode) {
29
29
  const adapter = optionString(options, "adapter");
30
30
  const output = optionString(options, "output");
31
31
  const evidence = optionString(options, "evidence");
32
- if (mode === "review" && !recipe || !task || !["mobile", "extension", "core"].includes(adapter ?? "")) {
32
+ if (mode === "review" && !recipe || !task || !["mobile", "extension", "core", "terminal"].includes(adapter ?? "")) {
33
33
  throw usageError("Use recipe-quality plan --task <task.json>, or advise --recipe <recipe.json> --task <TASK.md>, with --adapter <adapter> [--advisor <provider>].");
34
34
  }
35
35
  if (mode === "plan" && (recipe || evidence || optionString(options, "params"))) throw usageError("Planning runs before a recipe exists. Next: use advise to review a draft or evidence.");
@@ -27,7 +27,7 @@ function harnessVersion() {
27
27
  function adapterFromOptions(options, target) {
28
28
  const raw = optionString(options, "adapter") ?? optionString(options, "platform");
29
29
  if (raw !== void 0) {
30
- if (raw !== "mobile" && raw !== "extension" && raw !== "core") throw usageError(`--adapter must be mobile, extension, or core (got ${raw}).`);
30
+ if (raw !== "mobile" && raw !== "extension" && raw !== "core" && raw !== "terminal") throw usageError(`--adapter must be mobile, extension, core, or terminal (got ${raw}).`);
31
31
  return raw;
32
32
  }
33
33
  return detectAdapter(target) ?? "unscoped";
@@ -150,7 +150,7 @@ async function runRecipe(adapter, recipe, artifactsDir, projectRoot, actionManif
150
150
  if (executionError !== void 0) throw executionError;
151
151
  if (recordingError !== void 0) throw recordingError;
152
152
  if (!result) throw new Error("Recipe execution returned no result.");
153
- const finalized = finishRunDiagnostics(diagnosticBaseline, result);
153
+ const finalized = await finishRunDiagnostics(diagnosticBaseline, result);
154
154
  persistRunEffects(
155
155
  finalized.summaryPath,
156
156
  finalized.artifactManifestPath,
@@ -59,7 +59,7 @@ function flag(options, key) {
59
59
  function targetOf(options) {
60
60
  return path.resolve(str(options, "target") ?? process.cwd());
61
61
  }
62
- const ADAPTER_TOKENS = ["mobile", "extension", "core"];
62
+ const ADAPTER_TOKENS = ["mobile", "extension", "core", "terminal"];
63
63
  function resolveAdapter(options, target, hint) {
64
64
  const platform = str(options, "platform");
65
65
  const explicit = str(options, "adapter") ?? (platform === "ios" || platform === "android" ? "mobile" : platform);
package/dist/doctor.js CHANGED
@@ -45,6 +45,9 @@ function compatibilityMode(adapter, target) {
45
45
  if (adapter === "core") {
46
46
  return fs.existsSync(path.join(target, "packages/perps-controller/src/index.ts")) ? "headless controller (no bridge)" : "unsupported/no bridge";
47
47
  }
48
+ if (adapter === "terminal") {
49
+ return fs.existsSync(path.join(target, "src/features/perpetuals")) ? "slot browser over CDP (website; no product bridge)" : "unsupported/no bridge";
50
+ }
48
51
  if (adapter === "mobile") {
49
52
  if (shape.injectedHarness && shape.agenticService) return "runner bridge with injected app bridge";
50
53
  if (shape.agenticService) return "runner bridge with app bridge";
package/dist/harness.js CHANGED
@@ -7,11 +7,11 @@ import { resolveExtensionSlotPorts } from "./adapters/slot-ports.js";
7
7
  import { recipeHarnessPath, recipeRuntimeDir, runnerDir } from "./paths.js";
8
8
  import { colorHumanMessage } from "./cli-color.js";
9
9
  const HARNESS_ACTIONS = ["install", "verify", "cleanup"];
10
- const ADAPTERS = ["mobile", "extension", "core"];
10
+ const ADAPTERS = ["mobile", "extension", "core", "terminal"];
11
11
  function harnessUsage() {
12
12
  console.error(`mm-harness \u2014 install and validate the MetaMask recipe runtime in a checkout.
13
13
 
14
- Run it from inside a MetaMask checkout; the platform (mobile | extension | core)
14
+ Run it from inside a MetaMask checkout; the platform (mobile | extension | core | terminal)
15
15
  is auto-detected from the repo. Pass --platform only to override.
16
16
 
17
17
  Commands (one copy-pasteable example each):
@@ -23,7 +23,7 @@ Commands (one copy-pasteable example each):
23
23
  mm-harness cleanup
24
24
 
25
25
  Options:
26
- --platform <mobile|extension|core> Override auto-detection (alias: --adapter).
26
+ --platform <mobile|extension|core|terminal> Override auto-detection (alias: --adapter).
27
27
  --target <repo> Checkout to operate on (default: current dir).
28
28
  --json Machine-readable summary for agents/scripts.
29
29
  -- <args> Forward the rest verbatim to the underlying
@@ -67,7 +67,7 @@ function shellQuote(value) {
67
67
  return /^[A-Za-z0-9_./:=@+-]+$/u.test(value) ? value : `'${value.replace(/'/gu, `'\\''`)}'`;
68
68
  }
69
69
  function isAdapter(value) {
70
- return value === "mobile" || value === "extension" || value === "core";
70
+ return value === "mobile" || value === "extension" || value === "core" || value === "terminal";
71
71
  }
72
72
  function parseHarnessArgs(args) {
73
73
  const forward = [];
@@ -125,7 +125,7 @@ function parseHarnessArgs(args) {
125
125
  }
126
126
  return { adapter, json, quiet, forward };
127
127
  }
128
- const ADAPTER_DETECT_NEXT = "cd into a MetaMask checkout or pass --target <path>, or force it with --adapter <mobile|extension|core>";
128
+ const ADAPTER_DETECT_NEXT = "cd into a MetaMask checkout or pass --target <path>, or force it with --adapter <mobile|extension|core|terminal>";
129
129
  function detectAdapter(target) {
130
130
  let remote = "";
131
131
  try {
@@ -149,6 +149,7 @@ function detectAdapter(target) {
149
149
  if (exists("development/skills-sync.ts") || isDir("ui") && isDir("app/scripts")) return "extension";
150
150
  if (exists("scripts/skills-sync.mts") || isDir("ios") && isDir("android") && isDir("app/core")) return "mobile";
151
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";
152
153
  return void 0;
153
154
  }
154
155
  function resolveRuntimeContextPath(target) {
@@ -227,29 +228,34 @@ function isExecutable(file) {
227
228
  const INJECT_CANDIDATES = {
228
229
  mobile: { entry: "adapters/mobile/inject.sh", fallback: "scripts/inject-mobile-harness.sh" },
229
230
  extension: { entry: "adapters/extension/inject.mjs", fallback: "scripts/inject-extension-harness.mjs" },
230
- core: { entry: "adapters/core/inject.sh", fallback: "scripts/inject-core-harness.sh" }
231
+ core: { entry: "adapters/core/inject.sh", fallback: "scripts/inject-core-harness.sh" },
232
+ terminal: { entry: "adapters/terminal/inject.mjs", fallback: "adapters/terminal/inject.mjs" }
231
233
  };
232
234
  const CLEANUP_CANDIDATES = {
233
235
  mobile: { entry: "adapters/mobile/cleanup.sh", fallback: "scripts/cleanup-mobile-harness.sh" },
234
236
  extension: { entry: "adapters/extension/cleanup.mjs", fallback: "scripts/cleanup-extension-harness.mjs" },
235
- core: { entry: "adapters/core/cleanup.sh", fallback: "scripts/cleanup-core-harness.sh" }
237
+ core: { entry: "adapters/core/cleanup.sh", fallback: "scripts/cleanup-core-harness.sh" },
238
+ terminal: { entry: "adapters/terminal/cleanup.mjs", fallback: "adapters/terminal/cleanup.mjs" }
236
239
  };
237
240
  function resolveHarnessDispatch(adapter, action, target) {
238
241
  if (action === "install") {
239
242
  const { entry, fallback } = INJECT_CANDIDATES[adapter];
240
- if (adapter === "extension") {
243
+ if (adapter === "extension" || adapter === "terminal") {
241
244
  return { command: process.execPath, prefixArgs: [resolveEntry(runnerDir, [entry, fallback], "file")] };
242
245
  }
243
246
  return { command: resolveEntry(runnerDir, [entry, fallback], "file"), prefixArgs: [] };
244
247
  }
245
248
  if (action === "cleanup") {
246
249
  const { entry, fallback } = CLEANUP_CANDIDATES[adapter];
247
- const base = installedRunnerSource(target, adapter) ?? runnerDir;
248
- if (adapter === "extension") {
250
+ const base = adapter === "terminal" ? runnerDir : installedRunnerSource(target, adapter) ?? runnerDir;
251
+ if (adapter === "extension" || adapter === "terminal") {
249
252
  return { command: process.execPath, prefixArgs: [resolveEntry(base, [entry, fallback], "file")] };
250
253
  }
251
254
  return { command: resolveEntry(base, [entry, fallback], "file"), prefixArgs: [] };
252
255
  }
256
+ if (adapter === "terminal" && action === "verify") {
257
+ return { command: process.execPath, prefixArgs: [path.join(runnerDir, "adapters", "terminal", "verify.mjs")] };
258
+ }
253
259
  if (adapter === "core" && action === "verify") {
254
260
  const delegate = path.join(recipeHarnessPath(target, "core"), "runner", "bin", "mm-harness");
255
261
  if (!isExecutable(delegate)) {
@@ -282,7 +288,7 @@ async function handleHarness(argv) {
282
288
  if (quiet) {
283
289
  return 2;
284
290
  } else if (json) {
285
- const detectError = adapter ? { code: "UNSUPPORTED_PLATFORM", message: `unsupported platform: ${adapter}`, userAction: "pass --adapter <mobile|extension|core> to specify a supported adapter" } : { code: "ADAPTER_DETECTION_FAILED", message: `could not detect the MetaMask repo type for ${target}`, userAction: ADAPTER_DETECT_NEXT };
291
+ 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 };
286
292
  console.log(harnessSummary(harnessAction, void 0, target, "fail", 2, false, detectError));
287
293
  } else {
288
294
  harnessUsage();
package/dist/manifest.js CHANGED
@@ -11,10 +11,14 @@ function loadMetaMaskExtensionActionManifest() {
11
11
  function loadMetaMaskCoreActionManifest() {
12
12
  return asActionManifest(readJson(manifestPath("core")));
13
13
  }
14
+ function loadMetaMaskTerminalActionManifest() {
15
+ return asActionManifest(readJson(manifestPath("terminal")));
16
+ }
14
17
  function loadActionManifest(adapter, overridePath) {
15
18
  if (overridePath) return asActionManifest(readJson(path.resolve(overridePath)));
16
19
  if (adapter === "mobile") return loadMetaMaskMobileActionManifest();
17
20
  if (adapter === "core") return loadMetaMaskCoreActionManifest();
21
+ if (adapter === "terminal") return loadMetaMaskTerminalActionManifest();
18
22
  return loadMetaMaskExtensionActionManifest();
19
23
  }
20
24
  async function resolveActionManifest(adapter, overridePath, librarySources, taskActionRoot) {
@@ -165,6 +169,7 @@ export {
165
169
  loadMetaMaskCoreActionManifest,
166
170
  loadMetaMaskExtensionActionManifest,
167
171
  loadMetaMaskMobileActionManifest,
172
+ loadMetaMaskTerminalActionManifest,
168
173
  resolveActionManifest,
169
174
  validateManifest
170
175
  };
@@ -32,7 +32,7 @@ const REAL = [
32
32
 
33
33
  Loads the complete recipe guide shipped with this harness.
34
34
 
35
- --adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
35
+ --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
36
36
  --target <path> Checkout path (default: cwd)
37
37
  --json Load the same guide as structured agent context
38
38
 
@@ -121,7 +121,7 @@ Example:
121
121
  helpText: `mm-harness config <list|path|get <key>|set <key> <value>|unset <key>> [--json]
122
122
 
123
123
  Stored outside every repo in ~/.mm-harness/config.json (override: MM_HARNESS_CONFIG).
124
- Keys: libraries.<name> (team library checkout), references.<mobile|extension|core>
124
+ Keys: libraries.<name> (team library checkout), references.<mobile|extension|core|terminal>
125
125
  (the checkout used as the parity reference), advisor.enabled (true/false),
126
126
  advisor.provider, advisor.model, advisor.stages (plan,draft,evidence). Advisor
127
127
  settings contain no credentials and default to disabled with planning only.
@@ -267,7 +267,7 @@ Example:
267
267
  refuses with the numbered choice when that is ambiguous.
268
268
 
269
269
  --target <path> Checkout path (default: cwd)
270
- --platform <mobile|extension|core> Force the platform (auto-detected inside a checkout)
270
+ --platform <mobile|extension|core|terminal> Force the platform (auto-detected inside a checkout)
271
271
  --mobile-platform <ios|android> Mobile target when status cannot decide it
272
272
  --clear-metro Mobile only: clear Metro's transform cache in the launch step
273
273
  --artifacts-dir <dir> Where sandbox.json and the per-step reports land
@@ -378,10 +378,10 @@ Materialize:
378
378
 
379
379
  query Search names, categories, fields, and descriptions (typo-tolerant)
380
380
  --action <name> Describe one action; fuzzy-resolves like call (short or full name)
381
- --matrix Compare action availability across Mobile, Extension, and Core
381
+ --matrix Compare action availability across Mobile, Extension, Core, and Terminal
382
382
  --categories List compact action categories and counts
383
383
  --category <name> List only one category (for example ui, wallet, or perps)
384
- --adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
384
+ --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
385
385
  --target <path> Checkout path (default: cwd)
386
386
  --raw Dump raw action registry JSON
387
387
  --json Machine-readable output
@@ -409,7 +409,7 @@ Example:
409
409
  core headless \u2014 no dev server to stop (teaching error)
410
410
 
411
411
  --port <port> Metro/webpack port override; Extension CDP remains checkout-context scoped
412
- --adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
412
+ --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
413
413
  --target <path> Checkout path (default: cwd)
414
414
  --json Machine-readable output
415
415
 
@@ -437,7 +437,7 @@ Example:
437
437
  --arg k=v Action field value (repeatable; equivalent to key=value shorthand)
438
438
  --device <udid|serial|name> Mobile only: target this device (sets IOS_SIMULATOR / ADB_SERIAL and updates agentic-runtime.json). Without it, >1 connected mobile device fails fast and lists them. Do not shut down other devices to force selection.
439
439
  --cdp-port <port> Extension only: bind the action to this live browser runtime.
440
- --adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
440
+ --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
441
441
  --target <path> Checkout path (default: cwd)
442
442
  --artifacts-dir <dir> Where to write evidence (default: temp dir)
443
443
  --action-manifest <path> Override the action manifest
@@ -477,7 +477,7 @@ Discovery:
477
477
 
478
478
  Common overrides:
479
479
  --device <udid|serial|name> Mobile only: target this device (sets IOS_SIMULATOR / ADB_SERIAL and updates agentic-runtime.json). Without it, >1 connected mobile device fails fast and lists them. Do not shut down other devices to force selection.
480
- --adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
480
+ --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
481
481
  --platform <ios|android> Mobile platform preference; use --adapter for the product adapter
482
482
  --target <path> Checkout path (default: cwd)
483
483
  --artifacts-dir <dir> Override evidence output (default: active task/artifacts, otherwise a checkout-local temp/recipe/runs/<run>)
@@ -490,7 +490,7 @@ Common overrides:
490
490
  --json Machine-readable output
491
491
  --json-stream Line-flushed JSONL progress + terminal event
492
492
  --record-video=full-run Record a video of the run
493
- --hud <auto|show|hide> Recipe HUD policy (auto shows normal Mobile recipes; hide is explicit)
493
+ --hud <auto|show|hide> Recipe HUD policy (auto shows normal Mobile recipes and a headful terminal browser; hide is explicit)
494
494
 
495
495
  Advanced integrations:
496
496
  runtime: --cdp-port, --watcher-port/--metro-port, --runtime-dir, --slot,
@@ -537,7 +537,7 @@ Example:
537
537
  --cdp-port <port> Extension CDP port for the liveness probe (env: CDP_PORT / RECIPE_CDP_PORT)
538
538
  --device <udid|serial|name> Mobile only: target this device (env: IOS_SIMULATOR / ADB_SERIAL). doctor reports the connected devices; it never gates on ambiguity.
539
539
  --all-devices Mobile only: show every connected device instead of the slot-scoped target
540
- --adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
540
+ --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
541
541
  --target <path> Checkout path (default: cwd)
542
542
  --runtime-dir <dir> Runtime dir containing agentic-runtime.json (relative to target)
543
543
  --json Machine-readable output (incompatible with --print-ready)
@@ -559,7 +559,7 @@ Example:
559
559
  when available. Does not launch an app or run a recipe.
560
560
 
561
561
  --target <path> Checkout path (default: cwd)
562
- --adapter <mobile|extension|core> Adapter label (auto-detected when possible)
562
+ --adapter <mobile|extension|core|terminal> Adapter label (auto-detected when possible)
563
563
  --base <ref> Diff base (default: PR base, then remote HEAD, then repo fallback)
564
564
  --profile <fast|full> Validation depth (default: fast)
565
565
  --fix Fix changed files, then validate them
@@ -596,11 +596,11 @@ Example:
596
596
  Optional advice:
597
597
  advisor-status --json
598
598
  Resolve saved settings and overrides without reading tasks or calling a model.
599
- plan --task <task.json> --adapter <mobile|extension|core> --advisor typesafe
599
+ plan --task <task.json> --adapter <mobile|extension|core|terminal> --advisor typesafe
600
600
  Assess each acceptance criterion before a recipe exists. Task JSON contains
601
601
  acceptanceCriteria: ["criterion", "..."] (1\u201320 entries), with optional title/description.
602
602
  Returns known candidate refs for reuse/composition, or an explicit discovery gap.
603
- advise --recipe <recipe.json> --task <TASK.md> --adapter <mobile|extension|core>
603
+ advise --recipe <recipe.json> --task <TASK.md> --adapter <mobile|extension|core|terminal>
604
604
  --advisor <provider|off> Override saved policy for this task (currently: typesafe)
605
605
  --advisor-model <model> Override the provider default model or alias
606
606
  --advisor-stages <csv> Select plan,draft,evidence (saved/default: plan)
@@ -635,7 +635,7 @@ Example:
635
635
  provisioning path only: artifact cache + simulator create + simctl install.
636
636
  JavaScript dependencies and Metro remain dispatch-time launch concerns.
637
637
 
638
- --adapter <mobile|extension|core> Target adapter (mobile supported; extension/core teach)
638
+ --adapter <mobile|extension|core|terminal> Target adapter (mobile supported; extension/core/terminal teach)
639
639
  --target <path> Slot checkout path (default: cwd)
640
640
  --platform <ios> Platform (default ios)
641
641
  --simulator <name|udid> Override agentic-runtime.json simulator (alias: --device)
@@ -669,7 +669,7 @@ Example:
669
669
  artifact cache + simulator create + simctl install. JavaScript dependencies
670
670
  and Metro remain dispatch-time launch concerns.
671
671
 
672
- --adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
672
+ --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
673
673
  --target <path> Checkout path (default: cwd)
674
674
  --runway Thin mobile Runway artifact install instead of overlay install
675
675
  --platform <ios> Runway platform (default ios)
@@ -698,7 +698,7 @@ Example:
698
698
  Extension live checks infer the checkout CDP port from runtime context,
699
699
  pool, or slot suffix when --cdp-port is omitted.
700
700
 
701
- --adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
701
+ --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
702
702
  --target <path> Checkout path (default: cwd)
703
703
  --json Machine-readable output
704
704
 
@@ -714,7 +714,7 @@ Example:
714
714
 
715
715
  Remove the installed overlay and restore the checkout.
716
716
 
717
- --adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
717
+ --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
718
718
  --target <path> Checkout path (default: cwd)
719
719
 
720
720
  Example:
@@ -730,6 +730,7 @@ Example:
730
730
  Launch the app \u2014 quick relaunch by default; --build for a full native/webpack build.
731
731
  Mobile: target is MANDATORY (ios | android). Extension: no target (--fullscreen default;
732
732
  --sidepanel to override). core is headless (teaching error \u2014 use verify/run).
733
+ Terminal: starts or reuses the slot browser (visible by default) against the slot dev server.
733
734
  Extension quick launch REUSES a healthy live runtime (reload in place, no rebuild);
734
735
  --build is the escape hatch that always clean-builds + relaunches a fresh runtime.
735
736
 
@@ -750,7 +751,19 @@ Example:
750
751
  --device <udid|name> Target simulator/device (env: IOS_SIMULATOR / ADB_SERIAL)
751
752
  --cdp-port <port> Extension CDP port (env: CDP_PORT / RECIPE_CDP_PORT)
752
753
  --watcher-port <port> Metro/webpack port (env: WATCHER_PORT / METRO_PORT)
753
- --adapter <mobile|extension> Target adapter (auto-detected inside a checkout)
754
+ --signer <extension|injected> Terminal: MetaMask extension (default) or the injected test wallet
755
+ --account <name> Terminal: wallet-fixture account (default dev1)
756
+ --fresh-profile Terminal: start from a new browser profile
757
+ --headless Terminal: hide the browser (env: TERMINAL_HEADLESS=1); default is a
758
+ visible window placed per slot (--headful is accepted as the default)
759
+ --slow-mo <ms> Terminal: pause before each UI action (env: TERMINAL_SLOW_MO; default 0)
760
+ --network <testnet|mainnet> Terminal: venue the slot dev server serves (default testnet)
761
+ --mainnet-confirmation <REAL_FUNDS> Terminal: required with --network mainnet (real funds)
762
+ Terminal flags are rejected for other adapters. The terminal launch
763
+ refuses unless the farm next dev server on --watcher-port forces
764
+ Hyperliquid testnet, or --network mainnet --mainnet-confirmation REAL_FUNDS
765
+ is passed (a recipe launch node carries network/mainnet_confirmation).
766
+ --adapter <mobile|extension|terminal> Target adapter (auto-detected inside a checkout)
754
767
  --target <path> Checkout path (default: cwd)
755
768
  --json Machine-readable summary (recovered[] / mutations[] / phase)
756
769
  --json-stream Line-flushed JSONL progress + terminal event
@@ -777,7 +790,7 @@ Example:
777
790
  --source <label> Log source per adapter \u2014 mobile: metro|app;
778
791
  extension: extension|dapp|webpack|watcher|rebuild.
779
792
  Core is headless (teaching error).
780
- --adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
793
+ --adapter <mobile|extension|core|terminal> Target adapter (auto-detected inside a checkout)
781
794
  --target <path> Checkout path (default: cwd)
782
795
  --json Machine-readable output
783
796
 
@@ -981,6 +994,7 @@ function adapterFromRuntimeContext(ctx) {
981
994
  if (raw === "mobile" || raw === "ios" || raw === "android") return "mobile";
982
995
  if (raw === "extension" || raw === "chrome-extension") return "extension";
983
996
  if (raw === "core") return "core";
997
+ if (raw === "terminal") return "terminal";
984
998
  return void 0;
985
999
  }
986
1000
  function detectedSlotLine(out) {
@@ -1010,7 +1024,7 @@ function groupedHelp() {
1010
1024
  const out = (style, text) => color(style, text, { stream: process.stdout });
1011
1025
  const lines = [];
1012
1026
  lines.push(`${out("bold", "mm-harness")} \u2014 one front door for the MetaMask recipe loop: launch the app, prove behavior, manage the runtime overlay.`);
1013
- lines.push("Run it from inside a MetaMask checkout; the platform (mobile | extension | core) is auto-detected.");
1027
+ lines.push("Run it from inside a MetaMask checkout; the platform (mobile | extension | core | terminal) is auto-detected.");
1014
1028
  lines.push(`Grammar: ${out("cmd", "mm-harness <command> [target] [flags]")} (target is a positional: ios | android; flags add agent depth; --json is the agent contract)`);
1015
1029
  if (process.env.MM_HARNESS_BIN) {
1016
1030
  lines.push("");
package/dist/paths.js CHANGED
@@ -88,8 +88,8 @@ function isProtocolRoot(candidate) {
88
88
  return fs.existsSync(path.join(candidate, "packages/recipe-harness/package.json")) && fs.existsSync(path.join(candidate, "packages/protocol/package.json"));
89
89
  }
90
90
  function assertAdapter(adapter) {
91
- if (adapter !== "mobile" && adapter !== "extension" && adapter !== "core") {
92
- throw new Error("Adapter must be mobile, extension, or core.");
91
+ if (adapter !== "mobile" && adapter !== "extension" && adapter !== "core" && adapter !== "terminal") {
92
+ throw new Error("Adapter must be mobile, extension, core, or terminal.");
93
93
  }
94
94
  }
95
95
  function manifestPath(adapter) {
@@ -27,7 +27,12 @@ const READ_ONLY_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
27
27
  // Collector reads are host-only; starting capture remains arbitrary-code
28
28
  // because it spawns an HTTP listener.
29
29
  "metamask.analytics.read_events",
30
- "metamask.analytics.assert_events"
30
+ "metamask.analytics.assert_events",
31
+ // Terminal wallet request log reads.
32
+ "metamask.wallet.read_signatures",
33
+ "metamask.wallet.assert_signatures",
34
+ // Terminal console capture read.
35
+ "metamask.app.assert_no_console_errors"
31
36
  ]);
32
37
  const APP_MUTATION_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
33
38
  "metamask.assets.verify_sorting",
@@ -72,7 +77,10 @@ const APP_MUTATION_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
72
77
  "metamask.perps.start_state",
73
78
  "metamask.perps.teardown_state",
74
79
  // Consent changes wallet state and is therefore an app mutation.
75
- "metamask.analytics.set_consent"
80
+ "metamask.analytics.set_consent",
81
+ // Terminal slot browser lifecycle and venue agent revocation.
82
+ "metamask.app.launch",
83
+ "metamask.perps.revoke_agent"
76
84
  ]);
77
85
  const EXTERNAL_MUTATION_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
78
86
  "metamask.swap_bridge.submit_transaction",
@@ -82,7 +90,8 @@ const EXTERNAL_MUTATION_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
82
90
  "metamask.perps.close_visible_position",
83
91
  "metamask.perps.edit_margin",
84
92
  "metamask.perps.edit_order",
85
- "metamask.perps.update_position_tpsl"
93
+ "metamask.perps.update_position_tpsl",
94
+ "metamask.perps.revoke_agent"
86
95
  ]);
87
96
  function metaMaskActionExecutionCapabilities(action) {
88
97
  const capabilities = [];
@@ -4,7 +4,7 @@ import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { detectAdapter } from "../harness.js";
6
6
  import { usageError } from "../commands/parse-args.js";
7
- const REVIEW_ADAPTERS = ["mobile", "extension", "core"];
7
+ const REVIEW_ADAPTERS = ["mobile", "extension", "core", "terminal"];
8
8
  const ADVISOR_STAGES = ["plan", "draft", "evidence"];
9
9
  function parseAdvisorSettings(value) {
10
10
  if (!value || typeof value !== "object" || Array.isArray(value)) throw usageError("advisor settings must be an object. Next: use mm-harness config set advisor.enabled false.");