@deeeed/metamask-harness 0.81.0 → 0.82.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 (71) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/adapters/manifest.json +51 -51
  3. package/adapters/{terminal → web-dapp}/cleanup.mjs +9 -9
  4. package/adapters/{terminal → web-dapp}/inject.mjs +14 -14
  5. package/adapters/{terminal → web-dapp}/launch.mjs +23 -24
  6. package/adapters/web-dapp/lib/blocked-hosts.mjs +24 -0
  7. package/adapters/{terminal → web-dapp}/lib/cdp-client.mjs +1 -1
  8. package/adapters/{terminal → web-dapp}/lib/display.mjs +1 -1
  9. package/adapters/{terminal → web-dapp}/lib/hud.mjs +1 -1
  10. package/adapters/web-dapp/lib/policy.mjs +79 -0
  11. package/adapters/{terminal → web-dapp}/lib/processes.mjs +8 -8
  12. package/adapters/{terminal → web-dapp}/lib/readiness.mjs +15 -25
  13. package/adapters/{terminal → web-dapp}/stop.mjs +5 -4
  14. package/adapters/{terminal → web-dapp}/verify.mjs +11 -15
  15. package/adapters/{terminal → web-dapp}/wallet-host.mjs +10 -11
  16. package/dist/adapters/extension/run.js +5 -28
  17. package/dist/adapters/plugin-surface.js +98 -0
  18. package/dist/adapters/surface.js +1 -1
  19. package/dist/adapters/{terminal → web-dapp}/actions.js +7 -7
  20. package/dist/adapters/web-dapp/console-capture.js +63 -0
  21. package/dist/adapters/web-dapp/doctor-checks.js +33 -0
  22. package/dist/adapters/{terminal → web-dapp}/launch.js +21 -22
  23. package/dist/adapters/web-dapp/overlay.js +21 -0
  24. package/dist/adapters/web-dapp/readiness.js +10 -0
  25. package/dist/adapters/{terminal → web-dapp}/run.js +15 -15
  26. package/dist/adapters/{terminal → web-dapp}/stop.js +5 -4
  27. package/dist/adapters/web-dapp/surface.js +149 -0
  28. package/dist/cli.js +3 -0
  29. package/dist/command-contract.js +29 -7
  30. package/dist/commands/check.js +2 -2
  31. package/dist/commands/doctor.js +19 -2
  32. package/dist/commands/farmslot-ready.js +1 -0
  33. package/dist/commands/help.js +1 -1
  34. package/dist/commands/launch/device.js +5 -5
  35. package/dist/commands/self-test.js +9 -5
  36. package/dist/engine.js +6 -16
  37. package/dist/manifest.js +48 -24
  38. package/dist/mm-harness-cli.js +17 -0
  39. package/dist/performance-observation.js +7 -13
  40. package/dist/runner.js +26 -7
  41. package/docs/cli-contract.md +5 -3
  42. package/library/actions/shared/console-collector.mjs +1 -1
  43. package/library/actions/{terminal → web-dapp}/app/assert_no_console_errors.mjs +4 -4
  44. package/library/actions/{terminal → web-dapp}/app/launch.mjs +7 -7
  45. package/library/actions/{terminal → web-dapp}/platform/hud.mjs +5 -5
  46. package/library/actions/{terminal → web-dapp}/platform/page.mjs +11 -11
  47. package/library/actions/{terminal → web-dapp}/platform/runtime.mjs +16 -12
  48. package/library/actions/web-dapp/ui/navigate.mjs +24 -0
  49. package/library/actions/web-dapp/wallet/_signature-log.mjs +32 -0
  50. package/library/actions/{terminal → web-dapp}/wallet/assert_signatures.mjs +4 -4
  51. package/library/actions/{terminal → web-dapp}/wallet/list_accounts.mjs +2 -2
  52. package/library/actions/{terminal → web-dapp}/wallet/read_signatures.mjs +6 -6
  53. package/library/manifests/{terminal.action-manifest.json → web-dapp.action-manifest.json} +18 -702
  54. package/package.json +5 -5
  55. package/adapters/terminal/lib/mainnet-guard.mjs +0 -186
  56. package/adapters/terminal/policy.mjs +0 -35
  57. package/dist/adapters/terminal/console-capture.js +0 -59
  58. package/dist/adapters/terminal/doctor-checks.js +0 -24
  59. package/dist/adapters/terminal/overlay.js +0 -25
  60. package/dist/adapters/terminal/readiness.js +0 -11
  61. package/dist/adapters/terminal/surface.js +0 -135
  62. package/library/actions/terminal/perps/_venue.mjs +0 -242
  63. package/library/actions/terminal/perps/assert_orders.mjs +0 -54
  64. package/library/actions/terminal/perps/assert_positions.mjs +0 -40
  65. package/library/actions/terminal/perps/read_orders.mjs +0 -26
  66. package/library/actions/terminal/perps/read_positions.mjs +0 -25
  67. package/library/actions/terminal/perps/read_snapshot.mjs +0 -40
  68. package/library/actions/terminal/perps/revoke_agent.mjs +0 -58
  69. package/library/actions/terminal/perps/teardown_state.mjs +0 -104
  70. package/library/actions/terminal/ui/navigate.mjs +0 -31
  71. package/library/actions/terminal/wallet/_signature-log.mjs +0 -56
@@ -1,4 +1,4 @@
1
- // Stop the terminal slot browser and wallet host by their pid files and prove
1
+ // Stop the web-dapp slot browser and wallet host by their pid files and prove
2
2
  // the slot CDP port is released. A pid is signalled only when its command line
3
3
  // carries this slot's owner marker for that process kind. A pid file naming a
4
4
  // dead process is removed; one naming a live process without the marker is
@@ -10,7 +10,7 @@ import { existsSync, readFileSync, rmSync } from 'node:fs';
10
10
  import net from 'node:net';
11
11
  import path from 'node:path';
12
12
 
13
- import { terminalRuntimeDir } from '../../../library/actions/terminal/platform/runtime.mjs';
13
+ import { webDappPolicy, webDappRuntimeDir } from '../../../library/actions/web-dapp/platform/runtime.mjs';
14
14
 
15
15
  export function pidAlive(pid) {
16
16
  if (!Number.isInteger(Number(pid)) || Number(pid) <= 0) return false;
@@ -58,7 +58,7 @@ export const OWNER_FLAG = '--mm-harness-owner';
58
58
  export const OWNER_KINDS = Object.freeze(['browser', 'wallet-host', 'console-collector']);
59
59
 
60
60
  export function ownerMarker(kind, runtimeDir) {
61
- if (!OWNER_KINDS.includes(kind)) throw new Error(`unknown terminal process kind ${kind}`);
61
+ if (!OWNER_KINDS.includes(kind)) throw new Error(`unknown web-dapp process kind ${kind}`);
62
62
  const id = createHash('sha256').update(path.resolve(runtimeDir)).digest('hex').slice(0, 32);
63
63
  return `${OWNER_FLAG}=${kind}-${id}`;
64
64
  }
@@ -104,10 +104,10 @@ export async function terminate(pid, graceMs = 8000) {
104
104
  return true;
105
105
  }
106
106
 
107
- // The console collector a run started (src/adapters/terminal/console-capture.ts)
107
+ // The console collector a run started (src/adapters/web-dapp/console-capture.ts)
108
108
  // outlives browser relaunches inside the run; stop and cleanup end it.
109
109
  export async function stopConsoleCollector(target, { log = () => {}, runtimeDir } = {}) {
110
- const runtime = runtimeDir ?? terminalRuntimeDir(target);
110
+ const runtime = runtimeDir ?? webDappRuntimeDir(target);
111
111
  const pidFile = path.join(runtime, 'console-tail.pid');
112
112
  const pid = readPid(pidFile);
113
113
  if (!pid) return { stopped: null };
@@ -149,8 +149,8 @@ export function cdpListenerPids(port) {
149
149
  }
150
150
  }
151
151
 
152
- export async function stopTerminalBrowser(target, { cdpPort, log = () => {}, runtimeDir } = {}) {
153
- const runtime = runtimeDir ?? terminalRuntimeDir(target);
152
+ export async function stopWebDappBrowser(target, { cdpPort, log = () => {}, runtimeDir } = {}) {
153
+ const runtime = runtimeDir ?? webDappRuntimeDir(target);
154
154
  const stopped = [];
155
155
  const stale = [];
156
156
  const foreign = [];
@@ -181,7 +181,7 @@ export async function stopTerminalBrowser(target, { cdpPort, log = () => {}, run
181
181
  await new Promise((resolve) => setTimeout(resolve, 250));
182
182
  free = await portFree(cdpPort);
183
183
  }
184
- if (!free) throw new Error(`CDP port ${cdpPort} is still in use after stopping the terminal browser; another process owns it.`);
184
+ if (!free) throw new Error(`CDP port ${cdpPort} is still in use after stopping the ${webDappPolicy().adapterId} browser; another process owns it.`);
185
185
  }
186
186
  return { stopped, stale, foreign, cdpPortFree: free };
187
187
  }
@@ -1,4 +1,4 @@
1
- // Read-only readiness of a Web Terminal slot for recipe runs: checkout shape,
1
+ // Read-only readiness of a web-dapp slot for recipe runs: checkout shape,
2
2
  // dependencies, the slot dev server, forced testnet, wallet fixture, browser,
3
3
  // MetaMask extension copy and the per-slot CDP port. Never launches anything
4
4
  // except the optional short-lived browser start probe.
@@ -7,23 +7,13 @@ import { execFileSync } from 'node:child_process';
7
7
  import { existsSync, readFileSync } from 'node:fs';
8
8
  import path from 'node:path';
9
9
 
10
- import { canonicalPath, fixtureEntry, loadWalletFixture, readBrowserState, terminalRuntimeDir, viemAccountFromEntry } from '../../../library/actions/terminal/platform/runtime.mjs';
10
+ import { canonicalPath, fixtureEntry, loadWalletFixture, readBrowserState, viemAccountFromEntry, webDappPolicy, webDappRuntimeDir } from '../../../library/actions/web-dapp/platform/runtime.mjs';
11
11
  import { ownsProcess, pidAlive, portFree } from './processes.mjs';
12
12
 
13
13
  function check(id, ok, detail, { required = true, status } = {}) {
14
14
  return { id, status: status ?? (ok ? 'pass' : 'fail'), required, detail };
15
15
  }
16
16
 
17
- export function isTerminalCheckout(target) {
18
- try {
19
- const pkg = JSON.parse(readFileSync(path.join(target, 'package.json'), 'utf8'));
20
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
21
- return Boolean(deps.next) && existsSync(path.join(target, 'src/features/perpetuals'));
22
- } catch {
23
- return false;
24
- }
25
- }
26
-
27
17
  async function httpStatus(url, timeoutMs = 20000) {
28
18
  try {
29
19
  const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs), redirect: 'manual' });
@@ -39,9 +29,9 @@ async function httpStatus(url, timeoutMs = 20000) {
39
29
  // NEXT_PUBLIC_HYPERLIQUID_FORCE_TESTNET). Process inspection cannot prove
40
30
  // what the browser talks to (macOS reports a process's initial environment,
41
31
  // and lsof cannot see other users' sockets), so this only warns; the browser
42
- // is held to testnet by the network and wallet layers (mainnet-guard.mjs, the
43
- // wallet host). Each listener on the app port must descend from the pid-file
44
- // process and run from the checkout (its cwd); every process on the way from
32
+ // is held to testnet by the network and wallet layers (the venue policy's
33
+ // blocked hosts, the wallet host). Each listener on the app port must descend
34
+ // from the pid-file process and run from the checkout (its cwd); every process on the way from
45
35
  // a listener up to the pid-file process is read exactly (argv and environment
46
36
  // entries from sysctl KERN_PROCARGS2) and must carry the testnet variable. An
47
37
  // unreadable process makes the result unknown.
@@ -183,14 +173,14 @@ export function devServerTestnetDiagnostic(target, { appPort, variable, probe =
183
173
  export const MAINNET_CONFIRMATION = 'REAL_FUNDS';
184
174
 
185
175
  // The launch only checks the requested network: mainnet needs the real-funds
186
- // confirmation. Testnet is enforced inside the browser (mainnet-guard.mjs).
176
+ // confirmation. Testnet is enforced inside the browser (the venue policy's blocked hosts).
187
177
  export function assertLaunchNetwork(_target, { network, mainnetConfirmation } = {}) {
188
178
  const requested = network == null || network === '' ? 'testnet' : String(network);
189
179
  if (requested !== 'testnet' && requested !== 'mainnet') {
190
180
  throw new Error(`network must be testnet or mainnet, got ${JSON.stringify(network)}.`);
191
181
  }
192
182
  if (requested === 'mainnet' && mainnetConfirmation !== MAINNET_CONFIRMATION) {
193
- throw new Error(`network=mainnet needs mainnet_confirmation=${MAINNET_CONFIRMATION}; refusing to launch the terminal browser.`);
183
+ throw new Error(`network=mainnet needs mainnet_confirmation=${MAINNET_CONFIRMATION}; refusing to launch the ${webDappPolicy().adapterId} browser.`);
194
184
  }
195
185
  return requested;
196
186
  }
@@ -200,10 +190,10 @@ function listing(names) {
200
190
  return names.length > 1 ? `${names.slice(0, -1).join(', ')} and ${names.at(-1)}` : names.join('');
201
191
  }
202
192
 
203
- // `policy`: the venue policy (adapters/terminal/policy.mjs): dependencies,
204
- // startPath, testnetVariable.
205
- export async function terminalReadiness({
206
- policy,
193
+ // `policy`: the venue policy (checkout, dependencies, startPath, testnetVariable);
194
+ // by default the one RECIPE_WEB_DAPP_POLICY names.
195
+ export async function webDappReadiness({
196
+ policy = webDappPolicy(),
207
197
  target,
208
198
  appPort,
209
199
  cdpPort,
@@ -213,8 +203,8 @@ export async function terminalReadiness({
213
203
  probeBrowser = undefined,
214
204
  }) {
215
205
  const checks = [];
216
- const shape = isTerminalCheckout(target);
217
- checks.push(check('checkout', shape, shape ? 'Next.js checkout with src/features/perpetuals' : `${target} is not a Web Terminal checkout`));
206
+ const shape = policy.checkout.matches(target);
207
+ checks.push(check('checkout', shape, shape ? policy.checkout.label : `${target} is not a ${policy.checkout.name} checkout`));
218
208
  const deps = policy.dependencies.filter((name) => !existsSync(path.join(target, 'node_modules', name, 'package.json')));
219
209
  checks.push(check('dependencies', deps.length === 0, deps.length === 0 ? `${listing(policy.dependencies)} installed` : `missing: ${deps.join(', ')} (run npm ci on Node 22)`));
220
210
 
@@ -224,7 +214,7 @@ export async function terminalReadiness({
224
214
  } else {
225
215
  checks.push(check('app-dev-server', false, 'no dev-server port; pass --watcher-port <port> or set TERMINAL_APP_PORT'));
226
216
  }
227
- // Advisory: the browser enforces testnet itself (mainnet-guard.mjs).
217
+ // Advisory: the browser enforces testnet itself (the venue policy's blocked hosts).
228
218
  const testnet = devServerTestnetDiagnostic(target, { appPort, variable: policy.testnetVariable });
229
219
  checks.push(check('testnet-forced', testnet.known && testnet.forced, testnet.detail, {
230
220
  required: false,
@@ -264,7 +254,7 @@ export async function terminalReadiness({
264
254
  : 'TERMINAL_EXTENSION_CHECKOUT unset or not installed (needed to import the wallet into the extension)', { required: extensionRequired }));
265
255
 
266
256
  const state = await readBrowserState(target);
267
- const runtime = terminalRuntimeDir(target);
257
+ const runtime = webDappRuntimeDir(target);
268
258
  const live = Boolean(state && ownsProcess('browser', state.browserPid, runtime) && ownsProcess('wallet-host', state.hostPid, runtime));
269
259
  if (cdpPort) {
270
260
  const free = await portFree(cdpPort);
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // stop.mjs — stop the terminal slot browser, wallet host and console collector, verify the CDP port
2
+ // stop.mjs — stop the web-dapp slot browser, wallet host and console collector, verify the CDP port
3
3
  // is free. Idempotent: nothing running is success.
4
4
  //
5
5
  // Inputs: --target <checkout> [--cdp-port <port>] [--json]
@@ -9,7 +9,8 @@
9
9
  import path from 'node:path';
10
10
  import { fileURLToPath } from 'node:url';
11
11
 
12
- import { stopConsoleCollector, stopTerminalBrowser } from './lib/processes.mjs';
12
+ import { webDappPolicy } from '../../library/actions/web-dapp/platform/runtime.mjs';
13
+ import { stopConsoleCollector, stopWebDappBrowser } from './lib/processes.mjs';
13
14
 
14
15
  const usage = 'Usage: stop.mjs --target <checkout> [--cdp-port <port>] [--json]';
15
16
 
@@ -26,8 +27,8 @@ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.me
26
27
  const target = value('--target') ?? process.cwd();
27
28
  const rawPort = value('--cdp-port') ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
28
29
  try {
29
- const log = (message) => process.stderr.write(`[terminal/stop] ${message}\n`);
30
- const result = await stopTerminalBrowser(target, { cdpPort: rawPort ? Number(rawPort) : undefined, log });
30
+ const log = (message) => process.stderr.write(`[${webDappPolicy().adapterId}/stop] ${message}\n`);
31
+ const result = await stopWebDappBrowser(target, { cdpPort: rawPort ? Number(rawPort) : undefined, log });
31
32
  const collector = await stopConsoleCollector(target, { log });
32
33
  process.stdout.write(`${JSON.stringify({ status: 'pass', ...result, consoleCollector: collector })}\n`);
33
34
  } catch (error) {
@@ -1,20 +1,21 @@
1
1
  #!/usr/bin/env node
2
- // verify.mjs — `mm-harness verify --adapter terminal`: read-only readiness of a
3
- // Web Terminal slot (checkout, deps, dev server, forced testnet, fixture,
2
+ // verify.mjs — `mm-harness verify --adapter <web-dapp adapter>`: read-only readiness of a
3
+ // web-dapp slot (checkout, deps, dev server, forced testnet, fixture,
4
4
  // browser start probe, MetaMask extension copy, CDP port).
5
5
  //
6
- // Inputs: --policy-module <venue policy module> --target <checkout> [--cdp-port <port>] [--watcher-port <app port>]
6
+ // Inputs: --target <checkout> [--cdp-port <port>] [--watcher-port <app port>]
7
7
  // [--signer extension|injected] [--account <name>] [--json]
8
8
  // Outputs: readiness JSON (or a human summary) on stdout. Exit 0 ready; 1 not ready.
9
9
  // Never launches the slot browser or the dev server.
10
10
 
11
11
  import path from 'node:path';
12
- import { fileURLToPath, pathToFileURL } from 'node:url';
12
+ import { fileURLToPath } from 'node:url';
13
13
 
14
14
  import { resolveBrowser } from './launch.mjs';
15
- import { terminalReadiness } from './lib/readiness.mjs';
15
+ import { webDappPolicy } from '../../library/actions/web-dapp/platform/runtime.mjs';
16
+ import { webDappReadiness } from './lib/readiness.mjs';
16
17
 
17
- const usage = 'Usage: verify.mjs --policy-module <venue policy module> --target <checkout> [--cdp-port <port>] [--watcher-port <app port>] [--signer extension|injected] [--account <name>] [--json]';
18
+ const usage = 'Usage: verify.mjs --target <checkout> [--cdp-port <port>] [--watcher-port <app port>] [--signer extension|injected] [--account <name>] [--json]';
18
19
 
19
20
  if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
20
21
  const argv = process.argv.slice(2);
@@ -27,13 +28,8 @@ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.me
27
28
  return index >= 0 ? argv[index + 1] : undefined;
28
29
  };
29
30
  const num = (raw) => (raw && /^\d+$/u.test(raw) ? Number(raw) : undefined);
30
- const policyModule = value('--policy-module');
31
- if (!policyModule) {
32
- process.stderr.write(`Missing --policy-module\n${usage}\n`);
33
- process.exit(2);
34
- }
35
- const { policy } = await import(pathToFileURL(path.resolve(policyModule)).href);
36
- const report = await terminalReadiness({
31
+ const policy = webDappPolicy();
32
+ const report = await webDappReadiness({
37
33
  policy,
38
34
  target: path.resolve(value('--target') ?? process.cwd()),
39
35
  appPort: num(value('--watcher-port') ?? process.env.TERMINAL_APP_PORT ?? process.env.WATCHER_PORT),
@@ -43,13 +39,13 @@ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.me
43
39
  probeBrowser: resolveBrowser,
44
40
  });
45
41
  if (argv.includes('--json')) {
46
- process.stdout.write(`${JSON.stringify({ schemaVersion: 1, command: 'verify', adapter: 'terminal', ...report }, null, 2)}\n`);
42
+ process.stdout.write(`${JSON.stringify({ schemaVersion: 1, command: 'verify', adapter: policy.adapterId, ...report }, null, 2)}\n`);
47
43
  } else {
48
44
  for (const check of report.checks) {
49
45
  const mark = check.status === 'pass' ? '✓' : check.status === 'warn' || !check.required ? '·' : '✗';
50
46
  process.stdout.write(`${mark} ${check.id}: ${check.detail}\n`);
51
47
  }
52
- process.stdout.write(report.status === 'pass' ? 'terminal slot ready\n' : `not ready: ${report.failed.join(', ')}\n`);
48
+ process.stdout.write(report.status === 'pass' ? `${policy.adapterId} slot ready\n` : `not ready: ${report.failed.join(', ')}\n`);
53
49
  }
54
50
  process.exit(report.status === 'pass' ? 0 : 1);
55
51
  }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // wallet-host.mjs — long-running observer (and, in injected mode, signer) for the
3
- // terminal slot browser.
3
+ // web-dapp slot browser.
4
4
  //
5
5
  // Purpose:
6
6
  // Attaches to the slot browser over CDP and, for every app document, installs
@@ -27,20 +27,20 @@
27
27
  import { appendFileSync, chmodSync, existsSync, unwatchFile, watchFile, writeFileSync } from 'node:fs';
28
28
  import { createRequire } from 'node:module';
29
29
  import path from 'node:path';
30
- import { pathToFileURL } from 'node:url';
31
30
 
32
31
  import { createStrictWallet, createWalletRequestBinding, pageReadyExpression, pageScriptSource } from '@farmslot/adapter-web/dapp';
33
32
  import { isAppUrl as isExactAppUrl, shortUrl } from '@farmslot/adapter-web/origin';
34
33
 
35
34
  import { CdpClient, browserWebSocketUrl } from './lib/cdp-client.mjs';
36
35
  import { hudFile, hudRenderExpression, readHudState } from './lib/hud.mjs';
37
- import { hostOf, isMainnetVenueUrl } from './lib/mainnet-guard.mjs';
36
+ import { fixtureAccount, webDappPolicy } from '../../library/actions/web-dapp/platform/runtime.mjs';
37
+ import { hostOf, isBlockedUrl } from './lib/blocked-hosts.mjs';
38
38
  import { ownedBrowserPids } from './lib/processes.mjs';
39
39
 
40
40
  const { captureMacFrontmost, macFocusDisabled } = createRequire(import.meta.url)('@farmslot/adapter-web/macos-focus');
41
41
 
42
42
  function usage() {
43
- return 'Usage: wallet-host.mjs --cdp-port <port> --runtime-dir <dir> --signer <extension|injected> --app-origin <url> --policy-module <venue policy module> [--network testnet|mainnet] [--mainnet-hosts <h1,h2>] [--testnet-hosts <h1,h2>] [--extension-id <id>] [--target <checkout> --account <name> --start-chain <id>] [--observe-focus 1] [--window x,y,w,h] [--start-path <path>] [--mm-harness-owner=<marker>]';
43
+ return 'Usage: wallet-host.mjs --cdp-port <port> --runtime-dir <dir> --signer <extension|injected> --app-origin <url> [--network testnet|mainnet] [--mainnet-hosts <h1,h2>] [--testnet-hosts <h1,h2>] [--extension-id <id>] [--target <checkout> --account <name> --start-chain <id>] [--observe-focus 1] [--window x,y,w,h] [--start-path <path>] [--mm-harness-owner=<marker>]';
44
44
  }
45
45
 
46
46
  function parseArgs(argv) {
@@ -59,16 +59,16 @@ function parseArgs(argv) {
59
59
  args[flag.slice(2)] = argv[index + 1];
60
60
  index += 1;
61
61
  }
62
- for (const key of ['cdp-port', 'runtime-dir', 'signer', 'app-origin', 'policy-module']) {
62
+ for (const key of ['cdp-port', 'runtime-dir', 'signer', 'app-origin']) {
63
63
  if (!args[key]) throw new Error(`Missing --${key}\n${usage()}`);
64
64
  }
65
65
  return args;
66
66
  }
67
67
 
68
68
  const args = parseArgs(process.argv.slice(2));
69
- // The venue policy (the Terminal's adapters/terminal/policy.mjs): link hosts,
70
- // the probe host, the typed-data refusal and the start page.
71
- const { policy } = await import(pathToFileURL(path.resolve(args['policy-module'])).href);
69
+ // The venue policy (e.g. the Terminal plugin's policy.mjs), from RECIPE_WEB_DAPP_POLICY:
70
+ // link hosts, the probe, the typed-data refusal, the start page and chain.
71
+ const policy = webDappPolicy();
72
72
  const LINK_HOSTS = new Set(policy.linkHosts);
73
73
  const runtimeDir = path.resolve(args['runtime-dir']);
74
74
  const logFile = path.join(runtimeDir, 'wallet-requests.jsonl');
@@ -111,7 +111,6 @@ function say(message) {
111
111
 
112
112
  let wallet = null;
113
113
  if (signer === 'injected') {
114
- const { fixtureAccount } = await import('../../library/actions/terminal/platform/runtime.mjs');
115
114
  if (!args.target || !args.account) throw new Error('injected mode requires --target and --account');
116
115
  const account = await fixtureAccount(path.resolve(args.target), args.account);
117
116
  wallet = createStrictWallet({ account, chainId: Number(args['start-chain'] ?? policy.startChain) });
@@ -334,7 +333,7 @@ client.on('Page.frameNavigated', ({ frame }, sessionId) => {
334
333
  }
335
334
  });
336
335
  client.on('Fetch.requestPaused', ({ requestId, request, resourceType }, sessionId) => {
337
- const mainnet = isMainnetVenueUrl(request.url, mainnetHosts);
336
+ const mainnet = isBlockedUrl(request.url, mainnetHosts);
338
337
  if (mainnet) {
339
338
  appendEntry({ kind: 'blocked-mainnet', t: Date.now(), transport: 'http', url: shortUrl(request.url), resourceType, probe: request.url.includes(PROBE_MARK) });
340
339
  say(`blocked a mainnet request to ${shortUrl(request.url)}`);
@@ -361,7 +360,7 @@ client.on('Network.webSocketCreated', ({ requestId, url }, sessionId) => {
361
360
  if (url.includes(PROBE_MARK)) probeRequests.set(requestId, 'websocket');
362
361
  recordVenueTraffic(sessionId, url);
363
362
  // The host resolver rule fails the connection; the attempt is still evidence.
364
- if (isMainnetVenueUrl(url, mainnetHosts)) {
363
+ if (isBlockedUrl(url, mainnetHosts)) {
365
364
  appendEntry({ kind: 'blocked-mainnet', t: Date.now(), transport: 'websocket', url: shortUrl(url), probe: url.includes(PROBE_MARK) });
366
365
  say(`blocked a mainnet WebSocket to ${shortUrl(url)}`);
367
366
  }
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { EXIT, optionFlag, optionString } from "@farmslot/recipe-cli/harness";
2
+ import { EXIT, optionFlag } from "@farmslot/recipe-cli/harness";
3
3
  import { verifyOwnedCollectorControl } from "../console-capture.js";
4
4
  import { resolveExtensionBrowserPid } from "./browser-pid.js";
5
5
  import {
@@ -38,28 +38,9 @@ const extensionRun = {
38
38
  pageMode: "home-or-sidepanel"
39
39
  });
40
40
  },
41
- // The proof runtime is checked on the run's own ports: explicit flags win over
42
- // the slot's for the check, and the slot's come back afterwards.
43
- runtimeCheck({ target, options, heal, state, json, onRecovery }) {
44
- return async () => {
45
- const previousCdpPort = process.env.CDP_PORT;
46
- const previousRecipeCdpPort = process.env.RECIPE_CDP_PORT;
47
- const previousWatcherPort = process.env.WATCHER_PORT;
48
- const explicitCdpPort = optionString(options, "cdpPort");
49
- const explicitWatcherPort = optionString(options, "watcherPort") ?? optionString(options, "metroPort");
50
- if (explicitCdpPort) {
51
- process.env.CDP_PORT = explicitCdpPort;
52
- process.env.RECIPE_CDP_PORT = explicitCdpPort;
53
- }
54
- if (explicitWatcherPort) process.env.WATCHER_PORT = explicitWatcherPort;
55
- try {
56
- return await ensureExtensionProofRuntime(target, heal, state, json, { onRecovery });
57
- } finally {
58
- restoreEnv("CDP_PORT", previousCdpPort);
59
- restoreEnv("RECIPE_CDP_PORT", previousRecipeCdpPort);
60
- restoreEnv("WATCHER_PORT", previousWatcherPort);
61
- }
62
- };
41
+ // The proof runtime is checked on the run's own ports (the engine's scope).
42
+ runtimeCheck({ target, heal, state, json, onRecovery }) {
43
+ return () => ensureExtensionProofRuntime(target, heal, state, json, { onRecovery });
63
44
  },
64
45
  launchedBrowser: (target, artifactsDir, cdpPort) => launchedBrowser(target, artifactsDir, cdpPort),
65
46
  browserProvenance: (browser) => ({
@@ -207,16 +188,12 @@ const extensionObservation = {
207
188
  start: async (context) => {
208
189
  const observation = await import("../../performance-observation.js");
209
190
  return observation.startRunPerformanceObservation({
210
- backend: (target, env, ports) => observation.createExtensionPerformanceObserver(target, env, ports),
191
+ backend: (target, env) => observation.createExtensionPerformanceObserver(target, env),
211
192
  platform: () => "extension"
212
193
  }, context);
213
194
  }
214
195
  }
215
196
  };
216
- function restoreEnv(key, value) {
217
- if (value === void 0) delete process.env[key];
218
- else process.env[key] = value;
219
- }
220
197
  export {
221
198
  ensureExtensionProofRuntime,
222
199
  extensionDiagnostics,
@@ -0,0 +1,98 @@
1
+ import path from "node:path";
2
+ import {
3
+ adapterPlugin,
4
+ adapterSelectionFailureOut,
5
+ ensureAdapterLoaded,
6
+ selectedAdapterId
7
+ } from "@farmslot/recipe-cli/harness";
8
+ import { configPath, declaredLibraryPathEnv, readConfig } from "../review/knowledge.js";
9
+ import { adapterRegistry } from "./registry.js";
10
+ import { bindWebDappPolicy } from "./web-dapp/surface.js";
11
+ function configuredLibrarySources() {
12
+ let libraries;
13
+ try {
14
+ libraries = readConfig(configPath()).libraries;
15
+ } catch {
16
+ return [];
17
+ }
18
+ return Object.entries(libraries ?? {}).map(([name, root]) => ({ name, root: path.resolve(root) }));
19
+ }
20
+ function operatorLibraryEnv() {
21
+ const declared = declaredLibraryPathEnv();
22
+ if (!declared) return process.env;
23
+ const env = { ...process.env };
24
+ if (declared.value === void 0) delete env.RECIPE_LIBRARY_PATH;
25
+ else env.RECIPE_LIBRARY_PATH = declared.value;
26
+ return env;
27
+ }
28
+ async function loadSelectedAdapter(command, args) {
29
+ const passthrough = args.indexOf("--");
30
+ const own = passthrough < 0 ? args : args.slice(0, passthrough);
31
+ const libraries = [];
32
+ own.forEach((arg, index) => {
33
+ if (arg === "--library" && own[index + 1] !== void 0) libraries.push(own[index + 1]);
34
+ else if (arg.startsWith("--library=")) libraries.push(arg.slice("--library=".length));
35
+ });
36
+ const selected = selectedAdapterId(args);
37
+ try {
38
+ await ensureAdapterLoaded(selected, {
39
+ libraries,
40
+ configured: configuredLibrarySources(),
41
+ env: operatorLibraryEnv(),
42
+ adopt: adoptPluginAdapter
43
+ });
44
+ return void 0;
45
+ } catch (error) {
46
+ const refusal = error;
47
+ if (typeof refusal.code !== "string" || typeof refusal.userAction !== "string") throw error;
48
+ if (selected !== void 0 && adapterRegistry.has(selected) && refusal.code !== "ADAPTER_ID_CONFLICT") {
49
+ return void 0;
50
+ }
51
+ return adapterSelectionFailureOut(own.includes("--json"), command, {
52
+ code: refusal.code,
53
+ message: String(refusal.message),
54
+ userAction: refusal.userAction
55
+ });
56
+ }
57
+ }
58
+ const ADAPTER_PLUGIN_ENV = "METAMASK_RECIPE_ADAPTER_PLUGIN";
59
+ function adoptPluginAdapter(adapter) {
60
+ const surface = bindWebDappPolicy(adapter);
61
+ return {
62
+ ...surface,
63
+ run: {
64
+ ...surface.run,
65
+ envKeys: [...surface.run?.envKeys ?? [], ADAPTER_PLUGIN_ENV],
66
+ activateEnv(projectRoot, options) {
67
+ surface.run?.activateEnv?.(projectRoot, options);
68
+ process.env[ADAPTER_PLUGIN_ENV] = `${adapter.id} ${adapterPlugin(adapter.id)?.digest ?? "unknown"}`;
69
+ }
70
+ },
71
+ runwayProvision: surface.runwayProvision ?? {
72
+ async run(target) {
73
+ return {
74
+ schemaVersion: 1,
75
+ command: "provision",
76
+ adapter: adapter.id,
77
+ target: path.resolve(target),
78
+ status: "fail",
79
+ exitCode: 2,
80
+ error: {
81
+ code: "UNSUPPORTED_ADAPTER",
82
+ message: `${adapter.id} has no mobile simulator to provision.`,
83
+ userAction: `mm-harness run <recipe> --adapter ${adapter.id}`
84
+ }
85
+ };
86
+ }
87
+ },
88
+ readiness: surface.readiness ?? { compatibilityMode: () => "unsupported/no bridge" },
89
+ fixtures: surface.fixtures ?? { operations: [] }
90
+ };
91
+ }
92
+ export {
93
+ ADAPTER_PLUGIN_ENV,
94
+ adoptPluginAdapter,
95
+ configuredLibrarySources,
96
+ loadSelectedAdapter,
97
+ operatorLibraryEnv
98
+ };
@@ -3,7 +3,7 @@ import { adapterRegistry } from "./registry.js";
3
3
  import "./mobile/surface.js";
4
4
  import "./extension/surface.js";
5
5
  import "./core/surface.js";
6
- import "./terminal/surface.js";
6
+ import "./web-dapp/surface.js";
7
7
  function getAdapterSurface(adapter) {
8
8
  return adapterRegistry.get(adapter);
9
9
  }
@@ -1,7 +1,7 @@
1
1
  import { manifestPath } from "../../paths.js";
2
2
  import { perpsOrderInputFindings } from "../perps-order-inputs.js";
3
- const terminalActions = {
4
- manifestPath: () => manifestPath("terminal"),
3
+ const webDappActions = {
4
+ manifestPath: () => manifestPath("web-dapp"),
5
5
  semantic: [
6
6
  "app.network_capture",
7
7
  "app.network_assert",
@@ -12,8 +12,8 @@ const terminalActions = {
12
12
  ui: (harness, { hudPolicy }) => ({
13
13
  base: harness.createCdpWebUiTransport({
14
14
  async withPage(input, callback) {
15
- const { withTerminalPage } = await import("../../../library/actions/terminal/platform/page.mjs");
16
- return withTerminalPage(
15
+ const { withAppPage } = await import("../../../library/actions/web-dapp/platform/page.mjs");
16
+ return withAppPage(
17
17
  {
18
18
  action: input.action,
19
19
  node: input.node,
@@ -29,8 +29,8 @@ const terminalActions = {
29
29
  }),
30
30
  async execute(action, node, context, standard) {
31
31
  if (action === "app.hud") {
32
- const { showTerminalHud } = await import("../../../library/actions/terminal/platform/hud.mjs");
33
- return showTerminalHud(
32
+ const { showAppHud } = await import("../../../library/actions/web-dapp/platform/hud.mjs");
33
+ return showAppHud(
34
34
  { action, node, context: { nodeId: context.nodeId, projectRoot: context.projectRoot } },
35
35
  { hudPolicy }
36
36
  );
@@ -42,5 +42,5 @@ const terminalActions = {
42
42
  inputFindings: (nodeId, node) => perpsOrderInputFindings(nodeId, node, { ensureChecksOnly: false })
43
43
  };
44
44
  export {
45
- terminalActions
45
+ webDappActions
46
46
  };
@@ -0,0 +1,63 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { recipeRuntimePath } from "@farmslot/recipe-cli/harness";
4
+ import { verifyCapture } from "../../../library/actions/shared/console-collector.mjs";
5
+ import { ensureConsoleCollector } from "../console-capture.js";
6
+ import { webDappAdapterId } from "../../../adapters/web-dapp/lib/policy.mjs";
7
+ const WEB_DAPP_CONSOLE_FILES = Object.freeze({
8
+ app: "app-console.log",
9
+ extension: "extension-console.log",
10
+ collectorLog: "console-tail.log",
11
+ pidFile: "console-tail.pid"
12
+ });
13
+ function webDappRuntimeFile(target, name) {
14
+ let root = path.resolve(target);
15
+ try {
16
+ root = fs.realpathSync(root);
17
+ } catch {
18
+ }
19
+ return recipeRuntimePath(root, webDappAdapterId(), name);
20
+ }
21
+ function webDappCaptureFiles(target) {
22
+ return {
23
+ extensionLog: webDappRuntimeFile(target, WEB_DAPP_CONSOLE_FILES.extension),
24
+ pageLog: webDappRuntimeFile(target, WEB_DAPP_CONSOLE_FILES.app),
25
+ collectorLog: webDappRuntimeFile(target, WEB_DAPP_CONSOLE_FILES.collectorLog),
26
+ pidFile: webDappRuntimeFile(target, WEB_DAPP_CONSOLE_FILES.pidFile)
27
+ };
28
+ }
29
+ function webDappCdpPort() {
30
+ return process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
31
+ }
32
+ async function ensureWebDappConsoleCapture(target) {
33
+ const cdpPort = webDappCdpPort();
34
+ if (!cdpPort) {
35
+ const id = webDappAdapterId();
36
+ throw new Error(`${id.charAt(0).toUpperCase()}${id.slice(1)} console capture requires the slot CDP port.`);
37
+ }
38
+ await ensureConsoleCollector({
39
+ cdpPort,
40
+ ...webDappCaptureFiles(target),
41
+ waitForAttachment: false,
42
+ cwd: target
43
+ });
44
+ }
45
+ async function verifyWebDappConsoleControl(capture) {
46
+ let browser = null;
47
+ try {
48
+ const value = JSON.parse(fs.readFileSync(webDappRuntimeFile(capture.projectRoot, "browser.json"), "utf8"));
49
+ browser = typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
50
+ } catch {
51
+ browser = null;
52
+ }
53
+ if (typeof browser?.appOrigin !== "string") return { ok: false, detail: "no slot browser state (browser.json) to verify the page console capture against" };
54
+ return verifyCapture({ cdpPort: capture.cdpPort, log: capture.pageLog, target: "page", appOrigin: browser.appOrigin });
55
+ }
56
+ export {
57
+ WEB_DAPP_CONSOLE_FILES,
58
+ ensureWebDappConsoleCapture,
59
+ verifyWebDappConsoleControl,
60
+ webDappCaptureFiles,
61
+ webDappCdpPort,
62
+ webDappRuntimeFile
63
+ };
@@ -0,0 +1,33 @@
1
+ import { POLICY_ENV, webDappPolicy } from "../../../adapters/web-dapp/lib/policy.mjs";
2
+ import { webDappAppPort, webDappCdpPort } from "./surface.js";
3
+ async function webDappDoctorChecks(target) {
4
+ if (!process.env[POLICY_ENV]) {
5
+ return [{
6
+ id: "web-dapp-venue-policy",
7
+ status: "fail",
8
+ required: true,
9
+ message: "web-dapp needs a venue policy: select an adapter that extends it (e.g. --adapter terminal with its recipe library on RECIPE_LIBRARY_PATH)."
10
+ }];
11
+ }
12
+ const policy = webDappPolicy();
13
+ const { webDappReadiness } = await import("../../../adapters/web-dapp/lib/readiness.mjs");
14
+ const { resolveBrowser } = await import("../../../adapters/web-dapp/launch.mjs");
15
+ const report = await webDappReadiness({
16
+ policy,
17
+ target,
18
+ appPort: webDappAppPort(),
19
+ cdpPort: webDappCdpPort(),
20
+ signer: process.env.TERMINAL_SIGNER || "extension",
21
+ account: process.env.TERMINAL_ACCOUNT || "dev1",
22
+ probeBrowser: resolveBrowser
23
+ });
24
+ return report.checks.map((check) => ({
25
+ id: `${policy.adapterId}-${check.id}`,
26
+ status: check.status === "pass" ? "pass" : "fail",
27
+ required: check.required,
28
+ message: check.detail
29
+ }));
30
+ }
31
+ export {
32
+ webDappDoctorChecks
33
+ };