@deeeed/metamask-harness 0.33.2 → 0.34.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 (54) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +18 -2
  3. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +83 -23
  4. package/dist/adapters/mobile/prepare.js +14 -0
  5. package/dist/adapters/mobile/video-recorder.js +282 -0
  6. package/dist/adapters.js +225 -35
  7. package/dist/command-contract.js +1 -0
  8. package/dist/commands/device-target.js +17 -6
  9. package/dist/commands/launch/index.js +27 -5
  10. package/dist/commands/launch/mobile.js +9 -2
  11. package/dist/commands/run-engine.js +34 -2
  12. package/dist/devices.js +9 -2
  13. package/dist/heal-bounds.js +10 -0
  14. package/dist/mm-harness-cli.js +4 -2
  15. package/dist/recipe-security.js +1 -0
  16. package/dist/recording-target.js +23 -5
  17. package/dist/runner.js +107 -6
  18. package/dist/runtime-context.js +33 -1
  19. package/docs/RECIPES.md +41 -0
  20. package/library/actions/mobile/app/network-control.mjs +135 -0
  21. package/library/actions/mobile/app/network.mjs +4 -0
  22. package/library/actions/mobile/perps/capture_performance.mjs +4 -0
  23. package/library/actions/mobile/perps/performance-capture.mjs +383 -0
  24. package/library/actions/mobile/perps/perps.mjs +16 -0
  25. package/library/actions/mobile/platform/bridge.mjs +229 -13
  26. package/library/actions/mobile/wallet/ensure_unlocked.mjs +27 -6
  27. package/library/actions/mobile/wallet/select_account.mjs +21 -17
  28. package/library/manifests/mobile.action-manifest.json +130 -1
  29. package/library/recipes/mobile/perps/performance.homepage.android-background-reconnect.recipe.json +159 -0
  30. package/library/recipes/mobile/perps/performance.homepage.android-background-short.recipe.json +157 -0
  31. package/library/recipes/mobile/perps/performance.homepage.android-cold-disk-cache.recipe.json +147 -0
  32. package/library/recipes/mobile/perps/performance.homepage.android-cold-no-cache.recipe.json +137 -0
  33. package/library/recipes/mobile/perps/performance.homepage.android-network-recovery.recipe.json +149 -0
  34. package/library/recipes/mobile/perps/performance.homepage.ios-background-reconnect.recipe.json +110 -0
  35. package/library/recipes/mobile/perps/performance.homepage.ios-background-short.recipe.json +108 -0
  36. package/library/recipes/mobile/perps/performance.homepage.ios-cold-disk-cache.recipe.json +122 -0
  37. package/library/recipes/mobile/perps/performance.homepage.ios-cold-no-cache.recipe.json +95 -0
  38. package/package.json +1 -1
  39. package/scripts/site-contrast.mjs +34 -1
  40. package/site/architecture.html +12 -2
  41. package/site/assets/style.css +20 -1
  42. package/site/cheatsheet.html +7 -6
  43. package/site/index.html +119 -77
  44. package/site/perps.html +195 -0
  45. package/site/recipes.html +23 -1
  46. package/site/reviewers.html +2 -1
  47. package/site/tutorials/index.html +2 -1
  48. package/site/tutorials/v1.html +3 -2
  49. package/site/tutorials/v2.html +6 -5
  50. package/site/tutorials/v3.html +43 -2
  51. package/site/tutorials/v4.html +2 -1
  52. package/site/tutorials/v5.html +2 -1
  53. package/site/tutorials/v6.html +2 -1
  54. package/site/tutorials/v7.html +2 -1
@@ -0,0 +1,122 @@
1
+ {
2
+ "$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
3
+ "title": "iOS Homepage Perps cold disk-cache proof",
4
+ "description": "Warms the Perps disk cache without creating exchange state, restarts the assigned iOS app, and validates disk hydration plus fresh trending content at the visible Homepage endpoint.",
5
+ "workflow": {
6
+ "entry": "ensure-wallet",
7
+ "nodes": {
8
+ "ensure-wallet": {
9
+ "action": "metamask.wallet.ensure_unlocked",
10
+ "intent": "Use the existing simulator fixture",
11
+ "next": "open-wallet-home-before"
12
+ },
13
+ "open-wallet-home-before": {
14
+ "action": "ui.navigate",
15
+ "page": "home",
16
+ "intent": "Open Wallet Home so current Perps streams are cached",
17
+ "timeout_ms": 30000,
18
+ "next": "wait-perps-before"
19
+ },
20
+ "wait-perps-before": {
21
+ "action": "ui.wait_for",
22
+ "test_id": "homepage-section-title-perps",
23
+ "expected": "present",
24
+ "intent": "Require Perps content before restart",
25
+ "timeout_ms": 30000,
26
+ "next": "show-perps-before"
27
+ },
28
+ "show-perps-before": {
29
+ "action": "ui.scroll",
30
+ "test_id": "homepage-section-title-perps",
31
+ "scroll_into_view": true,
32
+ "animated": true,
33
+ "settle": true,
34
+ "intent": "Make current Perps data visible and cacheable",
35
+ "timeout_ms": 30000,
36
+ "next": "warm-disk-cache"
37
+ },
38
+ "warm-disk-cache": {
39
+ "action": "wait",
40
+ "duration_ms": 2500,
41
+ "intent": "Allow persisted stream cache writes to settle",
42
+ "next": "start-performance-capture"
43
+ },
44
+ "start-performance-capture": {
45
+ "action": "metamask.perps.capture_performance",
46
+ "phase": "start",
47
+ "intent": "Start evidence before cold process restart",
48
+ "next": "restart-app"
49
+ },
50
+ "restart-app": {
51
+ "action": "app.lifecycle",
52
+ "command": "restart",
53
+ "settle_ms": 15000,
54
+ "runtime_ready_timeout_ms": 60000,
55
+ "intent": "Cold-restart while preserving persisted app data",
56
+ "next": "unlock-after-restart"
57
+ },
58
+ "unlock-after-restart": {
59
+ "action": "metamask.wallet.ensure_unlocked",
60
+ "intent": "Restore the same fixture after cold launch",
61
+ "next": "open-wallet-home-after"
62
+ },
63
+ "open-wallet-home-after": {
64
+ "action": "ui.navigate",
65
+ "page": "home",
66
+ "intent": "Return to Wallet Home after disk hydration",
67
+ "timeout_ms": 30000,
68
+ "next": "wait-perps-after"
69
+ },
70
+ "wait-perps-after": {
71
+ "action": "ui.wait_for",
72
+ "test_id": "homepage-section-title-perps",
73
+ "expected": "present",
74
+ "intent": "Require the Perps section after cold launch",
75
+ "timeout_ms": 30000,
76
+ "next": "show-perps-after"
77
+ },
78
+ "show-perps-after": {
79
+ "action": "ui.scroll",
80
+ "test_id": "homepage-section-title-perps",
81
+ "scroll_into_view": true,
82
+ "animated": true,
83
+ "settle": true,
84
+ "intent": "Bring disk-hydrated/fresh content into the viewport",
85
+ "timeout_ms": 30000,
86
+ "next": "capture-after"
87
+ },
88
+ "capture-after": {
89
+ "action": "ui.screenshot",
90
+ "path": "screenshots/ios-homepage-perps-cold-disk-cache.png",
91
+ "intent": "Preserve visible cold disk-cache content",
92
+ "next": "settle-frame"
93
+ },
94
+ "settle-frame": {
95
+ "action": "wait",
96
+ "duration_ms": 2500,
97
+ "intent": "Allow the visible next-frame checkpoint to finish",
98
+ "next": "finish-performance-capture"
99
+ },
100
+ "finish-performance-capture": {
101
+ "action": "metamask.perps.capture_performance",
102
+ "phase": "end",
103
+ "required_stages": [
104
+ "disk_cache_hydrated",
105
+ "viewport_demand",
106
+ "socket_received",
107
+ "react_commit",
108
+ "next_frame_checkpoint"
109
+ ],
110
+ "required_lifecycles": ["cold_disk_cache"],
111
+ "required_sources": ["disk_cache", "fresh_socket"],
112
+ "required_fresh_content_variants": ["trending"],
113
+ "intent": "Require cold disk hydration and fresh visible content",
114
+ "next": "done"
115
+ },
116
+ "done": {
117
+ "action": "end",
118
+ "status": "pass"
119
+ }
120
+ }
121
+ }
122
+ }
@@ -0,0 +1,95 @@
1
+ {
2
+ "$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
3
+ "title": "iOS Homepage Perps cold start without cache proof",
4
+ "description": "Clears only Perps performance caches, restarts the assigned iOS app without replacing wallet state, and validates fresh trending content at the visible Homepage endpoint.",
5
+ "workflow": {
6
+ "entry": "ensure-wallet",
7
+ "nodes": {
8
+ "ensure-wallet": {
9
+ "action": "metamask.wallet.ensure_unlocked",
10
+ "intent": "Preserve and unlock the existing simulator fixture",
11
+ "next": "start-performance-capture"
12
+ },
13
+ "start-performance-capture": {
14
+ "action": "metamask.perps.capture_performance",
15
+ "phase": "start",
16
+ "intent": "Start evidence before cache removal and process restart",
17
+ "next": "clear-perps-caches"
18
+ },
19
+ "clear-perps-caches": {
20
+ "action": "metamask.perps.clear_performance_caches",
21
+ "intent": "Remove only Perps persisted market and user snapshots",
22
+ "next": "restart-app"
23
+ },
24
+ "restart-app": {
25
+ "action": "app.lifecycle",
26
+ "command": "restart",
27
+ "settle_ms": 15000,
28
+ "runtime_ready_timeout_ms": 60000,
29
+ "intent": "Cold-restart the assigned iOS app without deleting wallet state",
30
+ "next": "unlock-after-restart"
31
+ },
32
+ "unlock-after-restart": {
33
+ "action": "metamask.wallet.ensure_unlocked",
34
+ "intent": "Restore the same fixture after the cold process launch",
35
+ "next": "open-wallet-home"
36
+ },
37
+ "open-wallet-home": {
38
+ "action": "ui.navigate",
39
+ "page": "home",
40
+ "intent": "Open Wallet Home after the no-cache cold launch",
41
+ "timeout_ms": 30000,
42
+ "next": "wait-perps-section"
43
+ },
44
+ "wait-perps-section": {
45
+ "action": "ui.wait_for",
46
+ "test_id": "homepage-section-title-perps",
47
+ "expected": "present",
48
+ "intent": "Require the Perps section after cold launch",
49
+ "timeout_ms": 30000,
50
+ "next": "show-perps-section"
51
+ },
52
+ "show-perps-section": {
53
+ "action": "ui.scroll",
54
+ "test_id": "homepage-section-title-perps",
55
+ "scroll_into_view": true,
56
+ "animated": true,
57
+ "settle": true,
58
+ "intent": "Bring fresh Perps content into the viewport",
59
+ "timeout_ms": 30000,
60
+ "next": "capture-after"
61
+ },
62
+ "capture-after": {
63
+ "action": "ui.screenshot",
64
+ "path": "screenshots/ios-homepage-perps-cold-no-cache.png",
65
+ "intent": "Preserve visible cold-no-cache content",
66
+ "next": "settle-frame"
67
+ },
68
+ "settle-frame": {
69
+ "action": "wait",
70
+ "duration_ms": 2500,
71
+ "intent": "Allow the visible next-frame checkpoint to finish",
72
+ "next": "finish-performance-capture"
73
+ },
74
+ "finish-performance-capture": {
75
+ "action": "metamask.perps.capture_performance",
76
+ "phase": "end",
77
+ "required_stages": [
78
+ "viewport_demand",
79
+ "socket_received",
80
+ "react_commit",
81
+ "next_frame_checkpoint"
82
+ ],
83
+ "required_lifecycles": ["cold_no_cache"],
84
+ "required_sources": ["fresh_socket"],
85
+ "required_fresh_content_variants": ["trending"],
86
+ "intent": "Require cold-no-cache lifecycle and fresh visible trending content",
87
+ "next": "done"
88
+ },
89
+ "done": {
90
+ "action": "end",
91
+ "status": "pass"
92
+ }
93
+ }
94
+ }
95
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.33.2",
3
+ "version": "0.34.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"
@@ -342,7 +342,9 @@ async function checkBehavior(browserEndpoint, port, pages) {
342
342
  },
343
343
  },
344
344
  });
345
- const button = document.querySelector('.prompt .copy');
345
+ // The onboarding prompt is the hero call to action; it was a collapsible
346
+ // before, so accept either shape.
347
+ const button = document.querySelector('.cmd-hero .copy, .prompt .copy');
346
348
  if (!button) throw new Error('copy button was not initialized');
347
349
  button.click();
348
350
  await new Promise((resolve) => setTimeout(resolve, 25));
@@ -390,6 +392,17 @@ async function checkBehavior(browserEndpoint, port, pages) {
390
392
 
391
393
  for (const relative of pages) {
392
394
  await withPage(browserEndpoint, `${base}${relative}`, async (page) => {
395
+ const copyControls = await page.evaluate(String.raw`
396
+ (() => ({
397
+ blocks: document.querySelectorAll('.cmd').length,
398
+ buttons: document.querySelectorAll('.cmd > .copy').length,
399
+ }))()
400
+ `);
401
+ if (copyControls.blocks !== copyControls.buttons) {
402
+ throw new Error(
403
+ `${relative} initialized ${copyControls.buttons} copy buttons for ${copyControls.blocks} command blocks`,
404
+ );
405
+ }
393
406
  const broken = await page.evaluate(String.raw`
394
407
  (async () => {
395
408
  const links = [...document.querySelectorAll('a[href]')]
@@ -413,6 +426,26 @@ async function checkBehavior(browserEndpoint, port, pages) {
413
426
  if (broken.length) {
414
427
  throw new Error(`${relative} has broken internal links: ${broken.join(', ')}`);
415
428
  }
429
+ for (const width of [834, 390]) {
430
+ await page.send('Emulation.setDeviceMetricsOverride', {
431
+ width,
432
+ height: 1000,
433
+ deviceScaleFactor: 1,
434
+ mobile: false,
435
+ });
436
+ const layout = await page.evaluate(String.raw`
437
+ (() => {
438
+ const header = document.querySelector('.topbar')?.getBoundingClientRect();
439
+ const nav = document.querySelector('.nav')?.getBoundingClientRect();
440
+ return {
441
+ navOverflow: Boolean(header && nav && nav.bottom > header.bottom + 1),
442
+ };
443
+ })()
444
+ `);
445
+ if (layout.navOverflow) {
446
+ throw new Error(`${relative} navigation escapes its header at ${width}px`);
447
+ }
448
+ }
416
449
  });
417
450
  }
418
451
  }
@@ -19,6 +19,7 @@
19
19
  <nav class="nav" aria-label="Main">
20
20
  <a href="index.html">Start Here</a>
21
21
  <a href="recipes.html">Recipes</a>
22
+ <a href="perps.html">Perps</a>
22
23
  <a href="cheatsheet.html">Cheatsheet</a>
23
24
  <a href="architecture.html" aria-current="page">Architecture</a>
24
25
  <a href="tutorials/index.html">Tutorials</a>
@@ -177,6 +178,12 @@
177
178
  these contracts. Evidence returns in the same shape either way, so a fleet result is reviewable by
178
179
  whoever wrote the recipe. Parity is structural: one spec, both sides.
179
180
  </p>
181
+ <p>
182
+ That is what makes the trust layer scale. A proof written once becomes a permanent guard: it is
183
+ replayed by whoever changes that code next, it stays readable as evidence no matter who or what ran
184
+ it, and the bug it was written for cannot quietly return. Review does not get weaker as volume
185
+ grows.
186
+ </p>
180
187
  <p>The seam shows in the CLI's own help — the flags a control plane supplies:</p>
181
188
  <div class="out">managed trust boundary (not needed for normal runs; Farmslot supplies it):
182
189
  --source-trust, --source-kind, --source-name, --source-digest,
@@ -196,7 +203,10 @@
196
203
 
197
204
  <section class="wrap">
198
205
  <h2 id="loop">The improvement loop</h2>
199
- <p>The stack is arranged so each run can leave the next one better informed.</p>
206
+ <p>
207
+ A recipe guards the regression it was written for. This is how a team catches the ones nobody has
208
+ written down yet — each run leaving the next one better informed.
209
+ </p>
200
210
 
201
211
  <div class="doctrine">
202
212
  <p>
@@ -465,7 +475,7 @@
465
475
  <footer class="footer">
466
476
  <div class="wrap">
467
477
  <p>Internal getting-started guide for the MetaMask agentic coding workflow. Not official MetaMask product documentation.</p>
468
- <p>Verified against mm-harness 0.26+.</p>
478
+ <p>Verified against mm-harness 0.33+.</p>
469
479
  </div>
470
480
  </footer>
471
481
 
@@ -136,9 +136,18 @@ main { padding-bottom: 6rem; }
136
136
 
137
137
  .brand-name { font-family: var(--mono); font-size: 0.95rem; }
138
138
 
139
- .nav { display: flex; gap: 0.3rem; margin-left: auto; flex-wrap: wrap; }
139
+ .nav {
140
+ display: flex;
141
+ gap: 0.3rem;
142
+ margin-left: auto;
143
+ min-width: 0;
144
+ overflow-x: auto;
145
+ flex-wrap: nowrap;
146
+ scrollbar-width: thin;
147
+ }
140
148
 
141
149
  .nav a {
150
+ flex: none;
142
151
  color: var(--text-dim);
143
152
  font-size: 0.885rem;
144
153
  font-weight: 500;
@@ -333,6 +342,16 @@ main { padding-bottom: 6rem; }
333
342
 
334
343
  @media (hover: none) { .copy { opacity: 1; } }
335
344
 
345
+ /* The one-prompt block is a call to action: its copy control never hides, and
346
+ the prompt scrolls rather than pushing the page down. */
347
+ .cmd-hero .copy { opacity: 1; }
348
+ .cmd-hero pre {
349
+ white-space: pre-wrap;
350
+ word-break: break-word;
351
+ max-height: 22rem;
352
+ overflow-y: auto;
353
+ }
354
+
336
355
  /* Expected-output block */
337
356
  .out {
338
357
  background: var(--bg-raised);
@@ -19,6 +19,7 @@
19
19
  <nav class="nav" aria-label="Main">
20
20
  <a href="index.html">Start Here</a>
21
21
  <a href="recipes.html">Recipes</a>
22
+ <a href="perps.html">Perps</a>
22
23
  <a href="cheatsheet.html" aria-current="page">Cheatsheet</a>
23
24
  <a href="architecture.html">Architecture</a>
24
25
  <a href="tutorials/index.html">Tutorials</a>
@@ -37,8 +38,8 @@
37
38
  same command. Filter by what you actually work on.
38
39
  </p>
39
40
  <div class="hero-meta">
40
- <span>mm-harness 0.26+</span>
41
- <span>one bin, 20 commands</span>
41
+ <span>mm-harness 0.33+</span>
42
+ <span>one bin, 21 commands</span>
42
43
  <span>--json on everything</span>
43
44
  </div>
44
45
  </section>
@@ -146,15 +147,15 @@
146
147
  <tr data-platforms="all"><td>A recipe with parameters</td><td><code>mm-harness run perps.clean-market-testnet market=BTC</code></td></tr>
147
148
  <tr data-platforms="all"><td>A recipe file on disk</td><td><code>mm-harness run ./my-recipe.json</code></td></tr>
148
149
  <tr data-platforms="all"><td>Add a team recipe library</td><td><code>mm-harness run &lt;recipe&gt; --library perps=/path/to/library</code></td></tr>
149
- <tr data-platforms="mobile"><td>Record video of the whole run</td><td><code>mm-harness run &lt;recipe&gt; --record-video=full-run</code></td></tr>
150
+ <tr data-platforms="extension mobile"><td>Record video of the whole run (Extension or iOS)</td><td><code>mm-harness run &lt;recipe&gt; --record-video=full-run</code></td></tr>
150
151
  <tr data-platforms="all"><td>Streaming progress for an agent</td><td><code>mm-harness run &lt;recipe&gt; --json-stream</code></td></tr>
151
152
  <tr data-platforms="all"><td>What did I last run, and how did it go</td><td><code>mm-harness last</code></td></tr>
152
153
  </tbody>
153
154
  </table>
154
155
  </div>
155
156
  <p class="step-why" style="font-size:.92rem">
156
- iOS carries video; Extension capture is screenshots only. That asymmetry is real — do not promise
157
- a reviewer a video of an extension run.
157
+ Extension and iOS can record full-run video through capture-helper. Android replay video is not
158
+ implemented yet; use screenshot evidence there.
158
159
  </p>
159
160
  </div>
160
161
 
@@ -296,7 +297,7 @@
296
297
  <footer class="footer">
297
298
  <div class="wrap-wide">
298
299
  <p>Internal getting-started guide for the MetaMask agentic coding workflow. Not official MetaMask product documentation.</p>
299
- <p>Verified against mm-harness 0.26+.</p>
300
+ <p>Verified against mm-harness 0.33+.</p>
300
301
  </div>
301
302
  </footer>
302
303