@deeeed/metamask-harness 0.2.0 → 0.3.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 (109) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +20 -17
  3. package/{orchestration → adapters}/core/cleanup.sh +2 -2
  4. package/{orchestration → adapters}/core/inject.sh +8 -8
  5. package/{orchestration → adapters}/extension/cleanup.mjs +1 -1
  6. package/{orchestration → adapters}/extension/console-tail.mjs +3 -0
  7. package/{orchestration → adapters}/extension/ensure-browser.sh +4 -4
  8. package/{orchestration → adapters}/extension/inject.mjs +27 -31
  9. package/{orchestration → adapters}/extension/launch.sh +4 -4
  10. package/{orchestration → adapters}/extension/live.sh +5 -5
  11. package/{orchestration → adapters}/extension/readiness.mjs +12 -2
  12. package/{orchestration → adapters}/extension/refresh-build.sh +1 -1
  13. package/{orchestration → adapters}/extension/seed-fixture.sh +1 -1
  14. package/{orchestration → adapters}/extension/start-watch.sh +7 -7
  15. package/{runner → adapters}/extension/verify.sh +11 -11
  16. package/{orchestration → adapters}/extension/wallet-fixture-state.cjs +2 -2
  17. package/{orchestration → adapters}/manifest.json +96 -96
  18. package/{library/actions → adapters}/mobile/bridge-runtime/cdp-bridge.cjs +5 -5
  19. package/{orchestration → adapters}/mobile/cleanup.sh +3 -3
  20. package/{orchestration → adapters}/mobile/inject.sh +22 -24
  21. package/adapters/mobile/open-device.sh +250 -0
  22. package/{orchestration/compat-overlays → adapters/mobile/overlay/compat}/README.md +13 -4
  23. package/adapters/mobile/prewarm-bundle.sh +95 -0
  24. package/adapters/mobile/start-metro.sh +228 -0
  25. package/{runner → adapters}/mobile/verify.sh +6 -6
  26. package/adapters/mobile/wait-for-bridge.sh +108 -0
  27. package/adapters/mobile/yarn-setup.sh +20 -0
  28. package/{orchestration/lib → adapters/shared}/cli-ux.sh +5 -9
  29. package/{orchestration/lib → adapters/shared}/log-tui.mjs +57 -1
  30. package/{orchestration/lib → adapters/shared}/open-debug.mjs +32 -10
  31. package/{orchestration/lib → adapters/shared}/resolve-farmslot-ports.sh +47 -0
  32. package/adapters/shared/sync-wallet-fixture.sh +78 -0
  33. package/bin/mm-harness +5 -6
  34. package/docs/CLI-SPEC.md +41 -2
  35. package/docs/architecture.md +69 -38
  36. package/docs/runtime-file-conventions.md +3 -3
  37. package/library/actions/core/perps/_controller.mjs +1 -1
  38. package/library/actions/extension/platform/cdp.mjs +2 -2
  39. package/library/actions/extension/wallet/ensure_unlocked.mjs +1 -1
  40. package/library/actions/mobile/platform/bridge.mjs +1 -1
  41. package/library/actions/mobile/wallet/ensure_unlocked.mjs +1 -1
  42. package/library/actions/mobile/wallet/setup.mjs +2 -2
  43. package/library/library.json +1 -1
  44. package/package.json +8 -8
  45. package/{orchestration → src/adapters}/extension/runtime-decision.ts +5 -5
  46. package/{orchestration → src/adapters}/extension/runtime.ts +1 -1
  47. package/src/adapters/mobile/prepare.ts +210 -0
  48. package/{orchestration → src/adapters}/mobile/runtime-decision.ts +11 -3
  49. package/{runner/src → src}/adapters.ts +3 -3
  50. package/{orchestration/lib/cli-color.mjs → src/cli-color.ts} +21 -13
  51. package/{orchestration/lib/cli-commands.mjs → src/cli-commands.ts} +52 -45
  52. package/{orchestration/lib/cli-version.mjs → src/cli-version.ts} +35 -17
  53. package/{runner/src → src}/cli.ts +25 -18
  54. package/src/commands/debug.ts +61 -0
  55. package/src/commands/fixtures.ts +196 -0
  56. package/{runner/src → src}/commands/launch.ts +185 -41
  57. package/src/commands/logs.ts +86 -0
  58. package/{runner/src → src}/commands/shared.ts +26 -26
  59. package/{runner/src → src}/harness.ts +97 -10
  60. package/src/index.ts +15 -0
  61. package/{runner/src → src}/live-adapter-contract.ts +1 -1
  62. package/{runner/src → src}/mm-harness-cli.ts +19 -3
  63. package/{runner/src → src}/paths.ts +2 -2
  64. package/{orchestration/lib/progress.mjs → src/progress.ts} +31 -21
  65. package/docs/extension-runtime-commands.md +0 -60
  66. package/docs/harness-cli.md +0 -43
  67. package/orchestration/compat-overlays/mobile/README.md +0 -13
  68. package/orchestration/doctor.mjs +0 -72
  69. package/orchestration/lib/cli-home.mjs +0 -354
  70. package/orchestration/lib/recipe-paths.mjs +0 -26
  71. package/orchestration/mobile/launch.sh +0 -137
  72. package/orchestration/mobile/live.sh +0 -125
  73. package/orchestration/porcelain/metamask-recipe +0 -99
  74. package/orchestration/porcelain/mm-recipe +0 -1591
  75. package/orchestration/porcelain/mme-recipe +0 -1181
  76. package/runner/src/commands/debug.ts +0 -44
  77. package/runner/src/commands/fixtures.ts +0 -99
  78. package/runner/src/commands/logs.ts +0 -60
  79. package/runner/src/index.ts +0 -6
  80. /package/{orchestration → adapters}/extension/launch-browser.cjs +0 -0
  81. /package/{orchestration → adapters}/extension/pin-remote-flags.cjs +0 -0
  82. /package/{orchestration → adapters}/extension/sidepanel-toggle.sh +0 -0
  83. /package/{orchestration → adapters}/extension/snapshot-dist.sh +0 -0
  84. /package/{library/actions → adapters}/mobile/bridge-runtime/lib/cdp-eval.cjs +0 -0
  85. /package/{library/actions → adapters}/mobile/bridge-runtime/lib/config.cjs +0 -0
  86. /package/{library/actions → adapters}/mobile/bridge-runtime/lib/issue-capture.cjs +0 -0
  87. /package/{library/actions → adapters}/mobile/bridge-runtime/lib/target-discovery.cjs +0 -0
  88. /package/{library/actions → adapters}/mobile/bridge-runtime/lib/ws-client.cjs +0 -0
  89. /package/{library/actions → adapters}/mobile/bridge-runtime/setup-wallet.sh +0 -0
  90. /package/{library/actions/mobile/app-overlay → adapters/mobile/overlay}/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -0
  91. /package/{library/actions/mobile/app-overlay → adapters/mobile/overlay}/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -0
  92. /package/{orchestration/compat-overlays/mobile → adapters/mobile/overlay/compat}/rn81-message-event-source.patch +0 -0
  93. /package/{orchestration/lib → adapters/shared}/activate-repo-node.sh +0 -0
  94. /package/{orchestration/lib → adapters/shared}/ensure-runner-deps.sh +0 -0
  95. /package/{orchestration/lib → adapters/shared}/harness-path.sh +0 -0
  96. /package/{orchestration/lib → adapters/shared}/hash-helpers.sh +0 -0
  97. /package/{orchestration/lib → adapters/shared}/json-field.sh +0 -0
  98. /package/{orchestration/lib → adapters/shared}/path-defaults.json +0 -0
  99. /package/{orchestration → src/adapters}/extension/ensure-ready.ts +0 -0
  100. /package/{orchestration → src/adapters}/extension/extension-id.ts +0 -0
  101. /package/{orchestration → src/adapters}/mobile/deps-markers.ts +0 -0
  102. /package/{runner/src → src}/completions-cache.ts +0 -0
  103. /package/{runner/src → src}/doctor.ts +0 -0
  104. /package/{runner/src → src}/heal-bounds.ts +0 -0
  105. /package/{runner/src → src}/manifest.ts +0 -0
  106. /package/{runner/src → src}/recording-target.ts +0 -0
  107. /package/{runner/src → src}/run-recording.ts +0 -0
  108. /package/{runner/src → src}/runner.ts +0 -0
  109. /package/{runner/src → src}/types.ts +0 -0
@@ -1,354 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * cli-home.mjs — recipe CLI home screen and ports output.
4
- */
5
- import fs from 'node:fs';
6
- import http from 'node:http';
7
- import path from 'node:path';
8
- import { parseArgs } from 'node:util';
9
- import {
10
- color,
11
- colorKv,
12
- colorStatusWord,
13
- } from './cli-color.mjs';
14
-
15
- const OUT = process.stdout;
16
-
17
- function readJson(file) {
18
- if (!file || !fs.existsSync(file)) return null;
19
- try {
20
- return JSON.parse(fs.readFileSync(file, 'utf8'));
21
- } catch {
22
- return null;
23
- }
24
- }
25
-
26
- function httpProbe(url, timeoutMs = 1500) {
27
- return new Promise((resolve) => {
28
- const req = http.get(url, { timeout: timeoutMs }, (res) => {
29
- let body = '';
30
- res.on('data', (chunk) => { body += chunk; });
31
- res.on('end', () => resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, body }));
32
- });
33
- req.on('timeout', () => { req.destroy(); resolve({ ok: false, body: '' }); });
34
- req.on('error', () => resolve({ ok: false, body: '' }));
35
- });
36
- }
37
-
38
- function watcherPhase(logPath) {
39
- if (!logPath || !fs.existsSync(logPath)) return 'stopped';
40
- const text = fs.readFileSync(logPath, 'utf8');
41
- if (/Module build failed|^ERROR in /m.test(text)) return 'failed';
42
- if (/compiled successfully|MetaMask .* compiled|Bundle end:/m.test(text)) return 'ready';
43
- if (/webpack.Progress|Building|Compiling|yarn start/m.test(text)) return 'compiling';
44
- return 'unknown';
45
- }
46
-
47
- function metroPhase(logPath) {
48
- if (!logPath || !fs.existsSync(logPath)) return 'stopped';
49
- const text = fs.readFileSync(logPath, 'utf8');
50
- if (/Bundl(ed|ing)|Finished/m.test(text)) {
51
- const match = text.match(/(iOS|Android).*?([0-9]+(?:\.[0-9]+)?)%/);
52
- if (match && Number(match[2]) < 100) return 'bundling';
53
- return 'ready';
54
- }
55
- return 'starting';
56
- }
57
-
58
- function mobileBridgeSnapshot(runtimeStatus) {
59
- const raw = runtimeStatus?.bridge?.status;
60
- const targets = Array.isArray(raw) ? raw : raw ? [raw] : [];
61
- const ready = targets.find((item) => item && typeof item === 'object' && item.route);
62
- if (!ready) return { route: null, account: null, deviceName: null };
63
- return {
64
- route: ready.route?.name || null,
65
- account: ready.account?.name || ready.account?.address || null,
66
- deviceName: ready.deviceName || null,
67
- };
68
- }
69
-
70
- function watcherPhaseLabel(phase) {
71
- switch (phase) {
72
- case 'ready': return 'ready';
73
- case 'compiling': return 'compiling';
74
- case 'failed': return 'failed';
75
- case 'stopped': return 'stopped';
76
- default: return phase || 'unknown';
77
- }
78
- }
79
-
80
- function metroPhaseLabel(phase) {
81
- switch (phase) {
82
- case 'running':
83
- case 'ready': return 'ready';
84
- case 'bundling': return 'bundling';
85
- case 'starting': return 'starting';
86
- case 'down': return 'down';
87
- default: return phase || 'unknown';
88
- }
89
- }
90
-
91
- function nextActions(adapter, state) {
92
- const actions = [];
93
- if (state.harnessStale) {
94
- actions.push({ command: 'recipe sync', reason: 'refresh harness scripts + fixture context' });
95
- }
96
- if (adapter === 'extension') {
97
- if (state.ready) {
98
- actions.push({ command: 'recipe refresh', reason: 'fast incremental update when watcher is active' });
99
- actions.push({ command: 'recipe debug', reason: 'open Chrome DevTools' });
100
- actions.push({ command: 'recipe logs', reason: 'compact last build events (recipe logs --full for raw tail)' });
101
- } else if (state.watcher === 'ready' && state.decision === 'build') {
102
- actions.push({ command: 'recipe refresh', reason: 'watcher is up; wait for dist to catch up' });
103
- actions.push({ command: 'recipe rebuild', reason: 'heavy clean rebuild if refresh stalls' });
104
- } else if (state.watcher === 'compiling') {
105
- actions.push({ command: 'recipe logs', reason: 'compact webpack progress' });
106
- actions.push({ command: 'recipe status', reason: 're-check readiness' });
107
- } else {
108
- actions.push({ command: 'recipe up', reason: 'decide and run the minimum path to ready' });
109
- actions.push({ command: 'recipe rebuild', reason: 'clean webpack + browser launch (2-8 min)' });
110
- actions.push({ command: 'recipe watch', reason: 'start watcher only' });
111
- }
112
- if (!state.fixturePresent) actions.unshift({ command: 'recipe sync', reason: 'install fixture + harness context' });
113
- } else {
114
- if (state.ready) {
115
- actions.push({ command: 'recipe debug', reason: 'open React Native DevTools' });
116
- actions.push({ command: 'recipe logs', reason: 'compact Metro events (recipe logs --full for raw tail)' });
117
- } else if (state.metro === 'ready' && !state.bridgeReady) {
118
- actions.push({ command: 'recipe up', reason: 'launch dev client and wait for bridge' });
119
- actions.push({ command: 'recipe logs', reason: 'compact bundle progress' });
120
- } else {
121
- actions.push({ command: 'recipe up', reason: 'start Metro + launch dev client as needed' });
122
- actions.push({ command: 'recipe ios', reason: 'launch iOS dev client' });
123
- actions.push({ command: 'recipe android', reason: 'launch Android dev client' });
124
- }
125
- if (!state.fixturePresent) actions.unshift({ command: 'recipe sync', reason: 'install fixture + harness context' });
126
- }
127
- return actions.slice(0, 5);
128
- }
129
-
130
- function renderHuman(payload) {
131
- const lines = [];
132
- const head = payload.adapter === 'extension' ? 'Extension recipe' : 'Mobile recipe';
133
- lines.push(`${color('bold', head, { stream: OUT })} ${color('dim', '|', { stream: OUT })} ${color('path', payload.root, { stream: OUT })}`);
134
- lines.push(`${colorKv('slot', payload.slotId || '?', 'info', { stream: OUT })} ${colorKv('runtime', payload.runtimeDir, 'path', { stream: OUT })}`);
135
- if (payload.harnessStale) {
136
- lines.push(color('warn', 'harness: stale - run: recipe sync', { stream: OUT }));
137
- }
138
- if (payload.adapter === 'extension') {
139
- lines.push(
140
- `${colorKv('cdp', String(payload.ports.cdp ?? '?'), 'info', { stream: OUT })} ${colorKv('watcher-port', String(payload.ports.watcher ?? '?'), 'info', { stream: OUT })}`,
141
- );
142
- lines.push([
143
- `${color('label', 'browser:', { stream: OUT })} ${colorStatusWord(payload.browser, { stream: OUT })}`,
144
- `${color('label', 'webpack:', { stream: OUT })} ${colorStatusWord(watcherPhaseLabel(payload.watcher), { stream: OUT })}`,
145
- `${color('label', 'ready:', { stream: OUT })} ${colorStatusWord(payload.ready ? 'yes' : 'no', { stream: OUT })}`,
146
- payload.reason ? color('comment', ` (${payload.reason})`, { stream: OUT }) : '',
147
- ].join(' '));
148
- lines.push(`${color('label', 'fixture:', { stream: OUT })} ${colorStatusWord(payload.fixturePresent ? 'present' : 'missing', { stream: OUT })}`);
149
- } else {
150
- const bridgeBits = [];
151
- if (payload.appRoute) bridgeBits.push(`route=${payload.appRoute}`);
152
- if (payload.appAccount) bridgeBits.push(`account=${payload.appAccount}`);
153
- const bridgeDetail = bridgeBits.length > 0 ? color('comment', ` (${bridgeBits.join(', ')})`, { stream: OUT }) : '';
154
- lines.push(
155
- `${colorKv('metro-port', String(payload.ports.watcher ?? '?'), 'info', { stream: OUT })} ${colorKv('device', payload.device || payload.appDevice || '?', 'info', { stream: OUT })} ${color('label', 'metro:', { stream: OUT })} ${colorStatusWord(metroPhaseLabel(payload.metro), { stream: OUT })} ${color('label', 'bridge:', { stream: OUT })} ${colorStatusWord(payload.bridge, { stream: OUT })}${bridgeDetail}`,
156
- );
157
- lines.push([
158
- `${color('label', 'fixture:', { stream: OUT })} ${colorStatusWord(payload.fixturePresent ? 'present' : 'missing', { stream: OUT })}`,
159
- `${color('label', 'ready:', { stream: OUT })} ${colorStatusWord(payload.ready ? 'yes' : 'no', { stream: OUT })}`,
160
- payload.reason ? color('comment', ` (${payload.reason})`, { stream: OUT }) : '',
161
- ].join(' '));
162
- }
163
- if (payload.decision) {
164
- lines.push(`${colorKv('decision', payload.decision, 'ok', { stream: OUT })}${payload.reasonCode ? color('comment', ` - ${payload.reasonCode}`, { stream: OUT }) : ''}`);
165
- }
166
- if (payload.tmux) {
167
- lines.push(`${colorKv('tmux', payload.tmux, 'accent', { stream: OUT })}${color('comment', ' (webpack window may still stream full logs)', { stream: OUT })}`);
168
- }
169
- lines.push('');
170
- lines.push(color('label', 'next:', { stream: OUT }));
171
- for (const item of payload.nextActions) {
172
- lines.push(` ${color('cmd', item.command, { stream: OUT })} ${color('comment', `# ${item.reason}`, { stream: OUT })}`);
173
- }
174
- lines.push('');
175
- lines.push(color('comment', 'speed: fast -> watch -> refresh slow -> rebuild inspect -> status | logs | logs --full | debug', { stream: OUT }));
176
- return `${lines.join('\n')}\n`;
177
- }
178
-
179
- function renderPorts(payload) {
180
- const lines = [
181
- colorKv('slot', payload.slotId || '?', 'info', { stream: OUT }),
182
- `${colorKv('cdp', String(payload.ports.cdp ?? '?'), 'info', { stream: OUT })} ${colorKv('watcher', String(payload.ports.watcher ?? '?'), 'info', { stream: OUT })}`,
183
- `${colorKv('runtime', payload.runtimeDir, 'path', { stream: OUT })} ${colorKv('harness', payload.harnessDir, 'path', { stream: OUT })}`,
184
- ];
185
- if (payload.tmux) lines.push(colorKv('tmux', payload.tmux, 'accent', { stream: OUT }));
186
- return `${lines.join('\n')}\n`;
187
- }
188
-
189
- async function buildHome({ adapter, target, runtimeDir, ports, runtimeStatus, decision, harnessStale = false }) {
190
- const runtimeAbs = path.join(target, runtimeDir);
191
- const context = readJson(path.join(runtimeAbs, 'agentic-runtime.json'))
192
- || readJson(path.join(runtimeAbs, 'recipe-runtime.json'))
193
- || {};
194
- const slotId = context.slotId || null;
195
- const fixturePresent = Boolean(
196
- runtimeStatus?.fixture?.present
197
- || fs.existsSync(path.join(runtimeAbs, 'wallet-fixture.json')),
198
- );
199
-
200
- let browser = 'down';
201
- let watcher = 'stopped';
202
- let metro = 'stopped';
203
- let bridge = 'down';
204
- let bridgeReady = false;
205
- let device = process.env.IOS_SIMULATOR || process.env.ANDROID_DEVICE || process.env.ADB_SERIAL || null;
206
- let tmux = null;
207
-
208
- if (adapter === 'extension') {
209
- const cdp = ports.cdp;
210
- if (cdp) {
211
- const probe = await httpProbe(`http://127.0.0.1:${cdp}/json/version`);
212
- browser = probe.ok ? 'up' : 'down';
213
- }
214
- const logPath = path.join(runtimeAbs, 'webpack.log');
215
- watcher = watcherPhase(logPath);
216
- const slot = (path.basename(target).match(/-(\d+)$/) || [])[1];
217
- if (slot) tmux = `mme-${slot}:webpack-${ports.watcher || '?'}`;
218
- } else {
219
- const port = ports.watcher;
220
- if (port) {
221
- const status = await httpProbe(`http://127.0.0.1:${port}/status`);
222
- metro = status.ok && status.body.includes('packager-status:running') ? 'running' : 'down';
223
- if (metro === 'running') metro = metroPhase(path.join(runtimeAbs, 'metro.log'));
224
- }
225
- bridgeReady = Boolean(runtimeStatus?.bridge?.ready);
226
- bridge = bridgeReady ? 'ready' : (runtimeStatus?.debugTarget?.present ? 'waiting' : 'down');
227
- }
228
-
229
- const ready = Boolean(runtimeStatus?.ready);
230
- const decisionValue = decision?.decision || runtimeStatus?.decision?.decision || null;
231
- const reasonCode = decision?.reasonCode || runtimeStatus?.decision?.reasonCode || runtimeStatus?.reason || null;
232
- const bridgeSnap = adapter === 'mobile' ? mobileBridgeSnapshot(runtimeStatus) : null;
233
-
234
- const payload = {
235
- schemaVersion: 1,
236
- kind: 'home',
237
- adapter,
238
- root: target,
239
- runtimeDir,
240
- slotId,
241
- ports,
242
- fixturePresent,
243
- harnessStale,
244
- ready,
245
- reason: runtimeStatus?.reason || null,
246
- appRoute: bridgeSnap?.route || null,
247
- appAccount: bridgeSnap?.account || null,
248
- appDevice: bridgeSnap?.deviceName || null,
249
- decision: decisionValue,
250
- reasonCode,
251
- browser: adapter === 'extension' ? browser : undefined,
252
- watcher: adapter === 'extension' ? watcher : undefined,
253
- metro: adapter === 'mobile' ? metro : undefined,
254
- bridge: adapter === 'mobile' ? bridge : undefined,
255
- bridgeReady: adapter === 'mobile' ? bridgeReady : undefined,
256
- device: adapter === 'mobile' ? device : undefined,
257
- tmux: adapter === 'extension' ? tmux : undefined,
258
- runtimeStatus,
259
- nextActions: [],
260
- };
261
- payload.nextActions = nextActions(adapter, {
262
- ready,
263
- watcher,
264
- metro,
265
- bridgeReady,
266
- fixturePresent,
267
- harnessStale,
268
- decision: decisionValue,
269
- });
270
- return payload;
271
- }
272
-
273
- function buildPorts({ adapter, target, runtimeDir, ports, slotId }) {
274
- return {
275
- schemaVersion: 1,
276
- kind: 'ports',
277
- adapter,
278
- root: target,
279
- runtimeDir,
280
- slotId: slotId || null,
281
- ports,
282
- tmux: adapter === 'extension' && ports.watcher
283
- ? `mme-${(path.basename(target).match(/-(\d+)$/) || ['', ''])[1]}:webpack-${ports.watcher}`
284
- : null,
285
- harnessDir: process.env.RECIPE_HARNESS_ROOT || 'temp/recipe/harness',
286
- };
287
- }
288
-
289
- async function main() {
290
- const { values } = parseArgs({
291
- args: process.argv.slice(2),
292
- options: {
293
- adapter: { type: 'string' },
294
- target: { type: 'string' },
295
- 'runtime-dir': { type: 'string' },
296
- 'cdp-port': { type: 'string' },
297
- 'watcher-port': { type: 'string' },
298
- 'slot-id': { type: 'string' },
299
- mode: { type: 'string', default: 'home' },
300
- 'runtime-status-file': { type: 'string' },
301
- 'decision-file': { type: 'string' },
302
- 'harness-stale': { type: 'boolean', default: false },
303
- json: { type: 'boolean', default: false },
304
- },
305
- allowPositionals: false,
306
- });
307
-
308
- if (!values.adapter || !values.target) {
309
- console.error('cli-home.mjs requires --adapter and --target');
310
- process.exit(2);
311
- }
312
-
313
- const ports = {
314
- cdp: values['cdp-port'] ? Number(values['cdp-port']) : null,
315
- watcher: values['watcher-port'] ? Number(values['watcher-port']) : null,
316
- };
317
- const runtimeStatus = readJson(values['runtime-status-file']);
318
- const decision = readJson(values['decision-file']);
319
-
320
- const payload = values.mode === 'ports'
321
- ? buildPorts({
322
- adapter: values.adapter,
323
- target: values.target,
324
- runtimeDir: values['runtime-dir'] || 'temp/recipe/runtime',
325
- ports,
326
- slotId: values['slot-id'],
327
- })
328
- : await buildHome({
329
- adapter: values.adapter,
330
- target: values.target,
331
- runtimeDir: values['runtime-dir'] || 'temp/recipe/runtime',
332
- ports,
333
- runtimeStatus,
334
- decision,
335
- harnessStale: values['harness-stale'],
336
- });
337
-
338
- if (values.json) {
339
- process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
340
- return;
341
- }
342
-
343
- if (payload.kind === 'ports') {
344
- process.stdout.write(renderPorts(payload));
345
- return;
346
- }
347
-
348
- process.stdout.write(renderHuman(payload));
349
- }
350
-
351
- main().catch((error) => {
352
- console.error(error instanceof Error ? error.message : String(error));
353
- process.exit(1);
354
- });
@@ -1,26 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
-
5
- const defaultsPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'path-defaults.json');
6
- const defaults = JSON.parse(fs.readFileSync(defaultsPath, 'utf8'));
7
-
8
- export const DEFAULT_RECIPE_HARNESS_ROOT = defaults.recipeHarnessRoot;
9
- export const DEFAULT_RECIPE_RUNTIME_DIR = defaults.recipeRuntimeDir;
10
-
11
- export function validateRelativeRecipePath(name, value) {
12
- if (!value || path.isAbsolute(value)) throw new Error(`${name} must be a non-empty relative path: ${value}`);
13
- if (!/^[A-Za-z0-9._/-]+$/u.test(value)) throw new Error(`${name} contains unsupported characters: ${value}`);
14
- for (const part of value.split('/')) {
15
- if (!part || part === '.' || part === '..') throw new Error(`${name} contains unsafe path component: ${value}`);
16
- }
17
- return value;
18
- }
19
-
20
- export function recipeHarnessRoot(env = process.env) {
21
- return validateRelativeRecipePath('RECIPE_HARNESS_ROOT', env.RECIPE_HARNESS_ROOT || DEFAULT_RECIPE_HARNESS_ROOT);
22
- }
23
-
24
- export function recipeRuntimeDir(env = process.env) {
25
- return validateRelativeRecipePath('RECIPE_RUNTIME_DIR', env.RECIPE_RUNTIME_DIR || DEFAULT_RECIPE_RUNTIME_DIR);
26
- }
@@ -1,137 +0,0 @@
1
- #!/usr/bin/env bash
2
- # launch.sh — mobile instance identity intake + prepare/preflight launch
3
- #
4
- # Purpose:
5
- # Resolves the per-instance tuple (port, simulator/adb-serial, runtime dir,
6
- # platform, preflight mode) and runs the installed runner's mobile prepare.
7
- #
8
- # Inputs (flags / env precedence):
9
- # --target <metamask-mobile> (default $PWD)
10
- # --platform ios|android (default ios)
11
- # --preflight-mode|--mode fast|auto|default|rebuild-native|clean
12
- # (env RECIPE_HARNESS_MOBILE_PREFLIGHT_MODE, default fast)
13
- # --port|--watcher-port|--cdp-port <port> (env WATCHER_PORT > METRO_PORT > CDP_PORT)
14
- # --simulator <sim> (env IOS_SIMULATOR > SIM_UDID)
15
- # --adb-serial <serial> (env ADB_SERIAL > ANDROID_SERIAL)
16
- # --runtime-dir <dir> (env RECIPE_RUNTIME_DIR)
17
- # --wallet-setup / --no-wallet-setup, --wallet-fixture <json>
18
- # --artifacts-dir <dir>
19
- #
20
- # Outputs:
21
- # <artifacts>/summary.json (adapter/action/status/runtime/generatedAt),
22
- # <artifacts>/runtime-status.json, <artifacts>/logs/prepare.log
23
- # Exit 0 — launch pass; 1 — runner missing or prepare failed; 2 — bad args.
24
- #
25
- # Never touches: product source files; anything outside the target's harness
26
- # dir and the artifacts dir.
27
- set -euo pipefail
28
-
29
- TARGET="$PWD"
30
- PLATFORM="ios"
31
- PREFLIGHT_MODE="${RECIPE_HARNESS_MOBILE_PREFLIGHT_MODE:-fast}"
32
- ARTIFACTS=""
33
- PORT="${WATCHER_PORT:-${METRO_PORT:-${CDP_PORT:-}}}"
34
- SIMULATOR="${IOS_SIMULATOR:-${SIM_UDID:-}}"
35
- ADB_SERIAL_ARG="${ADB_SERIAL:-${ANDROID_SERIAL:-}}"
36
- RUNTIME_DIR_ARG="${RECIPE_RUNTIME_DIR:-}"
37
- WALLET_SETUP=false
38
- WALLET_FIXTURE_ARG=""
39
- require_value() { [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; }
40
- while [ "$#" -gt 0 ]; do
41
- case "$1" in
42
- --target) require_value "$@"; TARGET="$2"; shift 2 ;;
43
- --platform) require_value "$@"; PLATFORM="$2"; shift 2 ;;
44
- --preflight-mode|--mode) require_value "$@"; PREFLIGHT_MODE="$2"; shift 2 ;;
45
- --artifacts-dir) require_value "$@"; ARTIFACTS="$2"; shift 2 ;;
46
- --port|--watcher-port|--cdp-port) require_value "$@"; PORT="$2"; shift 2 ;;
47
- --simulator) require_value "$@"; SIMULATOR="$2"; shift 2 ;;
48
- --adb-serial) require_value "$@"; ADB_SERIAL_ARG="$2"; shift 2 ;;
49
- --runtime-dir) require_value "$@"; RUNTIME_DIR_ARG="$2"; shift 2 ;;
50
- --wallet-setup) WALLET_SETUP=true; shift ;;
51
- --no-wallet-setup) WALLET_SETUP=false; shift ;;
52
- --wallet-fixture) require_value "$@"; WALLET_FIXTURE_ARG="$2"; shift 2 ;;
53
- -h|--help)
54
- echo "Usage: launch.sh [--target <metamask-mobile>] [--platform ios|android] [--preflight-mode fast|auto|default|rebuild-native|clean] [--port <port>] [--simulator <sim>] [--runtime-dir <dir>] [--wallet-setup] [--wallet-fixture <json>]"
55
- exit 0
56
- ;;
57
- *) echo "Unknown arg: $1" >&2; exit 2 ;;
58
- esac
59
- done
60
-
61
- case "$PLATFORM" in ios|android) ;; *) echo "Unknown --platform: $PLATFORM" >&2; exit 2 ;; esac
62
- case "$PREFLIGHT_MODE" in fast|auto|default|rebuild-native|clean) ;; *) echo "Unknown --preflight-mode: $PREFLIGHT_MODE" >&2; exit 2 ;; esac
63
-
64
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
65
- # shellcheck disable=SC1091
66
- for _hp in "$SCRIPT_DIR/lib/harness-path.sh" "$SCRIPT_DIR/../lib/harness-path.sh"; do
67
- [ -f "$_hp" ] && { . "$_hp"; break; }
68
- done
69
- unset _hp
70
- if ! command -v harness_root >/dev/null 2>&1; then
71
- echo "mm-harness: shared lib orchestration/lib/harness-path.sh not found; reinstall the runner." >&2
72
- exit 1
73
- fi
74
- TARGET="$(cd "$TARGET" && pwd)"
75
- HARNESS_DIR="$(harness_dir "$TARGET" mobile)"
76
- RUNNER_BIN="$HARNESS_DIR/runner/bin/metamask-recipe"
77
- ARTIFACTS="${ARTIFACTS:-$HARNESS_DIR/launch/$(date -u +%Y%m%dT%H%M%SZ)}"
78
- mkdir -p "$ARTIFACTS/logs"
79
-
80
- if [ ! -x "$RUNNER_BIN" ]; then
81
- echo "Mobile recipe runner is not installed in $TARGET. Run metamask-recipe mobile install --target $TARGET first." >&2
82
- exit 1
83
- fi
84
-
85
- prepare_args=(mobile prepare --target "$TARGET" --platform "$PLATFORM" --preflight-mode "$PREFLIGHT_MODE")
86
- [ -n "$PORT" ] && prepare_args+=(--port "$PORT")
87
- [ -n "$SIMULATOR" ] && prepare_args+=(--simulator "$SIMULATOR")
88
- [ -n "$ADB_SERIAL_ARG" ] && prepare_args+=(--adb-serial "$ADB_SERIAL_ARG")
89
- [ -n "$RUNTIME_DIR_ARG" ] && prepare_args+=(--runtime-dir "$RUNTIME_DIR_ARG")
90
- $WALLET_SETUP && prepare_args+=(--wallet-setup)
91
- [ -n "$WALLET_FIXTURE_ARG" ] && prepare_args+=(--wallet-fixture "$WALLET_FIXTURE_ARG")
92
-
93
- status="pass"
94
- if (cd "$TARGET" && "$RUNNER_BIN" "${prepare_args[@]}") > "$ARTIFACTS/logs/prepare.log" 2>&1; then
95
- runtime_status="pass"
96
- else
97
- runtime_status="fail"
98
- status="fail"
99
- fi
100
-
101
- status_args=(mobile runtime-status --target "$TARGET" --json)
102
- [ -n "$PORT" ] && status_args+=(--port "$PORT")
103
- [ -n "$RUNTIME_DIR_ARG" ] && status_args+=(--runtime-dir "$RUNTIME_DIR_ARG")
104
- (cd "$TARGET" && "$RUNNER_BIN" "${status_args[@]}") > "$ARTIFACTS/runtime-status.json" 2> "$ARTIFACTS/logs/runtime-status.err" || true
105
-
106
- TARGET_FOR_SUMMARY="$TARGET" ARTIFACTS_FOR_SUMMARY="$ARTIFACTS" STATUS_FOR_SUMMARY="$status" RUNTIME_STATUS="$runtime_status" PLATFORM_FOR_SUMMARY="$PLATFORM" MODE_FOR_SUMMARY="$PREFLIGHT_MODE" node <<'NODE'
107
- const fs = require('fs');
108
- const path = require('path');
109
- const artifacts = process.env.ARTIFACTS_FOR_SUMMARY;
110
- let runtime = null;
111
- try { runtime = JSON.parse(fs.readFileSync(path.join(artifacts, 'runtime-status.json'), 'utf8')); } catch {}
112
- fs.writeFileSync(path.join(artifacts, 'summary.json'), `${JSON.stringify({
113
- adapter: 'mobile',
114
- action: 'launch',
115
- status: process.env.STATUS_FOR_SUMMARY,
116
- platform: process.env.PLATFORM_FOR_SUMMARY,
117
- preflightMode: process.env.MODE_FOR_SUMMARY,
118
- target: process.env.TARGET_FOR_SUMMARY,
119
- runtime: {
120
- status: process.env.RUNTIME_STATUS,
121
- ready: runtime?.ready === true,
122
- reason: runtime?.reason || null,
123
- port: runtime?.metro?.port || null,
124
- prepareLogPath: path.join(artifacts, 'logs/prepare.log'),
125
- runtimeStatusPath: path.join(artifacts, 'runtime-status.json'),
126
- },
127
- runtimePolicy: {
128
- nativeBuildPolicy: process.env.MODE_FOR_SUMMARY === 'fast'
129
- ? 'fast mode never runs a native build; it requires an installed dev client, starts/reuses Metro, prewarms the JS bundle, opens the dev client, and waits for the bridge'
130
- : 'non-fast modes may install or rebuild the native dev client when needed; then start/reuse Metro, prewarm the JS bundle, open the dev client, and wait for the bridge',
131
- },
132
- generatedAt: new Date().toISOString(),
133
- }, null, 2)}\n`);
134
- NODE
135
-
136
- echo "Mobile harness launch $status: $ARTIFACTS/summary.json"
137
- [ "$status" = "pass" ]
@@ -1,125 +0,0 @@
1
- #!/usr/bin/env bash
2
- # live.sh — mobile launch-then-verify chaining for one live slot run
3
- #
4
- # Purpose:
5
- # Runs launch.sh then verify.sh (verify with --no-auto-start; launch owns
6
- # startup) under a timestamped artifact dir and aggregates both results.
7
- #
8
- # Inputs (flags / env):
9
- # --target <metamask-mobile> (default $PWD)
10
- # --platform ios|android (default ios)
11
- # --preflight-mode|--mode fast|auto|default|rebuild-native|clean
12
- # (env RECIPE_HARNESS_MOBILE_PREFLIGHT_MODE, default fast)
13
- # --artifacts-dir <dir> (default <harness>/live/<UTC>)
14
- # --wallet-setup|--no-wallet-setup (default auto), --wallet-fixture <json>
15
- #
16
- # Outputs:
17
- # <artifacts>/summary.json (adapter/action/status, launch/verify exit codes
18
- # + child summary paths, easyCommand), <artifacts>/{launch,verify}/.
19
- # Exit 0 — launch and verify both passed; 1 — either failed; 2 — bad args.
20
- #
21
- # Never touches: product source files; anything outside the target's harness
22
- # dir and the artifacts dir.
23
- set -euo pipefail
24
-
25
- TARGET="$PWD"
26
- PLATFORM="ios"
27
- PREFLIGHT_MODE="${RECIPE_HARNESS_MOBILE_PREFLIGHT_MODE:-fast}"
28
- ARTIFACTS=""
29
- WALLET_SETUP="auto"
30
- WALLET_FIXTURE=""
31
- while [ "$#" -gt 0 ]; do
32
- case "$1" in
33
- --target) TARGET="$2"; shift 2 ;;
34
- --platform) PLATFORM="$2"; shift 2 ;;
35
- --preflight-mode|--mode) PREFLIGHT_MODE="$2"; shift 2 ;;
36
- --artifacts-dir) ARTIFACTS="$2"; shift 2 ;;
37
- --wallet-setup) WALLET_SETUP="true"; shift ;;
38
- --no-wallet-setup) WALLET_SETUP="false"; shift ;;
39
- --wallet-fixture) WALLET_FIXTURE="$2"; shift 2 ;;
40
- -h|--help) echo "Usage: live.sh [--target <metamask-mobile>] [--platform ios|android] [--preflight-mode fast|auto|default|rebuild-native|clean] [--artifacts-dir <dir>] [--wallet-setup|--no-wallet-setup]"; exit 0 ;;
41
- *) echo "Unknown arg: $1" >&2; exit 2 ;;
42
- esac
43
- done
44
-
45
- case "$PLATFORM" in ios|android) ;; *) echo "Unknown --platform: $PLATFORM" >&2; exit 2 ;; esac
46
- case "$PREFLIGHT_MODE" in fast|auto|default|rebuild-native|clean) ;; *) echo "Unknown --preflight-mode: $PREFLIGHT_MODE" >&2; exit 2 ;; esac
47
-
48
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
49
- # shellcheck disable=SC1091
50
- for _hp in "$SCRIPT_DIR/lib/harness-path.sh" "$SCRIPT_DIR/../lib/harness-path.sh"; do
51
- [ -f "$_hp" ] && { . "$_hp"; break; }
52
- done
53
- unset _hp
54
- if ! command -v harness_root >/dev/null 2>&1; then
55
- echo "mm-harness: shared lib orchestration/lib/harness-path.sh not found; reinstall the runner." >&2
56
- exit 1
57
- fi
58
- # verify.sh is a recipe-tree feature: co-located in installed copies, under
59
- # runner/mobile in a repo checkout.
60
- VERIFY_SH=""
61
- for _v in "$SCRIPT_DIR/verify.sh" "$SCRIPT_DIR/../../runner/mobile/verify.sh"; do
62
- [ -f "$_v" ] && { VERIFY_SH="$_v"; break; }
63
- done
64
- unset _v
65
- if [ -z "$VERIFY_SH" ]; then
66
- echo "mm-harness: mobile verify.sh not found next to $SCRIPT_DIR; reinstall the runner." >&2
67
- exit 1
68
- fi
69
- TARGET="$(cd "$TARGET" && pwd)"
70
- WALLET_FIXTURE="${WALLET_FIXTURE:-$(recipe_runtime_dir)/wallet-fixture.json}"
71
- ARTIFACTS="${ARTIFACTS:-$(harness_dir "$TARGET" mobile)/live/$(date -u +%Y%m%dT%H%M%SZ)}"
72
- mkdir -p "$ARTIFACTS"
73
-
74
- launch_args=(--target "$TARGET" --platform "$PLATFORM" --preflight-mode "$PREFLIGHT_MODE" --artifacts-dir "$ARTIFACTS/launch")
75
- case "$WALLET_SETUP" in
76
- true) launch_args+=(--wallet-setup) ;;
77
- false) launch_args+=(--no-wallet-setup) ;;
78
- esac
79
- launch_args+=(--wallet-fixture "$WALLET_FIXTURE")
80
-
81
- echo "Mobile live validation command:"
82
- echo " mm-harness launch $PLATFORM --verify"
83
- echo "Launch artifacts: $ARTIFACTS/launch"
84
- echo "Verify artifacts: $ARTIFACTS/verify"
85
-
86
- set +e
87
- "$SCRIPT_DIR/launch.sh" "${launch_args[@]}"
88
- launch_status=$?
89
- set -e
90
-
91
- verify_status=1
92
- if [ "$launch_status" -eq 0 ]; then
93
- set +e
94
- "$VERIFY_SH" --target "$TARGET" --platform "$PLATFORM" --preflight-mode "$PREFLIGHT_MODE" --no-auto-start --artifacts-dir "$ARTIFACTS/verify"
95
- verify_status=$?
96
- set -e
97
- else
98
- echo "Skipping Mobile live verify because launch failed; see $ARTIFACTS/launch/summary.json" >&2
99
- fi
100
-
101
- TARGET_FOR_SUMMARY="$TARGET" ARTIFACTS_FOR_SUMMARY="$ARTIFACTS" PLATFORM_FOR_SUMMARY="$PLATFORM" MODE_FOR_SUMMARY="$PREFLIGHT_MODE" LAUNCH_STATUS="$launch_status" VERIFY_STATUS="$verify_status" node <<'NODE'
102
- const fs = require('fs');
103
- const path = require('path');
104
- const artifacts = process.env.ARTIFACTS_FOR_SUMMARY;
105
- const launchSummary = path.join(artifacts, 'launch', 'summary.json');
106
- const verifySummary = path.join(artifacts, 'verify', 'summary.json');
107
- const launchStatus = Number(process.env.LAUNCH_STATUS);
108
- const verifyStatus = Number(process.env.VERIFY_STATUS);
109
- fs.writeFileSync(path.join(artifacts, 'summary.json'), `${JSON.stringify({
110
- adapter: 'mobile',
111
- action: 'live',
112
- status: launchStatus === 0 && verifyStatus === 0 ? 'pass' : 'fail',
113
- target: process.env.TARGET_FOR_SUMMARY,
114
- platform: process.env.PLATFORM_FOR_SUMMARY,
115
- preflightMode: process.env.MODE_FOR_SUMMARY,
116
- launch: { exitCode: launchStatus, summaryPath: fs.existsSync(launchSummary) ? launchSummary : null },
117
- verify: { exitCode: verifyStatus, summaryPath: fs.existsSync(verifySummary) ? verifySummary : null },
118
- easyCommand: `mm-harness launch ${process.env.PLATFORM_FOR_SUMMARY} --verify`,
119
- note: 'Runs launch then live verify so a developer can validate app startup, runner-owned CDP bridge, screenshot capture, and tiny recipe control from one runner-owned command.',
120
- generatedAt: new Date().toISOString(),
121
- }, null, 2)}\n`);
122
- NODE
123
-
124
- echo "Mobile live validation summary: $ARTIFACTS/summary.json"
125
- [ "$launch_status" -eq 0 ] && [ "$verify_status" -eq 0 ]