@runchr/gstack-antigravity 0.1.1 → 0.1.3

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.

Potentially problematic release.


This version of @runchr/gstack-antigravity might be problematic. Click here for more details.

Files changed (229) hide show
  1. package/.agents/skills/gstack/.agents/skills/gstack/SKILL.md +651 -0
  2. package/.agents/skills/gstack/.agents/skills/gstack-autoplan/SKILL.md +678 -0
  3. package/.agents/skills/gstack/.agents/skills/gstack-benchmark/SKILL.md +482 -0
  4. package/.agents/skills/gstack/.agents/skills/gstack-browse/SKILL.md +511 -0
  5. package/.agents/skills/gstack/.agents/skills/gstack-canary/SKILL.md +486 -0
  6. package/.agents/skills/gstack/.agents/skills/gstack-careful/SKILL.md +50 -0
  7. package/.agents/skills/gstack/.agents/skills/gstack-cso/SKILL.md +607 -0
  8. package/.agents/skills/gstack/.agents/skills/gstack-design-consultation/SKILL.md +615 -0
  9. package/.agents/skills/gstack/.agents/skills/gstack-design-review/SKILL.md +988 -0
  10. package/.agents/skills/gstack/.agents/skills/gstack-document-release/SKILL.md +604 -0
  11. package/.agents/skills/gstack/.agents/skills/gstack-freeze/SKILL.md +67 -0
  12. package/.agents/skills/gstack/.agents/skills/gstack-guard/SKILL.md +62 -0
  13. package/.agents/skills/gstack/.agents/skills/gstack-investigate/SKILL.md +415 -0
  14. package/.agents/skills/gstack/.agents/skills/gstack-land-and-deploy/SKILL.md +873 -0
  15. package/.agents/skills/gstack/.agents/skills/gstack-office-hours/SKILL.md +986 -0
  16. package/.agents/skills/gstack/.agents/skills/gstack-plan-ceo-review/SKILL.md +1268 -0
  17. package/.agents/skills/gstack/.agents/skills/gstack-plan-design-review/SKILL.md +668 -0
  18. package/.agents/skills/gstack/.agents/skills/gstack-plan-eng-review/SKILL.md +826 -0
  19. package/.agents/skills/gstack/.agents/skills/gstack-qa/SKILL.md +1006 -0
  20. package/.agents/skills/gstack/.agents/skills/gstack-qa-only/SKILL.md +626 -0
  21. package/.agents/skills/gstack/.agents/skills/gstack-retro/SKILL.md +1065 -0
  22. package/.agents/skills/gstack/.agents/skills/gstack-review/SKILL.md +704 -0
  23. package/.agents/skills/gstack/.agents/skills/gstack-setup-browser-cookies/SKILL.md +325 -0
  24. package/.agents/skills/gstack/.agents/skills/gstack-setup-deploy/SKILL.md +450 -0
  25. package/.agents/skills/gstack/.agents/skills/gstack-ship/SKILL.md +1312 -0
  26. package/.agents/skills/gstack/.agents/skills/gstack-unfreeze/SKILL.md +36 -0
  27. package/.agents/skills/gstack/.agents/skills/gstack-upgrade/SKILL.md +220 -0
  28. package/.agents/skills/gstack/.env.example +5 -0
  29. package/.agents/skills/gstack/.github/workflows/skill-docs.yml +17 -0
  30. package/.agents/skills/gstack/AGENTS.md +49 -0
  31. package/.agents/skills/gstack/ARCHITECTURE.md +359 -0
  32. package/.agents/skills/gstack/BROWSER.md +271 -0
  33. package/.agents/skills/gstack/CHANGELOG.md +800 -0
  34. package/.agents/skills/gstack/CLAUDE.md +284 -0
  35. package/.agents/skills/gstack/CONTRIBUTING.md +370 -0
  36. package/.agents/skills/gstack/ETHOS.md +129 -0
  37. package/.agents/skills/gstack/LICENSE +21 -0
  38. package/.agents/skills/gstack/README.md +228 -0
  39. package/.agents/skills/gstack/SKILL.md +657 -0
  40. package/.agents/skills/gstack/SKILL.md.tmpl +281 -0
  41. package/.agents/skills/gstack/TODOS.md +564 -0
  42. package/.agents/skills/gstack/VERSION +1 -0
  43. package/.agents/skills/gstack/autoplan/SKILL.md +689 -0
  44. package/.agents/skills/gstack/autoplan/SKILL.md.tmpl +416 -0
  45. package/.agents/skills/gstack/benchmark/SKILL.md +489 -0
  46. package/.agents/skills/gstack/benchmark/SKILL.md.tmpl +233 -0
  47. package/.agents/skills/gstack/bin/dev-setup +68 -0
  48. package/.agents/skills/gstack/bin/dev-teardown +56 -0
  49. package/.agents/skills/gstack/bin/gstack-analytics +191 -0
  50. package/.agents/skills/gstack/bin/gstack-community-dashboard +113 -0
  51. package/.agents/skills/gstack/bin/gstack-config +38 -0
  52. package/.agents/skills/gstack/bin/gstack-diff-scope +71 -0
  53. package/.agents/skills/gstack/bin/gstack-global-discover.ts +591 -0
  54. package/.agents/skills/gstack/bin/gstack-repo-mode +93 -0
  55. package/.agents/skills/gstack/bin/gstack-review-log +9 -0
  56. package/.agents/skills/gstack/bin/gstack-review-read +12 -0
  57. package/.agents/skills/gstack/bin/gstack-slug +15 -0
  58. package/.agents/skills/gstack/bin/gstack-telemetry-log +158 -0
  59. package/.agents/skills/gstack/bin/gstack-telemetry-sync +127 -0
  60. package/.agents/skills/gstack/bin/gstack-update-check +196 -0
  61. package/.agents/skills/gstack/browse/SKILL.md +517 -0
  62. package/.agents/skills/gstack/browse/SKILL.md.tmpl +141 -0
  63. package/.agents/skills/gstack/browse/bin/find-browse +21 -0
  64. package/.agents/skills/gstack/browse/bin/remote-slug +14 -0
  65. package/.agents/skills/gstack/browse/scripts/build-node-server.sh +48 -0
  66. package/.agents/skills/gstack/browse/src/browser-manager.ts +634 -0
  67. package/.agents/skills/gstack/browse/src/buffers.ts +137 -0
  68. package/.agents/skills/gstack/browse/src/bun-polyfill.cjs +109 -0
  69. package/.agents/skills/gstack/browse/src/cli.ts +420 -0
  70. package/.agents/skills/gstack/browse/src/commands.ts +111 -0
  71. package/.agents/skills/gstack/browse/src/config.ts +150 -0
  72. package/.agents/skills/gstack/browse/src/cookie-import-browser.ts +417 -0
  73. package/.agents/skills/gstack/browse/src/cookie-picker-routes.ts +207 -0
  74. package/.agents/skills/gstack/browse/src/cookie-picker-ui.ts +541 -0
  75. package/.agents/skills/gstack/browse/src/find-browse.ts +61 -0
  76. package/.agents/skills/gstack/browse/src/meta-commands.ts +269 -0
  77. package/.agents/skills/gstack/browse/src/platform.ts +17 -0
  78. package/.agents/skills/gstack/browse/src/read-commands.ts +335 -0
  79. package/.agents/skills/gstack/browse/src/server.ts +369 -0
  80. package/.agents/skills/gstack/browse/src/snapshot.ts +398 -0
  81. package/.agents/skills/gstack/browse/src/url-validation.ts +91 -0
  82. package/.agents/skills/gstack/browse/src/write-commands.ts +352 -0
  83. package/.agents/skills/gstack/browse/test/bun-polyfill.test.ts +72 -0
  84. package/.agents/skills/gstack/browse/test/commands.test.ts +1836 -0
  85. package/.agents/skills/gstack/browse/test/config.test.ts +250 -0
  86. package/.agents/skills/gstack/browse/test/cookie-import-browser.test.ts +397 -0
  87. package/.agents/skills/gstack/browse/test/cookie-picker-routes.test.ts +205 -0
  88. package/.agents/skills/gstack/browse/test/find-browse.test.ts +50 -0
  89. package/.agents/skills/gstack/browse/test/fixtures/basic.html +33 -0
  90. package/.agents/skills/gstack/browse/test/fixtures/cursor-interactive.html +22 -0
  91. package/.agents/skills/gstack/browse/test/fixtures/dialog.html +15 -0
  92. package/.agents/skills/gstack/browse/test/fixtures/empty.html +2 -0
  93. package/.agents/skills/gstack/browse/test/fixtures/forms.html +55 -0
  94. package/.agents/skills/gstack/browse/test/fixtures/qa-eval-checkout.html +108 -0
  95. package/.agents/skills/gstack/browse/test/fixtures/qa-eval-spa.html +98 -0
  96. package/.agents/skills/gstack/browse/test/fixtures/qa-eval.html +51 -0
  97. package/.agents/skills/gstack/browse/test/fixtures/responsive.html +49 -0
  98. package/.agents/skills/gstack/browse/test/fixtures/snapshot.html +55 -0
  99. package/.agents/skills/gstack/browse/test/fixtures/spa.html +24 -0
  100. package/.agents/skills/gstack/browse/test/fixtures/states.html +17 -0
  101. package/.agents/skills/gstack/browse/test/fixtures/upload.html +25 -0
  102. package/.agents/skills/gstack/browse/test/gstack-config.test.ts +125 -0
  103. package/.agents/skills/gstack/browse/test/gstack-update-check.test.ts +467 -0
  104. package/.agents/skills/gstack/browse/test/handoff.test.ts +235 -0
  105. package/.agents/skills/gstack/browse/test/path-validation.test.ts +63 -0
  106. package/.agents/skills/gstack/browse/test/platform.test.ts +37 -0
  107. package/.agents/skills/gstack/browse/test/snapshot.test.ts +467 -0
  108. package/.agents/skills/gstack/browse/test/test-server.ts +57 -0
  109. package/.agents/skills/gstack/browse/test/url-validation.test.ts +72 -0
  110. package/.agents/skills/gstack/canary/SKILL.md +493 -0
  111. package/.agents/skills/gstack/canary/SKILL.md.tmpl +220 -0
  112. package/.agents/skills/gstack/careful/SKILL.md +59 -0
  113. package/.agents/skills/gstack/careful/SKILL.md.tmpl +57 -0
  114. package/.agents/skills/gstack/careful/bin/check-careful.sh +112 -0
  115. package/.agents/skills/gstack/codex/SKILL.md +677 -0
  116. package/.agents/skills/gstack/codex/SKILL.md.tmpl +356 -0
  117. package/.agents/skills/gstack/conductor.json +6 -0
  118. package/.agents/skills/gstack/cso/SKILL.md +615 -0
  119. package/.agents/skills/gstack/cso/SKILL.md.tmpl +376 -0
  120. package/.agents/skills/gstack/design-consultation/SKILL.md +625 -0
  121. package/.agents/skills/gstack/design-consultation/SKILL.md.tmpl +369 -0
  122. package/.agents/skills/gstack/design-review/SKILL.md +998 -0
  123. package/.agents/skills/gstack/design-review/SKILL.md.tmpl +262 -0
  124. package/.agents/skills/gstack/docs/images/github-2013.png +0 -0
  125. package/.agents/skills/gstack/docs/images/github-2026.png +0 -0
  126. package/.agents/skills/gstack/docs/skills.md +877 -0
  127. package/.agents/skills/gstack/document-release/SKILL.md +613 -0
  128. package/.agents/skills/gstack/document-release/SKILL.md.tmpl +357 -0
  129. package/.agents/skills/gstack/freeze/SKILL.md +82 -0
  130. package/.agents/skills/gstack/freeze/SKILL.md.tmpl +80 -0
  131. package/.agents/skills/gstack/freeze/bin/check-freeze.sh +68 -0
  132. package/.agents/skills/gstack/gstack-upgrade/SKILL.md +226 -0
  133. package/.agents/skills/gstack/gstack-upgrade/SKILL.md.tmpl +224 -0
  134. package/.agents/skills/gstack/guard/SKILL.md +82 -0
  135. package/.agents/skills/gstack/guard/SKILL.md.tmpl +80 -0
  136. package/.agents/skills/gstack/investigate/SKILL.md +435 -0
  137. package/.agents/skills/gstack/investigate/SKILL.md.tmpl +196 -0
  138. package/.agents/skills/gstack/land-and-deploy/SKILL.md +880 -0
  139. package/.agents/skills/gstack/land-and-deploy/SKILL.md.tmpl +575 -0
  140. package/.agents/skills/gstack/office-hours/SKILL.md +996 -0
  141. package/.agents/skills/gstack/office-hours/SKILL.md.tmpl +624 -0
  142. package/.agents/skills/gstack/package.json +55 -0
  143. package/.agents/skills/gstack/plan-ceo-review/SKILL.md +1277 -0
  144. package/.agents/skills/gstack/plan-ceo-review/SKILL.md.tmpl +838 -0
  145. package/.agents/skills/gstack/plan-design-review/SKILL.md +676 -0
  146. package/.agents/skills/gstack/plan-design-review/SKILL.md.tmpl +314 -0
  147. package/.agents/skills/gstack/plan-eng-review/SKILL.md +836 -0
  148. package/.agents/skills/gstack/plan-eng-review/SKILL.md.tmpl +279 -0
  149. package/.agents/skills/gstack/qa/SKILL.md +1016 -0
  150. package/.agents/skills/gstack/qa/SKILL.md.tmpl +316 -0
  151. package/.agents/skills/gstack/qa/references/issue-taxonomy.md +85 -0
  152. package/.agents/skills/gstack/qa/templates/qa-report-template.md +126 -0
  153. package/.agents/skills/gstack/qa-only/SKILL.md +633 -0
  154. package/.agents/skills/gstack/qa-only/SKILL.md.tmpl +101 -0
  155. package/.agents/skills/gstack/retro/SKILL.md +1072 -0
  156. package/.agents/skills/gstack/retro/SKILL.md.tmpl +833 -0
  157. package/.agents/skills/gstack/review/SKILL.md +849 -0
  158. package/.agents/skills/gstack/review/SKILL.md.tmpl +259 -0
  159. package/.agents/skills/gstack/review/TODOS-format.md +62 -0
  160. package/.agents/skills/gstack/review/checklist.md +190 -0
  161. package/.agents/skills/gstack/review/design-checklist.md +132 -0
  162. package/.agents/skills/gstack/review/greptile-triage.md +220 -0
  163. package/.agents/skills/gstack/scripts/analytics.ts +190 -0
  164. package/.agents/skills/gstack/scripts/dev-skill.ts +82 -0
  165. package/.agents/skills/gstack/scripts/eval-compare.ts +96 -0
  166. package/.agents/skills/gstack/scripts/eval-list.ts +116 -0
  167. package/.agents/skills/gstack/scripts/eval-select.ts +86 -0
  168. package/.agents/skills/gstack/scripts/eval-summary.ts +187 -0
  169. package/.agents/skills/gstack/scripts/eval-watch.ts +172 -0
  170. package/.agents/skills/gstack/scripts/gen-skill-docs.ts +2414 -0
  171. package/.agents/skills/gstack/scripts/skill-check.ts +167 -0
  172. package/.agents/skills/gstack/setup +269 -0
  173. package/.agents/skills/gstack/setup-browser-cookies/SKILL.md +330 -0
  174. package/.agents/skills/gstack/setup-browser-cookies/SKILL.md.tmpl +74 -0
  175. package/.agents/skills/gstack/setup-deploy/SKILL.md +459 -0
  176. package/.agents/skills/gstack/setup-deploy/SKILL.md.tmpl +220 -0
  177. package/.agents/skills/gstack/ship/SKILL.md +1457 -0
  178. package/.agents/skills/gstack/ship/SKILL.md.tmpl +528 -0
  179. package/.agents/skills/gstack/supabase/config.sh +10 -0
  180. package/.agents/skills/gstack/supabase/functions/community-pulse/index.ts +59 -0
  181. package/.agents/skills/gstack/supabase/functions/telemetry-ingest/index.ts +135 -0
  182. package/.agents/skills/gstack/supabase/functions/update-check/index.ts +37 -0
  183. package/.agents/skills/gstack/supabase/migrations/001_telemetry.sql +89 -0
  184. package/.agents/skills/gstack/test/analytics.test.ts +277 -0
  185. package/.agents/skills/gstack/test/codex-e2e.test.ts +197 -0
  186. package/.agents/skills/gstack/test/fixtures/coverage-audit-fixture.ts +76 -0
  187. package/.agents/skills/gstack/test/fixtures/eval-baselines.json +7 -0
  188. package/.agents/skills/gstack/test/fixtures/qa-eval-checkout-ground-truth.json +43 -0
  189. package/.agents/skills/gstack/test/fixtures/qa-eval-ground-truth.json +43 -0
  190. package/.agents/skills/gstack/test/fixtures/qa-eval-spa-ground-truth.json +43 -0
  191. package/.agents/skills/gstack/test/fixtures/review-eval-design-slop.css +86 -0
  192. package/.agents/skills/gstack/test/fixtures/review-eval-design-slop.html +41 -0
  193. package/.agents/skills/gstack/test/fixtures/review-eval-enum-diff.rb +30 -0
  194. package/.agents/skills/gstack/test/fixtures/review-eval-enum.rb +27 -0
  195. package/.agents/skills/gstack/test/fixtures/review-eval-vuln.rb +14 -0
  196. package/.agents/skills/gstack/test/gemini-e2e.test.ts +173 -0
  197. package/.agents/skills/gstack/test/gen-skill-docs.test.ts +1049 -0
  198. package/.agents/skills/gstack/test/global-discover.test.ts +187 -0
  199. package/.agents/skills/gstack/test/helpers/codex-session-runner.ts +282 -0
  200. package/.agents/skills/gstack/test/helpers/e2e-helpers.ts +239 -0
  201. package/.agents/skills/gstack/test/helpers/eval-store.test.ts +548 -0
  202. package/.agents/skills/gstack/test/helpers/eval-store.ts +689 -0
  203. package/.agents/skills/gstack/test/helpers/gemini-session-runner.test.ts +104 -0
  204. package/.agents/skills/gstack/test/helpers/gemini-session-runner.ts +201 -0
  205. package/.agents/skills/gstack/test/helpers/llm-judge.ts +130 -0
  206. package/.agents/skills/gstack/test/helpers/observability.test.ts +283 -0
  207. package/.agents/skills/gstack/test/helpers/session-runner.test.ts +96 -0
  208. package/.agents/skills/gstack/test/helpers/session-runner.ts +357 -0
  209. package/.agents/skills/gstack/test/helpers/skill-parser.ts +206 -0
  210. package/.agents/skills/gstack/test/helpers/touchfiles.ts +260 -0
  211. package/.agents/skills/gstack/test/hook-scripts.test.ts +373 -0
  212. package/.agents/skills/gstack/test/skill-e2e-browse.test.ts +293 -0
  213. package/.agents/skills/gstack/test/skill-e2e-deploy.test.ts +279 -0
  214. package/.agents/skills/gstack/test/skill-e2e-design.test.ts +614 -0
  215. package/.agents/skills/gstack/test/skill-e2e-plan.test.ts +538 -0
  216. package/.agents/skills/gstack/test/skill-e2e-qa-bugs.test.ts +194 -0
  217. package/.agents/skills/gstack/test/skill-e2e-qa-workflow.test.ts +412 -0
  218. package/.agents/skills/gstack/test/skill-e2e-review.test.ts +535 -0
  219. package/.agents/skills/gstack/test/skill-e2e-workflow.test.ts +586 -0
  220. package/.agents/skills/gstack/test/skill-e2e.test.ts +3325 -0
  221. package/.agents/skills/gstack/test/skill-llm-eval.test.ts +787 -0
  222. package/.agents/skills/gstack/test/skill-parser.test.ts +179 -0
  223. package/.agents/skills/gstack/test/skill-routing-e2e.test.ts +605 -0
  224. package/.agents/skills/gstack/test/skill-validation.test.ts +1520 -0
  225. package/.agents/skills/gstack/test/telemetry.test.ts +278 -0
  226. package/.agents/skills/gstack/test/touchfiles.test.ts +262 -0
  227. package/.agents/skills/gstack/unfreeze/SKILL.md +40 -0
  228. package/.agents/skills/gstack/unfreeze/SKILL.md.tmpl +38 -0
  229. package/package.json +2 -1
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Tests for handoff/resume commands — headless-to-headed browser switching.
3
+ *
4
+ * Unit tests cover saveState/restoreState, failure tracking, and edge cases.
5
+ * Integration tests cover the full handoff flow with real Playwright browsers.
6
+ */
7
+
8
+ import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
9
+ import { startTestServer } from './test-server';
10
+ import { BrowserManager, type BrowserState } from '../src/browser-manager';
11
+ import { handleWriteCommand } from '../src/write-commands';
12
+ import { handleMetaCommand } from '../src/meta-commands';
13
+
14
+ let testServer: ReturnType<typeof startTestServer>;
15
+ let bm: BrowserManager;
16
+ let baseUrl: string;
17
+
18
+ beforeAll(async () => {
19
+ testServer = startTestServer(0);
20
+ baseUrl = testServer.url;
21
+
22
+ bm = new BrowserManager();
23
+ await bm.launch();
24
+ });
25
+
26
+ afterAll(() => {
27
+ try { testServer.server.stop(); } catch {}
28
+ setTimeout(() => process.exit(0), 500);
29
+ });
30
+
31
+ // ─── Unit Tests: Failure Tracking (no browser needed) ────────────
32
+
33
+ describe('failure tracking', () => {
34
+ test('getFailureHint returns null when below threshold', () => {
35
+ const tracker = new BrowserManager();
36
+ tracker.incrementFailures();
37
+ tracker.incrementFailures();
38
+ expect(tracker.getFailureHint()).toBeNull();
39
+ });
40
+
41
+ test('getFailureHint returns hint after 3 consecutive failures', () => {
42
+ const tracker = new BrowserManager();
43
+ tracker.incrementFailures();
44
+ tracker.incrementFailures();
45
+ tracker.incrementFailures();
46
+ const hint = tracker.getFailureHint();
47
+ expect(hint).not.toBeNull();
48
+ expect(hint).toContain('handoff');
49
+ expect(hint).toContain('3');
50
+ });
51
+
52
+ test('hint suppressed when already headed', () => {
53
+ const tracker = new BrowserManager();
54
+ (tracker as any).isHeaded = true;
55
+ tracker.incrementFailures();
56
+ tracker.incrementFailures();
57
+ tracker.incrementFailures();
58
+ expect(tracker.getFailureHint()).toBeNull();
59
+ });
60
+
61
+ test('resetFailures clears the counter', () => {
62
+ const tracker = new BrowserManager();
63
+ tracker.incrementFailures();
64
+ tracker.incrementFailures();
65
+ tracker.incrementFailures();
66
+ expect(tracker.getFailureHint()).not.toBeNull();
67
+ tracker.resetFailures();
68
+ expect(tracker.getFailureHint()).toBeNull();
69
+ });
70
+
71
+ test('getIsHeaded returns false by default', () => {
72
+ const tracker = new BrowserManager();
73
+ expect(tracker.getIsHeaded()).toBe(false);
74
+ });
75
+ });
76
+
77
+ // ─── Unit Tests: State Save/Restore (shared browser) ─────────────
78
+
79
+ describe('saveState', () => {
80
+ test('captures cookies and page URLs', async () => {
81
+ await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
82
+ await handleWriteCommand('cookie', ['testcookie=testvalue'], bm);
83
+
84
+ const state = await bm.saveState();
85
+
86
+ expect(state.cookies.length).toBeGreaterThan(0);
87
+ expect(state.cookies.some(c => c.name === 'testcookie')).toBe(true);
88
+ expect(state.pages.length).toBeGreaterThanOrEqual(1);
89
+ expect(state.pages.some(p => p.url.includes('/basic.html'))).toBe(true);
90
+ }, 15000);
91
+
92
+ test('captures localStorage and sessionStorage', async () => {
93
+ await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
94
+ const page = bm.getPage();
95
+ await page.evaluate(() => {
96
+ localStorage.setItem('lsKey', 'lsValue');
97
+ sessionStorage.setItem('ssKey', 'ssValue');
98
+ });
99
+
100
+ const state = await bm.saveState();
101
+ const activePage = state.pages.find(p => p.isActive);
102
+
103
+ expect(activePage).toBeDefined();
104
+ expect(activePage!.storage).not.toBeNull();
105
+ expect(activePage!.storage!.localStorage).toHaveProperty('lsKey', 'lsValue');
106
+ expect(activePage!.storage!.sessionStorage).toHaveProperty('ssKey', 'ssValue');
107
+ }, 15000);
108
+
109
+ test('captures multiple tabs', async () => {
110
+ while (bm.getTabCount() > 1) {
111
+ await bm.closeTab();
112
+ }
113
+ await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
114
+ await handleMetaCommand('newtab', [baseUrl + '/form.html'], bm, () => {});
115
+
116
+ const state = await bm.saveState();
117
+ expect(state.pages.length).toBe(2);
118
+ const activePage = state.pages.find(p => p.isActive);
119
+ expect(activePage).toBeDefined();
120
+ expect(activePage!.url).toContain('/form.html');
121
+
122
+ await bm.closeTab();
123
+ }, 15000);
124
+ });
125
+
126
+ describe('restoreState', () => {
127
+ test('state survives recreateContext round-trip', async () => {
128
+ await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
129
+ await handleWriteCommand('cookie', ['restored=yes'], bm);
130
+
131
+ const stateBefore = await bm.saveState();
132
+ expect(stateBefore.cookies.some(c => c.name === 'restored')).toBe(true);
133
+
134
+ await bm.recreateContext();
135
+
136
+ const stateAfter = await bm.saveState();
137
+ expect(stateAfter.cookies.some(c => c.name === 'restored')).toBe(true);
138
+ expect(stateAfter.pages.length).toBeGreaterThanOrEqual(1);
139
+ }, 30000);
140
+ });
141
+
142
+ // ─── Unit Tests: Handoff Edge Cases ──────────────────────────────
143
+
144
+ describe('handoff edge cases', () => {
145
+ test('handoff when already headed returns no-op', async () => {
146
+ (bm as any).isHeaded = true;
147
+ const result = await bm.handoff('test');
148
+ expect(result).toContain('Already in headed mode');
149
+ (bm as any).isHeaded = false;
150
+ }, 10000);
151
+
152
+ test('resume clears refs and resets failures', () => {
153
+ bm.incrementFailures();
154
+ bm.incrementFailures();
155
+ bm.incrementFailures();
156
+ bm.resume();
157
+ expect(bm.getFailureHint()).toBeNull();
158
+ expect(bm.getRefCount()).toBe(0);
159
+ });
160
+
161
+ test('resume without prior handoff works via meta command', async () => {
162
+ await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
163
+ const result = await handleMetaCommand('resume', [], bm, () => {});
164
+ expect(result).toContain('RESUMED');
165
+ }, 15000);
166
+ });
167
+
168
+ // ─── Integration Tests: Full Handoff Flow ────────────────────────
169
+ // Each handoff test creates its own BrowserManager since handoff swaps the browser.
170
+ // These tests run sequentially (one browser at a time) to avoid resource issues.
171
+
172
+ describe('handoff integration', () => {
173
+ test('full handoff: cookies preserved, headed mode active, commands work', async () => {
174
+ const hbm = new BrowserManager();
175
+ await hbm.launch();
176
+
177
+ try {
178
+ // Set up state
179
+ await handleWriteCommand('goto', [baseUrl + '/basic.html'], hbm);
180
+ await handleWriteCommand('cookie', ['handoff_test=preserved'], hbm);
181
+
182
+ // Handoff
183
+ const result = await hbm.handoff('Testing handoff');
184
+ expect(result).toContain('HANDOFF:');
185
+ expect(result).toContain('Testing handoff');
186
+ expect(result).toContain('resume');
187
+ expect(hbm.getIsHeaded()).toBe(true);
188
+
189
+ // Verify cookies survived
190
+ const { handleReadCommand } = await import('../src/read-commands');
191
+ const cookiesResult = await handleReadCommand('cookies', [], hbm);
192
+ expect(cookiesResult).toContain('handoff_test');
193
+
194
+ // Verify commands still work
195
+ const text = await handleReadCommand('text', [], hbm);
196
+ expect(text.length).toBeGreaterThan(0);
197
+
198
+ // Resume
199
+ const resumeResult = await handleMetaCommand('resume', [], hbm, () => {});
200
+ expect(resumeResult).toContain('RESUMED');
201
+ } finally {
202
+ await hbm.close();
203
+ }
204
+ }, 45000);
205
+
206
+ test('multi-tab handoff preserves all tabs', async () => {
207
+ const hbm = new BrowserManager();
208
+ await hbm.launch();
209
+
210
+ try {
211
+ await handleWriteCommand('goto', [baseUrl + '/basic.html'], hbm);
212
+ await handleMetaCommand('newtab', [baseUrl + '/form.html'], hbm, () => {});
213
+ expect(hbm.getTabCount()).toBe(2);
214
+
215
+ await hbm.handoff('multi-tab test');
216
+ expect(hbm.getTabCount()).toBe(2);
217
+ expect(hbm.getIsHeaded()).toBe(true);
218
+ } finally {
219
+ await hbm.close();
220
+ }
221
+ }, 45000);
222
+
223
+ test('handoff meta command joins args as message', async () => {
224
+ const hbm = new BrowserManager();
225
+ await hbm.launch();
226
+
227
+ try {
228
+ await handleWriteCommand('goto', [baseUrl + '/basic.html'], hbm);
229
+ const result = await handleMetaCommand('handoff', ['CAPTCHA', 'stuck'], hbm, () => {});
230
+ expect(result).toContain('CAPTCHA stuck');
231
+ } finally {
232
+ await hbm.close();
233
+ }
234
+ }, 45000);
235
+ });
@@ -0,0 +1,63 @@
1
+ import { describe, it, expect } from 'bun:test';
2
+ import { validateOutputPath } from '../src/meta-commands';
3
+ import { validateReadPath } from '../src/read-commands';
4
+
5
+ describe('validateOutputPath', () => {
6
+ it('allows paths within /tmp', () => {
7
+ expect(() => validateOutputPath('/tmp/screenshot.png')).not.toThrow();
8
+ });
9
+
10
+ it('allows paths in subdirectories of /tmp', () => {
11
+ expect(() => validateOutputPath('/tmp/browse/output.png')).not.toThrow();
12
+ });
13
+
14
+ it('allows paths within cwd', () => {
15
+ expect(() => validateOutputPath(`${process.cwd()}/output.png`)).not.toThrow();
16
+ });
17
+
18
+ it('blocks paths outside safe directories', () => {
19
+ expect(() => validateOutputPath('/etc/cron.d/backdoor.png')).toThrow(/Path must be within/);
20
+ });
21
+
22
+ it('blocks /tmpevil prefix collision', () => {
23
+ expect(() => validateOutputPath('/tmpevil/file.png')).toThrow(/Path must be within/);
24
+ });
25
+
26
+ it('blocks home directory paths', () => {
27
+ expect(() => validateOutputPath('/Users/someone/file.png')).toThrow(/Path must be within/);
28
+ });
29
+
30
+ it('blocks path traversal via ..', () => {
31
+ expect(() => validateOutputPath('/tmp/../etc/passwd')).toThrow(/Path must be within/);
32
+ });
33
+ });
34
+
35
+ describe('validateReadPath', () => {
36
+ it('allows absolute paths within /tmp', () => {
37
+ expect(() => validateReadPath('/tmp/script.js')).not.toThrow();
38
+ });
39
+
40
+ it('allows absolute paths within cwd', () => {
41
+ expect(() => validateReadPath(`${process.cwd()}/test.js`)).not.toThrow();
42
+ });
43
+
44
+ it('allows relative paths without traversal', () => {
45
+ expect(() => validateReadPath('src/index.js')).not.toThrow();
46
+ });
47
+
48
+ it('blocks absolute paths outside safe directories', () => {
49
+ expect(() => validateReadPath('/etc/passwd')).toThrow(/Absolute path must be within/);
50
+ });
51
+
52
+ it('blocks /tmpevil prefix collision', () => {
53
+ expect(() => validateReadPath('/tmpevil/file.js')).toThrow(/Absolute path must be within/);
54
+ });
55
+
56
+ it('blocks path traversal sequences', () => {
57
+ expect(() => validateReadPath('../../../etc/passwd')).toThrow(/Path traversal/);
58
+ });
59
+
60
+ it('blocks nested path traversal', () => {
61
+ expect(() => validateReadPath('src/../../etc/passwd')).toThrow(/Path traversal/);
62
+ });
63
+ });
@@ -0,0 +1,37 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import { TEMP_DIR, isPathWithin, IS_WINDOWS } from '../src/platform';
3
+
4
+ describe('platform constants', () => {
5
+ test('TEMP_DIR is /tmp on non-Windows', () => {
6
+ if (!IS_WINDOWS) {
7
+ expect(TEMP_DIR).toBe('/tmp');
8
+ }
9
+ });
10
+
11
+ test('IS_WINDOWS reflects process.platform', () => {
12
+ expect(IS_WINDOWS).toBe(process.platform === 'win32');
13
+ });
14
+ });
15
+
16
+ describe('isPathWithin', () => {
17
+ test('path inside directory returns true', () => {
18
+ expect(isPathWithin('/tmp/foo', '/tmp')).toBe(true);
19
+ });
20
+
21
+ test('path outside directory returns false', () => {
22
+ expect(isPathWithin('/etc/foo', '/tmp')).toBe(false);
23
+ });
24
+
25
+ test('exact match returns true', () => {
26
+ expect(isPathWithin('/tmp', '/tmp')).toBe(true);
27
+ });
28
+
29
+ test('partial prefix does not match (path traversal)', () => {
30
+ // /tmp-evil should NOT match /tmp
31
+ expect(isPathWithin('/tmp-evil/foo', '/tmp')).toBe(false);
32
+ });
33
+
34
+ test('nested path returns true', () => {
35
+ expect(isPathWithin('/tmp/a/b/c', '/tmp')).toBe(true);
36
+ });
37
+ });