@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,369 @@
1
+ /**
2
+ * gstack browse server — persistent Chromium daemon
3
+ *
4
+ * Architecture:
5
+ * Bun.serve HTTP on localhost → routes commands to Playwright
6
+ * Console/network/dialog buffers: CircularBuffer in-memory + async disk flush
7
+ * Chromium crash → server EXITS with clear error (CLI auto-restarts)
8
+ * Auto-shutdown after BROWSE_IDLE_TIMEOUT (default 30 min)
9
+ *
10
+ * State:
11
+ * State file: <project-root>/.gstack/browse.json (set via BROWSE_STATE_FILE env)
12
+ * Log files: <project-root>/.gstack/browse-{console,network,dialog}.log
13
+ * Port: random 10000-60000 (or BROWSE_PORT env for debug override)
14
+ */
15
+
16
+ import { BrowserManager } from './browser-manager';
17
+ import { handleReadCommand } from './read-commands';
18
+ import { handleWriteCommand } from './write-commands';
19
+ import { handleMetaCommand } from './meta-commands';
20
+ import { handleCookiePickerRoute } from './cookie-picker-routes';
21
+ import { COMMAND_DESCRIPTIONS } from './commands';
22
+ import { SNAPSHOT_FLAGS } from './snapshot';
23
+ import { resolveConfig, ensureStateDir, readVersionHash } from './config';
24
+ import * as fs from 'fs';
25
+ import * as path from 'path';
26
+ import * as crypto from 'crypto';
27
+
28
+ // ─── Config ─────────────────────────────────────────────────────
29
+ const config = resolveConfig();
30
+ ensureStateDir(config);
31
+
32
+ // ─── Auth ───────────────────────────────────────────────────────
33
+ const AUTH_TOKEN = crypto.randomUUID();
34
+ const BROWSE_PORT = parseInt(process.env.BROWSE_PORT || '0', 10);
35
+ const IDLE_TIMEOUT_MS = parseInt(process.env.BROWSE_IDLE_TIMEOUT || '1800000', 10); // 30 min
36
+
37
+ function validateAuth(req: Request): boolean {
38
+ const header = req.headers.get('authorization');
39
+ return header === `Bearer ${AUTH_TOKEN}`;
40
+ }
41
+
42
+ // ─── Help text (auto-generated from COMMAND_DESCRIPTIONS) ────────
43
+ function generateHelpText(): string {
44
+ // Group commands by category
45
+ const groups = new Map<string, string[]>();
46
+ for (const [cmd, meta] of Object.entries(COMMAND_DESCRIPTIONS)) {
47
+ const display = meta.usage || cmd;
48
+ const list = groups.get(meta.category) || [];
49
+ list.push(display);
50
+ groups.set(meta.category, list);
51
+ }
52
+
53
+ const categoryOrder = [
54
+ 'Navigation', 'Reading', 'Interaction', 'Inspection',
55
+ 'Visual', 'Snapshot', 'Meta', 'Tabs', 'Server',
56
+ ];
57
+
58
+ const lines = ['gstack browse — headless browser for AI agents', '', 'Commands:'];
59
+ for (const cat of categoryOrder) {
60
+ const cmds = groups.get(cat);
61
+ if (!cmds) continue;
62
+ lines.push(` ${(cat + ':').padEnd(15)}${cmds.join(', ')}`);
63
+ }
64
+
65
+ // Snapshot flags from source of truth
66
+ lines.push('');
67
+ lines.push('Snapshot flags:');
68
+ const flagPairs: string[] = [];
69
+ for (const flag of SNAPSHOT_FLAGS) {
70
+ const label = flag.valueHint ? `${flag.short} ${flag.valueHint}` : flag.short;
71
+ flagPairs.push(`${label} ${flag.long}`);
72
+ }
73
+ // Print two flags per line for compact display
74
+ for (let i = 0; i < flagPairs.length; i += 2) {
75
+ const left = flagPairs[i].padEnd(28);
76
+ const right = flagPairs[i + 1] || '';
77
+ lines.push(` ${left}${right}`);
78
+ }
79
+
80
+ return lines.join('\n');
81
+ }
82
+
83
+ // ─── Buffer (from buffers.ts) ────────────────────────────────────
84
+ import { consoleBuffer, networkBuffer, dialogBuffer, addConsoleEntry, addNetworkEntry, addDialogEntry, type LogEntry, type NetworkEntry, type DialogEntry } from './buffers';
85
+ export { consoleBuffer, networkBuffer, dialogBuffer, addConsoleEntry, addNetworkEntry, addDialogEntry, type LogEntry, type NetworkEntry, type DialogEntry };
86
+
87
+ const CONSOLE_LOG_PATH = config.consoleLog;
88
+ const NETWORK_LOG_PATH = config.networkLog;
89
+ const DIALOG_LOG_PATH = config.dialogLog;
90
+ let lastConsoleFlushed = 0;
91
+ let lastNetworkFlushed = 0;
92
+ let lastDialogFlushed = 0;
93
+ let flushInProgress = false;
94
+
95
+ async function flushBuffers() {
96
+ if (flushInProgress) return; // Guard against concurrent flush
97
+ flushInProgress = true;
98
+
99
+ try {
100
+ // Console buffer
101
+ const newConsoleCount = consoleBuffer.totalAdded - lastConsoleFlushed;
102
+ if (newConsoleCount > 0) {
103
+ const entries = consoleBuffer.last(Math.min(newConsoleCount, consoleBuffer.length));
104
+ const lines = entries.map(e =>
105
+ `[${new Date(e.timestamp).toISOString()}] [${e.level}] ${e.text}`
106
+ ).join('\n') + '\n';
107
+ fs.appendFileSync(CONSOLE_LOG_PATH, lines);
108
+ lastConsoleFlushed = consoleBuffer.totalAdded;
109
+ }
110
+
111
+ // Network buffer
112
+ const newNetworkCount = networkBuffer.totalAdded - lastNetworkFlushed;
113
+ if (newNetworkCount > 0) {
114
+ const entries = networkBuffer.last(Math.min(newNetworkCount, networkBuffer.length));
115
+ const lines = entries.map(e =>
116
+ `[${new Date(e.timestamp).toISOString()}] ${e.method} ${e.url} → ${e.status || 'pending'} (${e.duration || '?'}ms, ${e.size || '?'}B)`
117
+ ).join('\n') + '\n';
118
+ fs.appendFileSync(NETWORK_LOG_PATH, lines);
119
+ lastNetworkFlushed = networkBuffer.totalAdded;
120
+ }
121
+
122
+ // Dialog buffer
123
+ const newDialogCount = dialogBuffer.totalAdded - lastDialogFlushed;
124
+ if (newDialogCount > 0) {
125
+ const entries = dialogBuffer.last(Math.min(newDialogCount, dialogBuffer.length));
126
+ const lines = entries.map(e =>
127
+ `[${new Date(e.timestamp).toISOString()}] [${e.type}] "${e.message}" → ${e.action}${e.response ? ` "${e.response}"` : ''}`
128
+ ).join('\n') + '\n';
129
+ fs.appendFileSync(DIALOG_LOG_PATH, lines);
130
+ lastDialogFlushed = dialogBuffer.totalAdded;
131
+ }
132
+ } catch {
133
+ // Flush failures are non-fatal — buffers are in memory
134
+ } finally {
135
+ flushInProgress = false;
136
+ }
137
+ }
138
+
139
+ // Flush every 1 second
140
+ const flushInterval = setInterval(flushBuffers, 1000);
141
+
142
+ // ─── Idle Timer ────────────────────────────────────────────────
143
+ let lastActivity = Date.now();
144
+
145
+ function resetIdleTimer() {
146
+ lastActivity = Date.now();
147
+ }
148
+
149
+ const idleCheckInterval = setInterval(() => {
150
+ if (Date.now() - lastActivity > IDLE_TIMEOUT_MS) {
151
+ console.log(`[browse] Idle for ${IDLE_TIMEOUT_MS / 1000}s, shutting down`);
152
+ shutdown();
153
+ }
154
+ }, 60_000);
155
+
156
+ // ─── Command Sets (from commands.ts — single source of truth) ───
157
+ import { READ_COMMANDS, WRITE_COMMANDS, META_COMMANDS } from './commands';
158
+ export { READ_COMMANDS, WRITE_COMMANDS, META_COMMANDS };
159
+
160
+ // ─── Server ────────────────────────────────────────────────────
161
+ const browserManager = new BrowserManager();
162
+ let isShuttingDown = false;
163
+
164
+ // Find port: explicit BROWSE_PORT, or random in 10000-60000
165
+ async function findPort(): Promise<number> {
166
+ // Explicit port override (for debugging)
167
+ if (BROWSE_PORT) {
168
+ try {
169
+ const testServer = Bun.serve({ port: BROWSE_PORT, fetch: () => new Response('ok') });
170
+ testServer.stop();
171
+ return BROWSE_PORT;
172
+ } catch {
173
+ throw new Error(`[browse] Port ${BROWSE_PORT} (from BROWSE_PORT env) is in use`);
174
+ }
175
+ }
176
+
177
+ // Random port with retry
178
+ const MIN_PORT = 10000;
179
+ const MAX_PORT = 60000;
180
+ const MAX_RETRIES = 5;
181
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
182
+ const port = MIN_PORT + Math.floor(Math.random() * (MAX_PORT - MIN_PORT));
183
+ try {
184
+ const testServer = Bun.serve({ port, fetch: () => new Response('ok') });
185
+ testServer.stop();
186
+ return port;
187
+ } catch {
188
+ continue;
189
+ }
190
+ }
191
+ throw new Error(`[browse] No available port after ${MAX_RETRIES} attempts in range ${MIN_PORT}-${MAX_PORT}`);
192
+ }
193
+
194
+ /**
195
+ * Translate Playwright errors into actionable messages for AI agents.
196
+ */
197
+ function wrapError(err: any): string {
198
+ const msg = err.message || String(err);
199
+ // Timeout errors
200
+ if (err.name === 'TimeoutError' || msg.includes('Timeout') || msg.includes('timeout')) {
201
+ if (msg.includes('locator.click') || msg.includes('locator.fill') || msg.includes('locator.hover')) {
202
+ return `Element not found or not interactable within timeout. Check your selector or run 'snapshot' for fresh refs.`;
203
+ }
204
+ if (msg.includes('page.goto') || msg.includes('Navigation')) {
205
+ return `Page navigation timed out. The URL may be unreachable or the page may be loading slowly.`;
206
+ }
207
+ return `Operation timed out: ${msg.split('\n')[0]}`;
208
+ }
209
+ // Multiple elements matched
210
+ if (msg.includes('resolved to') && msg.includes('elements')) {
211
+ return `Selector matched multiple elements. Be more specific or use @refs from 'snapshot'.`;
212
+ }
213
+ // Pass through other errors
214
+ return msg;
215
+ }
216
+
217
+ async function handleCommand(body: any): Promise<Response> {
218
+ const { command, args = [] } = body;
219
+
220
+ if (!command) {
221
+ return new Response(JSON.stringify({ error: 'Missing "command" field' }), {
222
+ status: 400,
223
+ headers: { 'Content-Type': 'application/json' },
224
+ });
225
+ }
226
+
227
+ try {
228
+ let result: string;
229
+
230
+ if (READ_COMMANDS.has(command)) {
231
+ result = await handleReadCommand(command, args, browserManager);
232
+ } else if (WRITE_COMMANDS.has(command)) {
233
+ result = await handleWriteCommand(command, args, browserManager);
234
+ } else if (META_COMMANDS.has(command)) {
235
+ result = await handleMetaCommand(command, args, browserManager, shutdown);
236
+ } else if (command === 'help') {
237
+ const helpText = generateHelpText();
238
+ return new Response(helpText, {
239
+ status: 200,
240
+ headers: { 'Content-Type': 'text/plain' },
241
+ });
242
+ } else {
243
+ return new Response(JSON.stringify({
244
+ error: `Unknown command: ${command}`,
245
+ hint: `Available commands: ${[...READ_COMMANDS, ...WRITE_COMMANDS, ...META_COMMANDS].sort().join(', ')}`,
246
+ }), {
247
+ status: 400,
248
+ headers: { 'Content-Type': 'application/json' },
249
+ });
250
+ }
251
+
252
+ browserManager.resetFailures();
253
+ return new Response(result, {
254
+ status: 200,
255
+ headers: { 'Content-Type': 'text/plain' },
256
+ });
257
+ } catch (err: any) {
258
+ browserManager.incrementFailures();
259
+ let errorMsg = wrapError(err);
260
+ const hint = browserManager.getFailureHint();
261
+ if (hint) errorMsg += '\n' + hint;
262
+ return new Response(JSON.stringify({ error: errorMsg }), {
263
+ status: 500,
264
+ headers: { 'Content-Type': 'application/json' },
265
+ });
266
+ }
267
+ }
268
+
269
+ async function shutdown() {
270
+ if (isShuttingDown) return;
271
+ isShuttingDown = true;
272
+
273
+ console.log('[browse] Shutting down...');
274
+ clearInterval(flushInterval);
275
+ clearInterval(idleCheckInterval);
276
+ await flushBuffers(); // Final flush (async now)
277
+
278
+ await browserManager.close();
279
+
280
+ // Clean up state file
281
+ try { fs.unlinkSync(config.stateFile); } catch {}
282
+
283
+ process.exit(0);
284
+ }
285
+
286
+ // Handle signals
287
+ process.on('SIGTERM', shutdown);
288
+ process.on('SIGINT', shutdown);
289
+
290
+ // ─── Start ─────────────────────────────────────────────────────
291
+ async function start() {
292
+ // Clear old log files
293
+ try { fs.unlinkSync(CONSOLE_LOG_PATH); } catch {}
294
+ try { fs.unlinkSync(NETWORK_LOG_PATH); } catch {}
295
+ try { fs.unlinkSync(DIALOG_LOG_PATH); } catch {}
296
+
297
+ const port = await findPort();
298
+
299
+ // Launch browser
300
+ await browserManager.launch();
301
+
302
+ const startTime = Date.now();
303
+ const server = Bun.serve({
304
+ port,
305
+ hostname: '127.0.0.1',
306
+ fetch: async (req) => {
307
+ resetIdleTimer();
308
+
309
+ const url = new URL(req.url);
310
+
311
+ // Cookie picker routes — no auth required (localhost-only)
312
+ if (url.pathname.startsWith('/cookie-picker')) {
313
+ return handleCookiePickerRoute(url, req, browserManager);
314
+ }
315
+
316
+ // Health check — no auth required (now async)
317
+ if (url.pathname === '/health') {
318
+ const healthy = await browserManager.isHealthy();
319
+ return new Response(JSON.stringify({
320
+ status: healthy ? 'healthy' : 'unhealthy',
321
+ uptime: Math.floor((Date.now() - startTime) / 1000),
322
+ tabs: browserManager.getTabCount(),
323
+ currentUrl: browserManager.getCurrentUrl(),
324
+ }), {
325
+ status: 200,
326
+ headers: { 'Content-Type': 'application/json' },
327
+ });
328
+ }
329
+
330
+ // All other endpoints require auth
331
+ if (!validateAuth(req)) {
332
+ return new Response(JSON.stringify({ error: 'Unauthorized' }), {
333
+ status: 401,
334
+ headers: { 'Content-Type': 'application/json' },
335
+ });
336
+ }
337
+
338
+ if (url.pathname === '/command' && req.method === 'POST') {
339
+ const body = await req.json();
340
+ return handleCommand(body);
341
+ }
342
+
343
+ return new Response('Not found', { status: 404 });
344
+ },
345
+ });
346
+
347
+ // Write state file (atomic: write .tmp then rename)
348
+ const state = {
349
+ pid: process.pid,
350
+ port,
351
+ token: AUTH_TOKEN,
352
+ startedAt: new Date().toISOString(),
353
+ serverPath: path.resolve(import.meta.dir, 'server.ts'),
354
+ binaryVersion: readVersionHash() || undefined,
355
+ };
356
+ const tmpFile = config.stateFile + '.tmp';
357
+ fs.writeFileSync(tmpFile, JSON.stringify(state, null, 2), { mode: 0o600 });
358
+ fs.renameSync(tmpFile, config.stateFile);
359
+
360
+ browserManager.serverPort = port;
361
+ console.log(`[browse] Server running on http://127.0.0.1:${port} (PID: ${process.pid})`);
362
+ console.log(`[browse] State file: ${config.stateFile}`);
363
+ console.log(`[browse] Idle timeout: ${IDLE_TIMEOUT_MS / 1000}s`);
364
+ }
365
+
366
+ start().catch((err) => {
367
+ console.error(`[browse] Failed to start: ${err.message}`);
368
+ process.exit(1);
369
+ });