@deeeed/metamask-harness 0.82.1 → 0.84.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 +48 -0
  2. package/README.md +8 -1
  3. package/adapters/core/cleanup.sh +5 -29
  4. package/adapters/core/controller-import-probe.mjs +26 -0
  5. package/adapters/manifest.json +22 -102
  6. package/adapters/shared/session-lock.mjs +43 -0
  7. package/adapters/web-dapp/metamask-signer.mjs +212 -0
  8. package/dist/adapters/core/actions.js +0 -3
  9. package/dist/adapters/core/overlay.js +1 -5
  10. package/dist/adapters/core/readiness.js +133 -2
  11. package/dist/adapters/core/surface.js +53 -41
  12. package/dist/adapters/extension/extension-id.js +1 -0
  13. package/dist/adapters/extension/fixtures.js +60 -1
  14. package/dist/adapters/extension/network-observer.js +19 -206
  15. package/dist/adapters/extension/run.js +5 -3
  16. package/dist/adapters/extension/runtime.js +112 -20
  17. package/dist/adapters/mobile/android-gfxinfo-performance.js +3 -1
  18. package/dist/adapters/mobile/devices.js +5 -1
  19. package/dist/adapters/mobile/performance-observer.js +9 -6
  20. package/dist/adapters/mobile/run.js +1 -1
  21. package/dist/adapters/mobile/surface.js +1 -2
  22. package/dist/adapters/performance/trace-kinds.js +36 -0
  23. package/dist/adapters/plugin-surface.js +34 -34
  24. package/dist/adapters/session-lock.js +23 -0
  25. package/dist/adapters/web-dapp/console-capture.js +2 -20
  26. package/dist/adapters/web-dapp/run.js +31 -25
  27. package/dist/adapters/web-dapp/surface.js +32 -108
  28. package/dist/command-contract.js +1363 -630
  29. package/dist/commands/core-readiness.js +1 -9
  30. package/dist/commands/doctor.js +1 -1
  31. package/dist/commands/launch/device.js +1 -1
  32. package/dist/doctor.js +5 -2
  33. package/dist/mm-harness-cli.js +228 -1210
  34. package/dist/performance-observation.js +28 -5
  35. package/docs/CONTRIBUTING.md +6 -4
  36. package/docs/cli-contract.md +1 -1
  37. package/docs/feature-guard.md +1 -1
  38. package/library/actions/core/perps/_controller.mjs +10 -0
  39. package/library/actions/extension/wallet/import.mjs +21 -4
  40. package/library/actions/extension/wallet/visible-session.mjs +32 -12
  41. package/library/actions/shared/console-collector.mjs +1 -1
  42. package/library/actions/web-dapp/app/launch.mjs +1 -1
  43. package/library/actions/web-dapp/platform/adapter-web-lib.mjs +18 -0
  44. package/library/actions/web-dapp/platform/hud.mjs +1 -1
  45. package/library/actions/web-dapp/platform/page.mjs +2 -3
  46. package/library/actions/web-dapp/platform/runtime.mjs +29 -178
  47. package/package.json +7 -6
  48. package/scripts/completions.sh +1 -1
  49. package/adapters/web-dapp/cleanup.mjs +0 -69
  50. package/adapters/web-dapp/inject.mjs +0 -75
  51. package/adapters/web-dapp/launch.mjs +0 -631
  52. package/adapters/web-dapp/lib/blocked-hosts.mjs +0 -24
  53. package/adapters/web-dapp/lib/cdp-client.mjs +0 -84
  54. package/adapters/web-dapp/lib/display.mjs +0 -115
  55. package/adapters/web-dapp/lib/hud.mjs +0 -148
  56. package/adapters/web-dapp/lib/policy.mjs +0 -79
  57. package/adapters/web-dapp/lib/processes.mjs +0 -187
  58. package/adapters/web-dapp/lib/readiness.mjs +0 -272
  59. package/adapters/web-dapp/stop.mjs +0 -38
  60. package/adapters/web-dapp/verify.mjs +0 -51
  61. package/adapters/web-dapp/wallet-host.mjs +0 -520
  62. package/dist/adapters/core/live-script-env.js +0 -36
  63. package/dist/adapters/extension/browser-cdp.js +0 -174
  64. package/dist/adapters/extension/performance-observer.js +0 -75
  65. package/dist/adapters/performance/cdp-trace.js +0 -406
  66. package/dist/adapters/performance/js-task-metrics.js +0 -35
  67. package/dist/adapters/web-dapp/doctor-checks.js +0 -33
  68. package/dist/adapters/web-dapp/launch.js +0 -87
  69. package/dist/adapters/web-dapp/overlay.js +0 -21
  70. package/dist/adapters/web-dapp/readiness.js +0 -10
  71. package/dist/adapters/web-dapp/stop.js +0 -20
  72. package/dist/async.js +0 -19
  73. package/dist/cli.js +0 -202
@@ -1,272 +0,0 @@
1
- // Read-only readiness of a web-dapp slot for recipe runs: checkout shape,
2
- // dependencies, the slot dev server, forced testnet, wallet fixture, browser,
3
- // MetaMask extension copy and the per-slot CDP port. Never launches anything
4
- // except the optional short-lived browser start probe.
5
-
6
- import { execFileSync } from 'node:child_process';
7
- import { existsSync, readFileSync } from 'node:fs';
8
- import path from 'node:path';
9
-
10
- import { canonicalPath, fixtureEntry, loadWalletFixture, readBrowserState, viemAccountFromEntry, webDappPolicy, webDappRuntimeDir } from '../../../library/actions/web-dapp/platform/runtime.mjs';
11
- import { ownsProcess, pidAlive, portFree } from './processes.mjs';
12
-
13
- function check(id, ok, detail, { required = true, status } = {}) {
14
- return { id, status: status ?? (ok ? 'pass' : 'fail'), required, detail };
15
- }
16
-
17
- async function httpStatus(url, timeoutMs = 20000) {
18
- try {
19
- const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs), redirect: 'manual' });
20
- return response.status;
21
- } catch (error) {
22
- return error?.name === 'TimeoutError' ? 'timeout' : 'unreachable';
23
- }
24
- }
25
-
26
- // Diagnostic only. Whether the slot's dev server looks configured for testnet:
27
- // the farm starts `next dev` (temp/farmslot/next-dev.pid) with the policy's
28
- // testnet variable set to true (the Terminal's
29
- // NEXT_PUBLIC_HYPERLIQUID_FORCE_TESTNET). Process inspection cannot prove
30
- // what the browser talks to (macOS reports a process's initial environment,
31
- // and lsof cannot see other users' sockets), so this only warns; the browser
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
35
- // a listener up to the pid-file process is read exactly (argv and environment
36
- // entries from sysctl KERN_PROCARGS2) and must carry the testnet variable. An
37
- // unreadable process makes the result unknown.
38
- export const DEV_SERVER_PID_FILE = 'temp/farmslot/next-dev.pid';
39
-
40
- function run(command, args) {
41
- try {
42
- return execFileSync(command, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
43
- } catch (error) {
44
- return typeof error?.stdout === 'string' ? error.stdout : '';
45
- }
46
- }
47
-
48
- const PROCARGS_HELPER = `
49
- import ctypes, ctypes.util, json, struct, sys
50
- libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
51
- pid = int(sys.argv[1])
52
- argmax = ctypes.c_int(0)
53
- size = ctypes.c_size_t(ctypes.sizeof(argmax))
54
- if libc.sysctl((ctypes.c_int * 2)(1, 8), 2, ctypes.byref(argmax), ctypes.byref(size), None, 0) != 0:
55
- print("null"); sys.exit(0)
56
- buf = ctypes.create_string_buffer(argmax.value)
57
- size = ctypes.c_size_t(argmax.value)
58
- if libc.sysctl((ctypes.c_int * 3)(1, 49, pid), 3, buf, ctypes.byref(size), None, 0) != 0:
59
- print("null"); sys.exit(0)
60
- raw = buf.raw[:size.value]
61
- argc = struct.unpack("i", raw[:4])[0]
62
- rest = raw[4:]
63
- rest = rest[rest.index(b"\\0"):].lstrip(b"\\0")
64
- parts = rest.split(b"\\0")
65
- env = []
66
- for part in parts[argc:]:
67
- if not part: break
68
- env.append(part.decode("utf-8", "replace"))
69
- print(json.dumps({"argv": [p.decode("utf-8", "replace") for p in parts[:argc]], "env": env}))
70
- `;
71
-
72
- export const systemProbe = Object.freeze({
73
- listeners: (port) => run('lsof', ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-t']).split('\n').map(Number).filter((pid) => pid > 0),
74
- parents: () => new Map(run('ps', ['-A', '-o', 'pid=,ppid=']).split('\n')
75
- .map((line) => line.trim().split(/\s+/u).map(Number))
76
- .filter(([pid, ppid]) => pid > 0 && ppid >= 0)),
77
- cwd: (pid) => (run('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn']).split('\n').find((line) => line.startsWith('n')) ?? '').slice(1) || null,
78
- // { argv: string[], env: string[] } with exact element boundaries, or null.
79
- procArgs: (pid) => {
80
- const python = usablePython();
81
- if (!python) return null;
82
- try {
83
- return JSON.parse(execFileSync(python, ['-c', PROCARGS_HELPER, String(pid)], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 15000 }).trim());
84
- } catch {
85
- return null;
86
- }
87
- },
88
- });
89
-
90
- // macOS's /usr/bin/python3 is a stub until the Command Line Tools are
91
- // installed, and running it opens an install dialog that takes keyboard
92
- // focus. Use it only when xcode-select reports a developer directory; any
93
- // other python3 on PATH is a real install.
94
- export function pythonFor({ found, platform = process.platform, developerDir }) {
95
- if (!found) return null;
96
- if (platform === 'darwin' && found === '/usr/bin/python3' && !developerDir()) return null;
97
- return found;
98
- }
99
-
100
- let pythonChoice;
101
- function usablePython() {
102
- if (pythonChoice === undefined) {
103
- pythonChoice = pythonFor({ found: run('/usr/bin/which', ['python3']).trim(), developerDir: () => run('/usr/bin/xcode-select', ['-p']).trim() });
104
- }
105
- return pythonChoice;
106
- }
107
-
108
- export function environmentValue(env, name) {
109
- const entry = env.find((item) => item.startsWith(`${name}=`));
110
- return entry === undefined ? undefined : entry.slice(name.length + 1);
111
- }
112
-
113
- // `next dev` / `next start`: the argument after the next binary.
114
- export function nextServerMode(argv) {
115
- for (let index = 0; index < argv.length - 1; index += 1) {
116
- const element = argv[index];
117
- if (element === 'next' || element.endsWith('/next') || element.endsWith('/next/dist/bin/next')) {
118
- if (argv[index + 1] === 'dev' || argv[index + 1] === 'start') return argv[index + 1];
119
- }
120
- }
121
- return null;
122
- }
123
-
124
- function chainTo(listener, root, parents) {
125
- const chain = [];
126
- let current = listener;
127
- for (let hops = 0; current && hops < 64; hops += 1) {
128
- chain.push(current);
129
- if (current === root) return chain;
130
- current = parents.get(current);
131
- }
132
- return null;
133
- }
134
-
135
- export function devServerTestnetDiagnostic(target, { appPort, variable, probe = systemProbe } = {}) {
136
- const checkout = canonicalPath(target);
137
- const unknown = (detail) => ({ known: false, forced: false, detail });
138
- const pidFile = path.join(checkout, DEV_SERVER_PID_FILE);
139
- if (!existsSync(pidFile)) return unknown(`no ${DEV_SERVER_PID_FILE}`);
140
- const root = Number(readFileSync(pidFile, 'utf8').trim());
141
- if (!pidAlive(root)) return unknown(`next dev pid ${root} is not running`);
142
- if (!appPort) return unknown('no app port to check');
143
- const listeners = probe.listeners(appPort);
144
- if (listeners.length === 0) return unknown(`nothing (visible to this user) listens on app port ${appPort}`);
145
- const parents = probe.parents();
146
- let mode = null;
147
- for (const listener of listeners) {
148
- const chain = chainTo(listener, root, parents);
149
- if (!chain) return { known: true, forced: false, detail: `app port ${appPort} is served by pid ${listener}, outside next dev pid ${root}` };
150
- const cwd = probe.cwd(listener);
151
- if (!cwd || canonicalPath(cwd) !== checkout) {
152
- return { known: true, forced: false, detail: `pid ${listener} on app port ${appPort} runs from ${cwd ?? 'an unknown directory'}, not ${checkout}` };
153
- }
154
- for (const pid of chain) {
155
- const args = probe.procArgs(pid);
156
- if (!args) return unknown(`cannot read the arguments and environment of pid ${pid}`);
157
- // A process that rewrote its title (next-server) overwrote the memory
158
- // its arguments and environment are read from.
159
- if (args.env.length === 0) return unknown(`pid ${pid} (${args.argv[0] ?? '?'}) rewrote its process title; its environment is not readable`);
160
- const processMode = nextServerMode(args.argv);
161
- if (processMode === 'start') return { known: true, forced: false, detail: `pid ${pid} runs next start; its environment cannot prove what the build inlined` };
162
- if (processMode === 'dev') mode = 'dev';
163
- const value = environmentValue(args.env, variable);
164
- if (value !== 'true') {
165
- return { known: true, forced: false, detail: `pid ${pid} (between app port ${appPort} and next dev pid ${root}) has ${variable}=${value ?? '<unset>'}` };
166
- }
167
- }
168
- }
169
- if (mode !== 'dev') return { known: true, forced: false, detail: `no process serving app port ${appPort} runs next dev` };
170
- return { known: true, forced: true, detail: `every process from app port ${appPort}'s listeners (cwd ${checkout}) up to next dev pid ${root} has ${variable}=true` };
171
- }
172
-
173
- export const MAINNET_CONFIRMATION = 'REAL_FUNDS';
174
-
175
- // The launch only checks the requested network: mainnet needs the real-funds
176
- // confirmation. Testnet is enforced inside the browser (the venue policy's blocked hosts).
177
- export function assertLaunchNetwork(_target, { network, mainnetConfirmation } = {}) {
178
- const requested = network == null || network === '' ? 'testnet' : String(network);
179
- if (requested !== 'testnet' && requested !== 'mainnet') {
180
- throw new Error(`network must be testnet or mainnet, got ${JSON.stringify(network)}.`);
181
- }
182
- if (requested === 'mainnet' && mainnetConfirmation !== MAINNET_CONFIRMATION) {
183
- throw new Error(`network=mainnet needs mainnet_confirmation=${MAINNET_CONFIRMATION}; refusing to launch the ${webDappPolicy().adapterId} browser.`);
184
- }
185
- return requested;
186
- }
187
-
188
- // "a, b and c".
189
- function listing(names) {
190
- return names.length > 1 ? `${names.slice(0, -1).join(', ')} and ${names.at(-1)}` : names.join('');
191
- }
192
-
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(),
197
- target,
198
- appPort,
199
- cdpPort,
200
- signer = 'extension',
201
- account = 'dev1',
202
- env = process.env,
203
- probeBrowser = undefined,
204
- }) {
205
- const checks = [];
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`));
208
- const deps = policy.dependencies.filter((name) => !existsSync(path.join(target, 'node_modules', name, 'package.json')));
209
- checks.push(check('dependencies', deps.length === 0, deps.length === 0 ? `${listing(policy.dependencies)} installed` : `missing: ${deps.join(', ')} (run npm ci on Node 22)`));
210
-
211
- if (appPort) {
212
- const status = await httpStatus(`http://127.0.0.1:${appPort}${policy.startPath}`);
213
- checks.push(check('app-dev-server', status === 200, `GET http://localhost:${appPort}${policy.startPath} -> ${status}`));
214
- } else {
215
- checks.push(check('app-dev-server', false, 'no dev-server port; pass --watcher-port <port> or set TERMINAL_APP_PORT'));
216
- }
217
- // Advisory: the browser enforces testnet itself (the venue policy's blocked hosts).
218
- const testnet = devServerTestnetDiagnostic(target, { appPort, variable: policy.testnetVariable });
219
- checks.push(check('testnet-forced', testnet.known && testnet.forced, testnet.detail, {
220
- required: false,
221
- status: testnet.known && testnet.forced ? 'pass' : 'warn',
222
- }));
223
-
224
- try {
225
- const { file, fixture } = await loadWalletFixture(target, env);
226
- const address = viemAccountFromEntry(fixtureEntry(fixture, account)).address;
227
- checks.push(check('wallet-fixture', true, `${path.basename(file)}: account ${account} -> ${address}`));
228
- } catch (error) {
229
- checks.push(check('wallet-fixture', false, error.message.split('\n')[0]));
230
- }
231
-
232
- if (probeBrowser) {
233
- try {
234
- const browser = await probeBrowser(env);
235
- checks.push(check('browser', true, `${browser.version ?? browser.bin} (${browser.source})`));
236
- } catch (error) {
237
- checks.push(check('browser', false, error.message.split('\n')[0]));
238
- }
239
- }
240
-
241
- const extensionDir = path.resolve(env.TERMINAL_EXTENSION_DIR || path.join(target, 'temp/metamask-extension/chrome'));
242
- const manifestPath = path.join(extensionDir, 'manifest.json');
243
- const extensionRequired = signer === 'extension';
244
- if (existsSync(manifestPath)) {
245
- const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
246
- checks.push(check('metamask-extension', true, `MetaMask ${manifest.version} at ${extensionDir}`, { required: extensionRequired }));
247
- } else {
248
- checks.push(check('metamask-extension', false, `no extension dist at ${extensionDir}`, { required: extensionRequired }));
249
- }
250
- const checkout = env.TERMINAL_EXTENSION_CHECKOUT;
251
- const checkoutOk = Boolean(checkout) && existsSync(path.join(checkout, 'node_modules/@metamask/browser-passworder'));
252
- checks.push(check('extension-fixture-tooling', checkoutOk, checkoutOk
253
- ? `extension checkout ${checkout} has fixture-state dependencies`
254
- : 'TERMINAL_EXTENSION_CHECKOUT unset or not installed (needed to import the wallet into the extension)', { required: extensionRequired }));
255
-
256
- const state = await readBrowserState(target);
257
- const runtime = webDappRuntimeDir(target);
258
- const live = Boolean(state && ownsProcess('browser', state.browserPid, runtime) && ownsProcess('wallet-host', state.hostPid, runtime));
259
- if (cdpPort) {
260
- const free = await portFree(cdpPort);
261
- const owned = live && state.cdpPort === cdpPort;
262
- checks.push(check('cdp-port', free || owned, free ? `CDP port ${cdpPort} is free` : owned ? `CDP port ${cdpPort} held by this slot's browser` : `CDP port ${cdpPort} is held by another process`));
263
- } else {
264
- checks.push(check('cdp-port', false, 'no CDP port; pass --cdp-port <port>'));
265
- }
266
- checks.push(check('browser-live', live, live
267
- ? `browser pid ${state.browserPid} (${state.signer}, ${state.account?.name}) and wallet host pid ${state.hostPid}`
268
- : 'no slot browser running (metamask.app.launch starts it)', { required: false }));
269
-
270
- const failed = checks.filter((item) => item.required && item.status === 'fail').map((item) => item.id);
271
- return { status: failed.length === 0 ? 'pass' : 'fail', checks, failed, browser: live ? state : null };
272
- }
@@ -1,38 +0,0 @@
1
- #!/usr/bin/env node
2
- // stop.mjs — stop the web-dapp slot browser, wallet host and console collector, verify the CDP port
3
- // is free. Idempotent: nothing running is success.
4
- //
5
- // Inputs: --target <checkout> [--cdp-port <port>] [--json]
6
- // Outputs: JSON summary on stdout. Exit 0 stopped/nothing to stop; 1 port still owned.
7
- // Never touches: the Next.js dev server (Farmslot owns it), browser profiles.
8
-
9
- import path from 'node:path';
10
- import { fileURLToPath } from 'node:url';
11
-
12
- import { webDappPolicy } from '../../library/actions/web-dapp/platform/runtime.mjs';
13
- import { stopConsoleCollector, stopWebDappBrowser } from './lib/processes.mjs';
14
-
15
- const usage = 'Usage: stop.mjs --target <checkout> [--cdp-port <port>] [--json]';
16
-
17
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
18
- const argv = process.argv.slice(2);
19
- if (argv.includes('--help') || argv.includes('-h')) {
20
- process.stdout.write(`${usage}\n`);
21
- process.exit(0);
22
- }
23
- const value = (flag) => {
24
- const index = argv.indexOf(flag);
25
- return index >= 0 ? argv[index + 1] : undefined;
26
- };
27
- const target = value('--target') ?? process.cwd();
28
- const rawPort = value('--cdp-port') ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
29
- try {
30
- const log = (message) => process.stderr.write(`[${webDappPolicy().adapterId}/stop] ${message}\n`);
31
- const result = await stopWebDappBrowser(target, { cdpPort: rawPort ? Number(rawPort) : undefined, log });
32
- const collector = await stopConsoleCollector(target, { log });
33
- process.stdout.write(`${JSON.stringify({ status: 'pass', ...result, consoleCollector: collector })}\n`);
34
- } catch (error) {
35
- process.stdout.write(`${JSON.stringify({ status: 'fail', error: error.message })}\n`);
36
- process.exit(1);
37
- }
38
- }
@@ -1,51 +0,0 @@
1
- #!/usr/bin/env node
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
- // browser start probe, MetaMask extension copy, CDP port).
5
- //
6
- // Inputs: --target <checkout> [--cdp-port <port>] [--watcher-port <app port>]
7
- // [--signer extension|injected] [--account <name>] [--json]
8
- // Outputs: readiness JSON (or a human summary) on stdout. Exit 0 ready; 1 not ready.
9
- // Never launches the slot browser or the dev server.
10
-
11
- import path from 'node:path';
12
- import { fileURLToPath } from 'node:url';
13
-
14
- import { resolveBrowser } from './launch.mjs';
15
- import { webDappPolicy } from '../../library/actions/web-dapp/platform/runtime.mjs';
16
- import { webDappReadiness } from './lib/readiness.mjs';
17
-
18
- const usage = 'Usage: verify.mjs --target <checkout> [--cdp-port <port>] [--watcher-port <app port>] [--signer extension|injected] [--account <name>] [--json]';
19
-
20
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
21
- const argv = process.argv.slice(2);
22
- if (argv.includes('--help') || argv.includes('-h')) {
23
- process.stdout.write(`${usage}\n`);
24
- process.exit(0);
25
- }
26
- const value = (flag) => {
27
- const index = argv.indexOf(flag);
28
- return index >= 0 ? argv[index + 1] : undefined;
29
- };
30
- const num = (raw) => (raw && /^\d+$/u.test(raw) ? Number(raw) : undefined);
31
- const policy = webDappPolicy();
32
- const report = await webDappReadiness({
33
- policy,
34
- target: path.resolve(value('--target') ?? process.cwd()),
35
- appPort: num(value('--watcher-port') ?? process.env.TERMINAL_APP_PORT ?? process.env.WATCHER_PORT),
36
- cdpPort: num(value('--cdp-port') ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT),
37
- signer: value('--signer') ?? process.env.TERMINAL_SIGNER ?? 'extension',
38
- account: value('--account') ?? process.env.TERMINAL_ACCOUNT ?? 'dev1',
39
- probeBrowser: resolveBrowser,
40
- });
41
- if (argv.includes('--json')) {
42
- process.stdout.write(`${JSON.stringify({ schemaVersion: 1, command: 'verify', adapter: policy.adapterId, ...report }, null, 2)}\n`);
43
- } else {
44
- for (const check of report.checks) {
45
- const mark = check.status === 'pass' ? '✓' : check.status === 'warn' || !check.required ? '·' : '✗';
46
- process.stdout.write(`${mark} ${check.id}: ${check.detail}\n`);
47
- }
48
- process.stdout.write(report.status === 'pass' ? `${policy.adapterId} slot ready\n` : `not ready: ${report.failed.join(', ')}\n`);
49
- }
50
- process.exit(report.status === 'pass' ? 0 : 1);
51
- }