@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.
- package/CHANGELOG.md +18 -0
- package/adapters/extension/console-tail.mjs +38 -6
- package/adapters/manifest.json +128 -0
- package/adapters/terminal/cleanup.mjs +68 -0
- package/adapters/terminal/inject.mjs +74 -0
- package/adapters/terminal/launch.mjs +628 -0
- package/adapters/terminal/lib/cdp-client.mjs +84 -0
- package/adapters/terminal/lib/display.mjs +115 -0
- package/adapters/terminal/lib/hud.mjs +148 -0
- package/adapters/terminal/lib/mainnet-guard.mjs +177 -0
- package/adapters/terminal/lib/origin.mjs +30 -0
- package/adapters/terminal/lib/page-script.mjs +246 -0
- package/adapters/terminal/lib/processes.mjs +187 -0
- package/adapters/terminal/lib/readiness.mjs +274 -0
- package/adapters/terminal/lib/strict-wallet.mjs +138 -0
- package/adapters/terminal/stop.mjs +37 -0
- package/adapters/terminal/verify.mjs +48 -0
- package/adapters/terminal/wallet-host.mjs +703 -0
- package/dist/adapters/console-capture.js +122 -0
- package/dist/adapters/extension/console-capture.js +22 -85
- package/dist/adapters/surface.js +3 -1
- package/dist/adapters/terminal/console-capture.js +46 -0
- package/dist/adapters/terminal/stop.js +19 -0
- package/dist/adapters/terminal/surface.js +104 -0
- package/dist/adapters.js +27 -4
- package/dist/cli.js +2 -2
- package/dist/command-contract.js +12 -4
- package/dist/commands/check.js +1 -1
- package/dist/commands/config.js +1 -1
- package/dist/commands/doctor.js +24 -1
- package/dist/commands/domain.js +1 -1
- package/dist/commands/farmslot-ready.js +2 -0
- package/dist/commands/help.js +2 -2
- package/dist/commands/launch/index.js +97 -2
- package/dist/commands/manifest.js +3 -3
- package/dist/commands/recipe-advice.js +1 -1
- package/dist/commands/review.js +1 -1
- package/dist/commands/run-engine.js +1 -1
- package/dist/commands/shared.js +1 -1
- package/dist/doctor.js +3 -0
- package/dist/harness.js +17 -11
- package/dist/manifest.js +5 -0
- package/dist/mm-harness-cli.js +34 -20
- package/dist/paths.js +2 -2
- package/dist/recipe-security.js +12 -3
- package/dist/review/knowledge.js +1 -1
- package/dist/run-diagnostics.js +150 -33
- package/dist/runner.js +1 -1
- package/dist/runtime-context.js +3 -3
- package/docs/CONTRIBUTING.md +1 -1
- package/library/actions/shared/console-allowlist-add.mjs +137 -0
- package/library/actions/shared/console-collector.mjs +230 -0
- package/library/actions/shared/console-findings.mjs +511 -0
- package/library/actions/terminal/app/assert_no_console_errors.mjs +119 -0
- package/library/actions/terminal/app/launch.mjs +45 -0
- package/library/actions/terminal/perps/_venue.mjs +242 -0
- package/library/actions/terminal/perps/assert_orders.mjs +54 -0
- package/library/actions/terminal/perps/assert_positions.mjs +40 -0
- package/library/actions/terminal/perps/read_orders.mjs +26 -0
- package/library/actions/terminal/perps/read_positions.mjs +25 -0
- package/library/actions/terminal/perps/read_snapshot.mjs +40 -0
- package/library/actions/terminal/perps/revoke_agent.mjs +58 -0
- package/library/actions/terminal/perps/teardown_state.mjs +104 -0
- package/library/actions/terminal/platform/hud.mjs +71 -0
- package/library/actions/terminal/platform/page.mjs +247 -0
- package/library/actions/terminal/platform/runtime.mjs +200 -0
- package/library/actions/terminal/ui/navigate.mjs +31 -0
- package/library/actions/terminal/wallet/_signature-log.mjs +177 -0
- package/library/actions/terminal/wallet/assert_signatures.mjs +43 -0
- package/library/actions/terminal/wallet/list_accounts.mjs +27 -0
- package/library/actions/terminal/wallet/read_signatures.mjs +37 -0
- package/library/manifests/terminal.action-manifest.json +1786 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.71.0 - 2026-10-02
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `terminal` adapter for the MetaMask Web Terminal (Next.js site): `mm-harness launch|doctor|verify|install|cleanup|stop|run --adapter terminal` drive a per-slot Chrome for Testing on `--cdp-port` against the slot dev server on `--watcher-port`. `signer=extension` loads a MetaMask build with the fixture account imported; `signer=injected` is a strict EIP-1193 test wallet that rejects typed data for another chain. A wallet host logs every wallet request (method, EIP-712 primary type, chain, outcome; never params or signatures) and every MetaMask confirmation shown.
|
|
10
|
+
- Terminal actions: `metamask.app.launch`, venue-backed `metamask.perps.read_positions|read_orders|read_snapshot|assert_positions|assert_orders`, testnet `metamask.perps.teardown_state` and `metamask.perps.revoke_agent` (signed in Node by the fixture account; mainnet only with `mainnet_confirmation=REAL_FUNDS`), `metamask.wallet.read_signatures|assert_signatures|list_accounts`, and `ui.*` with `surface: app|wallet` to drive the MetaMask side panel / notification.
|
|
11
|
+
- Terminal browser is visible by default, one window per slot side by side on the main screen (odd CDP port left, even right; `TERMINAL_WINDOW` / `TERMINAL_SCREEN` override) with a profile zoom that keeps the desktop layout. `--headless`, launch node `headless: true` or `TERMINAL_HEADLESS=1` hide it; `--headful` stays accepted. `--slow-mo <ms>` / `slow_mo_ms` / `TERMINAL_SLOW_MO` pause before each UI action. Terminal `app.hud` draws the recipe, step N/M, current intent and last result on the app tab (closed shadow root outside `<body>`, pointer-events none, redrawn after navigation, never on MetaMask pages); `--hud auto` shows it only in a visible browser.
|
|
12
|
+
- Terminal testnet enforcement: every mainnet host the venue SDK names, and the API and base URLs of `@metamask/perps-controller` and the app source, are unresolvable for the whole browser and blocked by the wallet host (`blocked-mainnet`); mainnet Hyperliquid typed data is refused before any signer sees it (`refused-mainnet`). The venues' web hosts (`app.hyperliquid.xyz`, `app.hyperliquid-testnet.xyz`) are neither blocked nor proof of the served network, and a launch fails if any scanned source uses one with an API path. Source URLs are compared in canonical form (parsed: lowercase host without a trailing dot, default port dropped, dot segments resolved), and the host resolver rules cover each host's trailing-dot form. The launch proves both blocks with probes and that the app talks to a testnet API host, and fails if the SDK names a mainnet host missing from the block list. Mainnet needs `network: mainnet` with `mainnet_confirmation: REAL_FUNDS`. A browser is reused only while it enforces the same host list.
|
|
13
|
+
- Terminal slot safety: slot processes carry `--mm-harness-owner` markers and only proven-owned ones are signalled; wallet calls are judged against their own document's commit, and anything that can't be (including calls pending when a tab detaches) is logged as `unattributed`; a failed launch stops what it started and removes temporary key material; a first page that never commits is reported with what the tab and the dev server did. Only `TERMINAL_CHROME_BIN` overrides the browser.
|
|
14
|
+
- Terminal focus: a visible browser starts in the background through LaunchServices with no startup window; one check after the launch (shared `macos-focus.cjs`, `restoreFrontmostIfOurs`) gives the front back once, by pid, only if this slot's browser took it, and the wallet host only observes. Pages come to front only with `MM_HARNESS_FOCUS_BROWSER=1`.
|
|
15
|
+
- Terminal run diagnostics: recipe runs start the shared console collector for the slot (`terminal/app-console.log` for the app page, `terminal/extension-console.log` for MetaMask; `mm-harness logs` lists both and the wallet request log), and the run's non-probe `blocked-mainnet`, `refused-mainnet` and `unattributed` wallet entries are findings. `metamask.app.assert_no_console_errors` fails a recipe on unexplained page, browser or extension errors.
|
|
16
|
+
- Console allowlists (`diagnostics/console-allowlist.json`, `schemaVersion: 3`, earlier versions refused): exact signatures of an event's first line, continuation and optional location, compared by string equality through one matcher, `library/actions/shared/console-findings.mjs`, which recipe-library guards also call. Normalisation only touches parsed fields: URLs (Next.js scope only on a plain path: every segment made of letters, digits, `.`, `_`, `@`, `-` and `%40`, never `.` or `..`) in a loopback port, a `/_next/static/` asset name (its directory and extension kept), `dpl`/`v` on `/_next/` paths and an unpacked extension id; frames and locations lose their position; other text only in times, UUIDs and hex of 9+ digits. `library/actions/shared/console-allowlist-add.mjs <run artifacts dir>` writes entries from a real run.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Console collector capture files are framed: every line after an event's first starts with `│ `, and a browser Log entry's `[browser:<source>]` tag sits in the record header before the level word, where message text can't put it; its location is its URL alone, without a line number. **Logs from older collectors:** each unprefixed line is a finding of its own (at least an error) and their browser entries read as page events, so they report REVIEW.
|
|
21
|
+
- Run diagnostics and `assert_no_console_errors` require capture evidence: this runtime's own collector (a process running this checkout's `console-tail.mjs` as its script, with its exact argument list, so install and runtime paths may contain spaces; owner marker; a marker-less collector for the same log is replaced once, and this runtime's collector started by another harness install is retired before its replacement starts; capture files rotate past 32 MB) and a control line logged in the captured target that reaches the capture file (for the Extension, from MetaMask's own extension id). **Extension consumers:** a run whose capture can't be proven at the end reports UNAVAILABLE instead of CLEAN, so the browser must still be open then; a capture over the 512 KB read limit with no findings is UNAVAILABLE too. Capture control lines are never findings.
|
|
22
|
+
|
|
5
23
|
## 0.70.1 - 2026-10-02
|
|
6
24
|
|
|
7
25
|
### Fixed
|
|
@@ -39,11 +39,13 @@ function parseArgs(argv) {
|
|
|
39
39
|
case '-h':
|
|
40
40
|
case '--help':
|
|
41
41
|
process.stdout.write(
|
|
42
|
-
'Usage: console-tail.mjs --cdp-port <port> [--extension-log <file>] [--dapp-log <file>]\n',
|
|
42
|
+
'Usage: console-tail.mjs --cdp-port <port> [--extension-log <file>] [--dapp-log <file>] [--mm-harness-owner=<marker>]\n',
|
|
43
43
|
);
|
|
44
44
|
process.exit(0);
|
|
45
45
|
break;
|
|
46
46
|
default:
|
|
47
|
+
// An ownership marker the caller identifies its collector by.
|
|
48
|
+
if (arg.startsWith('--mm-harness-owner=')) break;
|
|
47
49
|
throw new Error(`Unknown argument: ${arg}`);
|
|
48
50
|
}
|
|
49
51
|
}
|
|
@@ -73,7 +75,13 @@ const cooldown = new Map();
|
|
|
73
75
|
// Writes formatted console-event strings (never raw HTTP response bodies) to
|
|
74
76
|
// stdout and optionally to a caller-supplied log file. The source is the local
|
|
75
77
|
// Chrome CDP WebSocket (127.0.0.1:<cdpPort>) — a trusted-local endpoint.
|
|
76
|
-
|
|
78
|
+
// Every line after an event's first is written with this prefix, so text
|
|
79
|
+
// inside a message can never read as the start of another event.
|
|
80
|
+
const CONTINUATION_PREFIX = '│ ';
|
|
81
|
+
|
|
82
|
+
function out(event, destination = null) {
|
|
83
|
+
const [first, ...rest] = String(event).split('\n');
|
|
84
|
+
const line = [first, ...rest.map((continued) => `${CONTINUATION_PREFIX}${continued}`)].join('\n');
|
|
77
85
|
process.stdout.write(`${line}\n`);
|
|
78
86
|
if (destination) {
|
|
79
87
|
// This file is explicitly an untrusted application-console transcript; it is never executed or parsed as configuration.
|
|
@@ -136,10 +144,23 @@ function previewArg(arg) {
|
|
|
136
144
|
return arg.subtype || arg.type || '?';
|
|
137
145
|
}
|
|
138
146
|
|
|
147
|
+
// Where an event came from, appended as " (at <frame>)": the first stack
|
|
148
|
+
// frame of a console call or exception, the URL of a browser log entry. Run
|
|
149
|
+
// diagnostics group findings by message and report this frame.
|
|
150
|
+
function frameText(callFrame) {
|
|
151
|
+
if (!callFrame?.url) return '';
|
|
152
|
+
return `${callFrame.functionName || '<anonymous>'} ${callFrame.url}:${(callFrame.lineNumber ?? 0) + 1}:${(callFrame.columnNumber ?? 0) + 1}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function atSuffix(frame) {
|
|
156
|
+
return frame ? ` (at ${frame})` : '';
|
|
157
|
+
}
|
|
158
|
+
|
|
139
159
|
function emitConsole(label, params, destination) {
|
|
140
160
|
const level = String(params.type || 'log').toUpperCase();
|
|
141
161
|
const text = (params.args || []).map(previewArg).join(' ');
|
|
142
|
-
|
|
162
|
+
const [first, ...rest] = text.split('\n');
|
|
163
|
+
out(`${stamp()} [${label}] ${level} ${first}${atSuffix(frameText(params.stackTrace?.callFrames?.[0]))}${rest.length ? `\n${rest.join('\n')}` : ''}`, destination);
|
|
143
164
|
}
|
|
144
165
|
|
|
145
166
|
function emitException(label, params, destination) {
|
|
@@ -149,13 +170,23 @@ function emitException(label, params, destination) {
|
|
|
149
170
|
d.exception?.value ||
|
|
150
171
|
d.text ||
|
|
151
172
|
'uncaught exception';
|
|
152
|
-
|
|
173
|
+
const frame = frameText(d.stackTrace?.callFrames?.[0]) || frameText({ url: d.url, lineNumber: d.lineNumber, columnNumber: d.columnNumber });
|
|
174
|
+
const [first, ...rest] = String(text).split('\n');
|
|
175
|
+
out(`${stamp()} [${label}] EXCEPTION ${first}${atSuffix(frame)}${rest.length ? `\n${rest.join('\n')}` : ''}`, destination);
|
|
153
176
|
}
|
|
154
177
|
|
|
178
|
+
// Browser log entries (network failures, interventions, deprecations) are
|
|
179
|
+
// tagged with their Log source in the record header, before the level word,
|
|
180
|
+
// e.g. "[dapp:page:BTC] [browser:network] ERROR …": message text (written
|
|
181
|
+
// after the level word) can never produce the tag.
|
|
155
182
|
function emitLogEntry(label, params, destination) {
|
|
156
183
|
const e = params.entry || {};
|
|
157
184
|
const level = String(e.level || 'log').toUpperCase();
|
|
158
|
-
|
|
185
|
+
const source = String(e.source || 'other').replace(/[^a-z-]/gu, '') || 'other';
|
|
186
|
+
// A Log entry's location is its URL as is: a line number appended to a URL
|
|
187
|
+
// could not be told apart from the URL's own text.
|
|
188
|
+
const where = e.url ? e.url : frameText(e.stackTrace?.callFrames?.[0]);
|
|
189
|
+
out(`${stamp()} [${label}] [browser:${source}] ${level} ${e.text || ''}${atSuffix(where)}`, destination);
|
|
159
190
|
}
|
|
160
191
|
|
|
161
192
|
function handleMessage(label, destination, data) {
|
|
@@ -185,7 +216,8 @@ function handleMessage(label, destination, data) {
|
|
|
185
216
|
function attach(target) {
|
|
186
217
|
const stream = targetStream(target);
|
|
187
218
|
if (!stream) return;
|
|
188
|
-
|
|
219
|
+
// No brackets or spaces: the label is a header field.
|
|
220
|
+
const label = `${stream.kind}:${shortLabel(target)}`.replace(/[[\]\s]/gu, '_');
|
|
189
221
|
const ws = new WebSocket(target.webSocketDebuggerUrl);
|
|
190
222
|
const entry = { ws, label, destination: stream.log, opened: false };
|
|
191
223
|
attached.set(target.id, entry);
|
package/adapters/manifest.json
CHANGED
|
@@ -638,6 +638,134 @@
|
|
|
638
638
|
"purpose": "Bootstrap numbered MetaMask checkouts through the setup-base command.",
|
|
639
639
|
"inputs": "setup-base command arguments",
|
|
640
640
|
"outputs": "numbered checkouts, saved preferences, and run summary"
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
"id": "terminal/launch",
|
|
644
|
+
"entry": "adapters/terminal/launch.mjs",
|
|
645
|
+
"kind": "node",
|
|
646
|
+
"purpose": "Start or reuse the Web Terminal slot browser (Chrome for Testing unless TERMINAL_CHROME_BIN; MetaMask extension with the fixture imported, or the injected strict wallet) and its wallet host. On macOS the browser starts through LaunchServices with no startup window; in testnet mode the venue SDK's mainnet hosts are unresolvable for the whole browser.",
|
|
647
|
+
"inputs": "--target --cdp-port --app-port [--signer extension|injected] [--account] [--fresh-profile] [--headless|--headful] [--slow-mo] [--network mainnet --mainnet-confirmation REAL_FUNDS] [--json]; env TERMINAL_CHROME_BIN, TERMINAL_EXTENSION_DIR, TERMINAL_EXTENSION_CHECKOUT, RECIPE_WALLET_FIXTURE, TERMINAL_HEADLESS, TERMINAL_SLOW_MO, TERMINAL_WINDOW, TERMINAL_SCREEN",
|
|
648
|
+
"outputs": "<runtime>/terminal/browser.json + pid/log files; JSON on stdout; exit 0/1/2"
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
"id": "terminal/wallet-host",
|
|
652
|
+
"entry": "adapters/terminal/wallet-host.mjs",
|
|
653
|
+
"kind": "node",
|
|
654
|
+
"purpose": "Long-running CDP observer: opens the app tab blank, hooks it once per target, navigates it to the app and verifies the log binding before ready; logs every wallet request from the app top frame and every MetaMask confirmation shown; answers the injected strict wallet in injected mode (other frames and blank popups get 4100).",
|
|
655
|
+
"inputs": "--cdp-port --runtime-dir --signer --app-origin [--extension-id] [--target --account --start-chain] [--browser-app <.app>]",
|
|
656
|
+
"outputs": "<runtime>/terminal/wallet-requests.jsonl, wallet-host.ready"
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
"id": "terminal/stop",
|
|
660
|
+
"entry": "adapters/terminal/stop.mjs",
|
|
661
|
+
"kind": "node",
|
|
662
|
+
"purpose": "Stop the terminal slot browser, wallet host and console collector and prove the CDP port is free.",
|
|
663
|
+
"inputs": "--target [--cdp-port]",
|
|
664
|
+
"outputs": "JSON on stdout; exit 0/1"
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
"id": "terminal/inject",
|
|
668
|
+
"entry": "adapters/terminal/inject.mjs",
|
|
669
|
+
"kind": "node",
|
|
670
|
+
"purpose": "Terminal install: overlay marker, runtime dir and wallet fixture (0600); no product writes.",
|
|
671
|
+
"inputs": "--target [--fixture] [--force]; env RECIPE_WALLET_FIXTURE",
|
|
672
|
+
"outputs": "<harness>/terminal/install.json, <runtime>/wallet-fixture.json; exit 0/1"
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
"id": "terminal/cleanup",
|
|
676
|
+
"entry": "adapters/terminal/cleanup.mjs",
|
|
677
|
+
"kind": "node",
|
|
678
|
+
"purpose": "Terminal cleanup: stop browser/host, remove terminal runtime state, overlay marker and installed fixture; profiles kept unless --reset-profile.",
|
|
679
|
+
"inputs": "--target [--cdp-port] [--reset-profile]",
|
|
680
|
+
"outputs": "JSON on stdout; exit 0/1"
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
"id": "terminal/verify",
|
|
684
|
+
"entry": "adapters/terminal/verify.mjs",
|
|
685
|
+
"kind": "node",
|
|
686
|
+
"purpose": "Read-only terminal slot readiness: checkout, deps, dev server, forced testnet, fixture, browser start probe, extension copy, CDP port.",
|
|
687
|
+
"inputs": "--target [--cdp-port] [--watcher-port] [--signer] [--account] [--json]",
|
|
688
|
+
"outputs": "readiness report; exit 0 ready / 1 not ready"
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
"id": "terminal/lib-cdp-client",
|
|
692
|
+
"entry": "adapters/terminal/lib/cdp-client.mjs",
|
|
693
|
+
"kind": "lib",
|
|
694
|
+
"purpose": "Browser-level CDP client with flattened target sessions for the wallet host.",
|
|
695
|
+
"inputs": "CdpClient.connect(url)",
|
|
696
|
+
"outputs": "send/on"
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
"id": "terminal/lib-page-script",
|
|
700
|
+
"entry": "adapters/terminal/lib/page-script.mjs",
|
|
701
|
+
"kind": "lib",
|
|
702
|
+
"purpose": "Page script source: wallet request logging and the injected strict provider (EIP-6963 io.metamask).",
|
|
703
|
+
"inputs": "pageScriptSource({ signer, appOrigin })",
|
|
704
|
+
"outputs": "script source string"
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
"id": "terminal/lib-strict-wallet",
|
|
708
|
+
"entry": "adapters/terminal/lib/strict-wallet.mjs",
|
|
709
|
+
"kind": "lib",
|
|
710
|
+
"purpose": "EIP-1193 strict test wallet enforcing typed-data chainId == active chain.",
|
|
711
|
+
"inputs": "createStrictWallet({ account, chainId })",
|
|
712
|
+
"outputs": "request()"
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
"id": "terminal/lib-processes",
|
|
716
|
+
"entry": "adapters/terminal/lib/processes.mjs",
|
|
717
|
+
"kind": "lib",
|
|
718
|
+
"purpose": "Pid-file process stop with owner-marker checks (--mm-harness-owner=<kind>-<id>, exact token; live foreign pids kept and reported, dead ones dropped), process-group termination, CDP port release check.",
|
|
719
|
+
"inputs": "stopTerminalBrowser(target, { cdpPort }), stopConsoleCollector(target), ownerMarker(kind, runtimeDir), ownsProcess(kind, pid, runtimeDir)",
|
|
720
|
+
"outputs": "stopped processes"
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
"id": "terminal/lib-readiness",
|
|
724
|
+
"entry": "adapters/terminal/lib/readiness.mjs",
|
|
725
|
+
"kind": "lib",
|
|
726
|
+
"purpose": "Terminal slot readiness checks shared by verify, doctor and the surface.",
|
|
727
|
+
"inputs": "terminalReadiness({ target, appPort, cdpPort, signer }), assertLaunchNetwork(target, { network, mainnetConfirmation }), devServerTestnetDiagnostic(target, { appPort }) (advisory)",
|
|
728
|
+
"outputs": "checks[]"
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
"id": "terminal/lib-display",
|
|
732
|
+
"entry": "adapters/terminal/lib/display.mjs",
|
|
733
|
+
"kind": "lib",
|
|
734
|
+
"purpose": "Headless/headful and slow-mo resolution, per-slot window placement and the profile zoom that keeps the desktop layout.",
|
|
735
|
+
"inputs": "resolveHeadless, resolveSlowMo, windowPlacement, writeProfileZoom",
|
|
736
|
+
"outputs": "browser display args"
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
"id": "terminal/lib-hud",
|
|
740
|
+
"entry": "adapters/terminal/lib/hud.mjs",
|
|
741
|
+
"kind": "lib",
|
|
742
|
+
"purpose": "Recipe HUD state (<runtime>/hud.json) and the page expression that draws it on the app origin only.",
|
|
743
|
+
"inputs": "nextHudState, hudRenderExpression",
|
|
744
|
+
"outputs": "HUD state, expression source"
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
"id": "terminal/lib-origin",
|
|
748
|
+
"entry": "adapters/terminal/lib/origin.mjs",
|
|
749
|
+
"kind": "lib",
|
|
750
|
+
"purpose": "Exact app-origin and app top-frame checks for targets, wallet requests and log entries.",
|
|
751
|
+
"inputs": "isAppUrl(url, appOrigin), isAppTopFrameContext(context, { targetId, appOrigin })",
|
|
752
|
+
"outputs": "boolean"
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
"id": "terminal/lib-mainnet-guard",
|
|
756
|
+
"entry": "adapters/terminal/lib/mainnet-guard.mjs",
|
|
757
|
+
"kind": "lib",
|
|
758
|
+
"purpose": "Testnet enforcement: the venue endpoint hosts (from the venue SDK, @metamask/perps-controller and the app source in the checkout), the browser's host-resolver block, and the mainnet typed-data classifier used by the page script and the wallet host.",
|
|
759
|
+
"inputs": "venueHosts(checkout), hostResolverRules(hosts), isMainnetVenueUrl(url, hosts), mainnetTypedDataReason(data)",
|
|
760
|
+
"outputs": "host list, launch switch, refusal reason"
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
"id": "terminal/surface",
|
|
764
|
+
"entry": "src/adapters/terminal/surface.ts",
|
|
765
|
+
"kind": "module",
|
|
766
|
+
"purpose": "Terminal adapter surface (runtime status, browser stop, logs, hints).",
|
|
767
|
+
"inputs": "terminalSurface",
|
|
768
|
+
"outputs": "AdapterSurface"
|
|
641
769
|
}
|
|
642
770
|
]
|
|
643
771
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cleanup.mjs — `mm-harness cleanup --adapter terminal`: stop the slot browser
|
|
3
|
+
// and wallet host, then remove the terminal runtime state and the installed
|
|
4
|
+
// wallet fixture. Browser profiles are kept unless --reset-profile.
|
|
5
|
+
//
|
|
6
|
+
// Inputs: --target <checkout> [--cdp-port <port>] [--reset-profile]
|
|
7
|
+
// Outputs: JSON summary on stdout. Exit 0 cleaned; 1 the CDP port stayed busy.
|
|
8
|
+
// Never touches: product source files, the Next.js dev server.
|
|
9
|
+
|
|
10
|
+
import { existsSync, readdirSync, rmSync } from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
import { recipeHarnessPath, walletFixturePath } from '../../library/actions/harness-exports.mjs';
|
|
15
|
+
import { terminalRuntimeDir } from '../../library/actions/terminal/platform/runtime.mjs';
|
|
16
|
+
import { stopConsoleCollector, stopTerminalBrowser } from './lib/processes.mjs';
|
|
17
|
+
|
|
18
|
+
const usage = 'Usage: cleanup.mjs --target <checkout> [--cdp-port <port>] [--reset-profile]';
|
|
19
|
+
|
|
20
|
+
export async function cleanupTerminalRuntime({ target, cdpPort, resetProfile = false }) {
|
|
21
|
+
const root = path.resolve(target);
|
|
22
|
+
const stop = await stopTerminalBrowser(root, { cdpPort });
|
|
23
|
+
await stopConsoleCollector(root);
|
|
24
|
+
const runtime = terminalRuntimeDir(root);
|
|
25
|
+
const removed = [];
|
|
26
|
+
if (existsSync(runtime)) {
|
|
27
|
+
for (const entry of readdirSync(runtime)) {
|
|
28
|
+
if (entry.startsWith('profile-') && !resetProfile) continue;
|
|
29
|
+
rmSync(path.join(runtime, entry), { recursive: true, force: true });
|
|
30
|
+
removed.push(entry);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const overlay = recipeHarnessPath(root, 'terminal');
|
|
34
|
+
if (existsSync(overlay)) {
|
|
35
|
+
rmSync(overlay, { recursive: true, force: true });
|
|
36
|
+
removed.push(path.relative(root, overlay));
|
|
37
|
+
}
|
|
38
|
+
const fixture = walletFixturePath(root);
|
|
39
|
+
if (existsSync(fixture)) {
|
|
40
|
+
rmSync(fixture, { force: true });
|
|
41
|
+
removed.push(path.relative(root, fixture));
|
|
42
|
+
}
|
|
43
|
+
return { status: 'pass', target: root, stopped: stop.stopped, cdpPortFree: stop.cdpPortFree, removed };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
47
|
+
const argv = process.argv.slice(2);
|
|
48
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
49
|
+
process.stdout.write(`${usage}\n`);
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
const value = (flag) => {
|
|
53
|
+
const index = argv.indexOf(flag);
|
|
54
|
+
return index >= 0 ? argv[index + 1] : undefined;
|
|
55
|
+
};
|
|
56
|
+
const rawPort = value('--cdp-port') ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
|
|
57
|
+
try {
|
|
58
|
+
const result = await cleanupTerminalRuntime({
|
|
59
|
+
target: value('--target') ?? process.cwd(),
|
|
60
|
+
cdpPort: rawPort ? Number(rawPort) : undefined,
|
|
61
|
+
resetProfile: argv.includes('--reset-profile'),
|
|
62
|
+
});
|
|
63
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
process.stdout.write(`${JSON.stringify({ status: 'fail', error: error.message })}\n`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// inject.mjs — `mm-harness install --adapter terminal`: prepare the slot runtime
|
|
3
|
+
// directory and install the wallet fixture the terminal actions read.
|
|
4
|
+
//
|
|
5
|
+
// Inputs: --target <terminal checkout> [--fixture <wallet-fixture.json>] [--force]
|
|
6
|
+
// env RECIPE_WALLET_FIXTURE (fixture source when --fixture is absent)
|
|
7
|
+
// Outputs: <target>/<harness root>/terminal/install.json (overlay marker),
|
|
8
|
+
// <target>/<runtime>/terminal/ (0700), <runtime>/wallet-fixture.json (0600);
|
|
9
|
+
// JSON summary on stdout (account names only). Exit 0 installed; 1 failure; 2 usage.
|
|
10
|
+
// Never touches: product source files; never prints key material.
|
|
11
|
+
|
|
12
|
+
import { copyFileSync, chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
import { recipeHarnessPath, recipeRuntimeDir, walletFixturePath } from '../../library/actions/harness-exports.mjs';
|
|
17
|
+
import { terminalRuntimeDir } from '../../library/actions/terminal/platform/runtime.mjs';
|
|
18
|
+
import { isTerminalCheckout } from './lib/readiness.mjs';
|
|
19
|
+
|
|
20
|
+
const usage = 'Usage: inject.mjs --target <terminal checkout> [--fixture <wallet-fixture.json>] [--force]';
|
|
21
|
+
|
|
22
|
+
export function installTerminalRuntime({ target, fixture, force = false }) {
|
|
23
|
+
const root = path.resolve(target);
|
|
24
|
+
if (!isTerminalCheckout(root)) throw new Error(`${root} is not a Web Terminal checkout (needs next + src/features/perpetuals).`);
|
|
25
|
+
mkdirSync(path.join(root, recipeRuntimeDir()), { recursive: true, mode: 0o700 });
|
|
26
|
+
mkdirSync(terminalRuntimeDir(root), { recursive: true, mode: 0o700 });
|
|
27
|
+
const destination = walletFixturePath(root);
|
|
28
|
+
let fixtureAction = 'kept';
|
|
29
|
+
if (fixture && (force || !existsSync(destination))) {
|
|
30
|
+
const parsed = JSON.parse(readFileSync(fixture, 'utf8'));
|
|
31
|
+
if (!Array.isArray(parsed.accounts) || parsed.accounts.length === 0) throw new Error(`${fixture} has no accounts.`);
|
|
32
|
+
copyFileSync(fixture, destination);
|
|
33
|
+
chmodSync(destination, 0o600);
|
|
34
|
+
fixtureAction = 'installed';
|
|
35
|
+
} else if (!existsSync(destination)) {
|
|
36
|
+
fixtureAction = 'missing';
|
|
37
|
+
}
|
|
38
|
+
const accounts = existsSync(destination)
|
|
39
|
+
? JSON.parse(readFileSync(destination, 'utf8')).accounts.map((account) => account?.name).filter(Boolean)
|
|
40
|
+
: [];
|
|
41
|
+
const marker = { schemaVersion: 1, adapter: 'terminal', installedAt: new Date().toISOString(), fixture: fixtureAction };
|
|
42
|
+
// The overlay directory is the install marker run/call auto-ensure checks.
|
|
43
|
+
const overlay = recipeHarnessPath(root, 'terminal');
|
|
44
|
+
mkdirSync(overlay, { recursive: true, mode: 0o700 });
|
|
45
|
+
writeFileSync(path.join(overlay, 'install.json'), `${JSON.stringify(marker, null, 2)}\n`, { mode: 0o600 });
|
|
46
|
+
return { status: fixtureAction === 'missing' ? 'fail' : 'pass', target: root, fixture: fixtureAction, fixturePath: destination, accounts };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
50
|
+
const argv = process.argv.slice(2);
|
|
51
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
52
|
+
process.stdout.write(`${usage}\n`);
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
const value = (flag) => {
|
|
56
|
+
const index = argv.indexOf(flag);
|
|
57
|
+
return index >= 0 ? argv[index + 1] : undefined;
|
|
58
|
+
};
|
|
59
|
+
try {
|
|
60
|
+
const result = installTerminalRuntime({
|
|
61
|
+
target: value('--target') ?? process.cwd(),
|
|
62
|
+
fixture: value('--fixture') ?? process.env.RECIPE_WALLET_FIXTURE,
|
|
63
|
+
force: argv.includes('--force'),
|
|
64
|
+
});
|
|
65
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
66
|
+
if (result.status !== 'pass') {
|
|
67
|
+
process.stderr.write('No wallet fixture installed. Next: set RECIPE_WALLET_FIXTURE or pass --fixture <wallet-fixture.json>.\n');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
process.stderr.write(`${error.message}\n`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|