@runchr/gstack-antigravity 0.1.0 → 0.1.2

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 (231) 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/README.md +12 -7
  230. package/README_KO.md +12 -6
  231. package/package.json +3 -2
@@ -0,0 +1,541 @@
1
+ /**
2
+ * Cookie picker UI — self-contained HTML page
3
+ *
4
+ * Dark theme, two-panel layout, vanilla HTML/CSS/JS.
5
+ * Left: source browser domains with search + import buttons.
6
+ * Right: imported domains with trash buttons.
7
+ * No cookie values exposed anywhere.
8
+ */
9
+
10
+ export function getCookiePickerHTML(serverPort: number): string {
11
+ const baseUrl = `http://127.0.0.1:${serverPort}`;
12
+
13
+ return `<!DOCTYPE html>
14
+ <html lang="en">
15
+ <head>
16
+ <meta charset="utf-8">
17
+ <meta name="viewport" content="width=device-width, initial-scale=1">
18
+ <title>Cookie Import — gstack browse</title>
19
+ <style>
20
+ * { margin: 0; padding: 0; box-sizing: border-box; }
21
+ body {
22
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
23
+ background: #0a0a0a;
24
+ color: #e0e0e0;
25
+ height: 100vh;
26
+ overflow: hidden;
27
+ }
28
+
29
+ /* ─── Header ──────────────────────────── */
30
+ .header {
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: space-between;
34
+ padding: 16px 24px;
35
+ border-bottom: 1px solid #222;
36
+ background: #0f0f0f;
37
+ }
38
+ .header h1 {
39
+ font-size: 16px;
40
+ font-weight: 600;
41
+ color: #fff;
42
+ }
43
+ .header .port {
44
+ font-size: 12px;
45
+ color: #666;
46
+ font-family: 'SF Mono', 'Fira Code', monospace;
47
+ }
48
+
49
+ /* ─── Layout ──────────────────────────── */
50
+ .container {
51
+ display: flex;
52
+ height: calc(100vh - 53px);
53
+ }
54
+ .panel {
55
+ flex: 1;
56
+ display: flex;
57
+ flex-direction: column;
58
+ overflow: hidden;
59
+ }
60
+ .panel-left {
61
+ border-right: 1px solid #222;
62
+ }
63
+ .panel-header {
64
+ padding: 16px 20px 12px;
65
+ font-size: 11px;
66
+ font-weight: 600;
67
+ text-transform: uppercase;
68
+ letter-spacing: 0.5px;
69
+ color: #888;
70
+ }
71
+
72
+ /* ─── Browser Pills ───────────────────── */
73
+ .browser-pills {
74
+ display: flex;
75
+ gap: 8px;
76
+ padding: 0 20px 12px;
77
+ flex-wrap: wrap;
78
+ }
79
+ .pill {
80
+ padding: 6px 14px;
81
+ border-radius: 20px;
82
+ border: 1px solid #333;
83
+ background: #1a1a1a;
84
+ color: #aaa;
85
+ font-size: 13px;
86
+ cursor: pointer;
87
+ transition: all 0.15s;
88
+ display: flex;
89
+ align-items: center;
90
+ gap: 6px;
91
+ }
92
+ .pill:hover { border-color: #555; color: #ddd; }
93
+ .pill.active {
94
+ border-color: #4ade80;
95
+ background: #0a2a14;
96
+ color: #4ade80;
97
+ }
98
+ .pill .dot {
99
+ width: 6px; height: 6px;
100
+ border-radius: 50%;
101
+ background: #4ade80;
102
+ }
103
+
104
+ /* ─── Search ──────────────────────────── */
105
+ .search-wrap {
106
+ padding: 0 20px 12px;
107
+ }
108
+ .search-input {
109
+ width: 100%;
110
+ padding: 8px 12px;
111
+ border-radius: 8px;
112
+ border: 1px solid #333;
113
+ background: #141414;
114
+ color: #e0e0e0;
115
+ font-size: 13px;
116
+ outline: none;
117
+ transition: border-color 0.15s;
118
+ }
119
+ .search-input::placeholder { color: #555; }
120
+ .search-input:focus { border-color: #555; }
121
+
122
+ /* ─── Domain List ─────────────────────── */
123
+ .domain-list {
124
+ flex: 1;
125
+ overflow-y: auto;
126
+ padding: 0 12px;
127
+ }
128
+ .domain-list::-webkit-scrollbar { width: 6px; }
129
+ .domain-list::-webkit-scrollbar-track { background: transparent; }
130
+ .domain-list::-webkit-scrollbar-thumb { background: #333; border-radius: 3px; }
131
+
132
+ .domain-row {
133
+ display: flex;
134
+ align-items: center;
135
+ padding: 8px 10px;
136
+ border-radius: 6px;
137
+ transition: background 0.1s;
138
+ gap: 8px;
139
+ }
140
+ .domain-row:hover { background: #1a1a1a; }
141
+ .domain-name {
142
+ flex: 1;
143
+ font-family: 'SF Mono', 'Fira Code', monospace;
144
+ font-size: 13px;
145
+ color: #ccc;
146
+ overflow: hidden;
147
+ text-overflow: ellipsis;
148
+ white-space: nowrap;
149
+ }
150
+ .domain-count {
151
+ font-size: 12px;
152
+ color: #666;
153
+ font-family: 'SF Mono', 'Fira Code', monospace;
154
+ min-width: 28px;
155
+ text-align: right;
156
+ }
157
+ .btn-add, .btn-trash {
158
+ width: 28px; height: 28px;
159
+ border-radius: 6px;
160
+ border: 1px solid #333;
161
+ background: #1a1a1a;
162
+ color: #888;
163
+ font-size: 16px;
164
+ cursor: pointer;
165
+ display: flex;
166
+ align-items: center;
167
+ justify-content: center;
168
+ transition: all 0.15s;
169
+ flex-shrink: 0;
170
+ }
171
+ .btn-add:hover { border-color: #4ade80; color: #4ade80; background: #0a2a14; }
172
+ .btn-trash:hover { border-color: #f87171; color: #f87171; background: #2a0a0a; }
173
+ .btn-add:disabled, .btn-trash:disabled {
174
+ opacity: 0.3;
175
+ cursor: not-allowed;
176
+ pointer-events: none;
177
+ }
178
+ .btn-add.imported {
179
+ border-color: #333;
180
+ color: #4ade80;
181
+ background: transparent;
182
+ cursor: default;
183
+ font-size: 14px;
184
+ }
185
+
186
+ /* ─── Footer ──────────────────────────── */
187
+ .panel-footer {
188
+ padding: 12px 20px;
189
+ border-top: 1px solid #222;
190
+ font-size: 12px;
191
+ color: #666;
192
+ }
193
+
194
+ /* ─── Imported Panel ──────────────────── */
195
+ .imported-empty {
196
+ flex: 1;
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: center;
200
+ color: #444;
201
+ font-size: 13px;
202
+ padding: 20px;
203
+ text-align: center;
204
+ }
205
+
206
+ /* ─── Banner ──────────────────────────── */
207
+ .banner {
208
+ padding: 10px 20px;
209
+ font-size: 13px;
210
+ display: none;
211
+ align-items: center;
212
+ gap: 10px;
213
+ }
214
+ .banner.error {
215
+ background: #1a0a0a;
216
+ border-bottom: 1px solid #3a1111;
217
+ color: #f87171;
218
+ }
219
+ .banner.info {
220
+ background: #0a1a2a;
221
+ border-bottom: 1px solid #112233;
222
+ color: #60a5fa;
223
+ }
224
+ .banner .banner-text { flex: 1; }
225
+ .banner .banner-close, .banner .banner-retry {
226
+ background: none;
227
+ border: 1px solid currentColor;
228
+ color: inherit;
229
+ padding: 3px 10px;
230
+ border-radius: 4px;
231
+ cursor: pointer;
232
+ font-size: 12px;
233
+ }
234
+
235
+ /* ─── Spinner ─────────────────────────── */
236
+ .spinner {
237
+ display: inline-block;
238
+ width: 14px; height: 14px;
239
+ border: 2px solid #333;
240
+ border-top-color: #4ade80;
241
+ border-radius: 50%;
242
+ animation: spin 0.6s linear infinite;
243
+ }
244
+ @keyframes spin { to { transform: rotate(360deg); } }
245
+
246
+ .loading-row {
247
+ display: flex;
248
+ align-items: center;
249
+ justify-content: center;
250
+ padding: 40px;
251
+ gap: 10px;
252
+ color: #666;
253
+ font-size: 13px;
254
+ }
255
+ </style>
256
+ </head>
257
+ <body>
258
+
259
+ <div class="header">
260
+ <h1>Cookie Import</h1>
261
+ <span class="port">localhost:${serverPort}</span>
262
+ </div>
263
+
264
+ <div id="banner" class="banner"></div>
265
+
266
+ <div class="container">
267
+ <!-- Left Panel: Source Browser -->
268
+ <div class="panel panel-left">
269
+ <div class="panel-header">Source Browser</div>
270
+ <div id="browser-pills" class="browser-pills"></div>
271
+ <div class="search-wrap">
272
+ <input type="text" class="search-input" id="search" placeholder="Search domains..." />
273
+ </div>
274
+ <div class="domain-list" id="source-domains">
275
+ <div class="loading-row"><span class="spinner"></span> Detecting browsers...</div>
276
+ </div>
277
+ <div class="panel-footer" id="source-footer"></div>
278
+ </div>
279
+
280
+ <!-- Right Panel: Imported -->
281
+ <div class="panel panel-right">
282
+ <div class="panel-header">Imported to Session</div>
283
+ <div class="domain-list" id="imported-domains">
284
+ <div class="imported-empty">No cookies imported yet</div>
285
+ </div>
286
+ <div class="panel-footer" id="imported-footer"></div>
287
+ </div>
288
+ </div>
289
+
290
+ <script>
291
+ (function() {
292
+ const BASE = '${baseUrl}';
293
+ let activeBrowser = null;
294
+ let allDomains = [];
295
+ let importedSet = {}; // domain → count
296
+ let inflight = {}; // domain → true (prevents double-click)
297
+
298
+ const $pills = document.getElementById('browser-pills');
299
+ const $search = document.getElementById('search');
300
+ const $sourceDomains = document.getElementById('source-domains');
301
+ const $importedDomains = document.getElementById('imported-domains');
302
+ const $sourceFooter = document.getElementById('source-footer');
303
+ const $importedFooter = document.getElementById('imported-footer');
304
+ const $banner = document.getElementById('banner');
305
+
306
+ // ─── Banner ────────────────────────────
307
+ function showBanner(msg, type, retryFn) {
308
+ $banner.className = 'banner ' + type;
309
+ $banner.style.display = 'flex';
310
+ let html = '<span class="banner-text">' + escHtml(msg) + '</span>';
311
+ if (retryFn) {
312
+ html += '<button class="banner-retry" id="banner-retry">Retry</button>';
313
+ }
314
+ html += '<button class="banner-close" id="banner-close">×</button>';
315
+ $banner.innerHTML = html;
316
+ document.getElementById('banner-close').onclick = () => { $banner.style.display = 'none'; };
317
+ if (retryFn) {
318
+ document.getElementById('banner-retry').onclick = () => {
319
+ $banner.style.display = 'none';
320
+ retryFn();
321
+ };
322
+ }
323
+ }
324
+
325
+ function escHtml(s) {
326
+ return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
327
+ }
328
+
329
+ // ─── API ────────────────────────────────
330
+ async function api(path, opts) {
331
+ const res = await fetch(BASE + '/cookie-picker' + path, opts);
332
+ const data = await res.json();
333
+ if (!res.ok) {
334
+ const err = new Error(data.error || 'Request failed');
335
+ err.code = data.code;
336
+ err.action = data.action;
337
+ throw err;
338
+ }
339
+ return data;
340
+ }
341
+
342
+ // ─── Init ───────────────────────────────
343
+ async function init() {
344
+ try {
345
+ const [browserData, importedData] = await Promise.all([
346
+ api('/browsers'),
347
+ api('/imported'),
348
+ ]);
349
+
350
+ // Populate imported state
351
+ for (const entry of importedData.domains) {
352
+ importedSet[entry.domain] = entry.count;
353
+ }
354
+ renderImported();
355
+
356
+ // Render browser pills
357
+ const browsers = browserData.browsers;
358
+ if (browsers.length === 0) {
359
+ $sourceDomains.innerHTML = '<div class="imported-empty">No Chromium browsers detected</div>';
360
+ return;
361
+ }
362
+
363
+ $pills.innerHTML = '';
364
+ browsers.forEach(b => {
365
+ const pill = document.createElement('button');
366
+ pill.className = 'pill';
367
+ pill.innerHTML = '<span class="dot"></span>' + escHtml(b.name);
368
+ pill.onclick = () => selectBrowser(b.name);
369
+ $pills.appendChild(pill);
370
+ });
371
+
372
+ // Auto-select first browser
373
+ selectBrowser(browsers[0].name);
374
+ } catch (err) {
375
+ showBanner(err.message, 'error', init);
376
+ $sourceDomains.innerHTML = '<div class="imported-empty">Failed to load</div>';
377
+ }
378
+ }
379
+
380
+ // ─── Select Browser ────────────────────
381
+ async function selectBrowser(name) {
382
+ activeBrowser = name;
383
+
384
+ // Update pills
385
+ $pills.querySelectorAll('.pill').forEach(p => {
386
+ p.classList.toggle('active', p.textContent === name);
387
+ });
388
+
389
+ $sourceDomains.innerHTML = '<div class="loading-row"><span class="spinner"></span> Loading domains...</div>';
390
+ $sourceFooter.textContent = '';
391
+ $search.value = '';
392
+
393
+ try {
394
+ const data = await api('/domains?browser=' + encodeURIComponent(name));
395
+ allDomains = data.domains;
396
+ renderSourceDomains();
397
+ } catch (err) {
398
+ showBanner(err.message, 'error', err.action === 'retry' ? () => selectBrowser(name) : null);
399
+ $sourceDomains.innerHTML = '<div class="imported-empty">Failed to load domains</div>';
400
+ }
401
+ }
402
+
403
+ // ─── Render Source Domains ─────────────
404
+ function renderSourceDomains() {
405
+ const query = $search.value.toLowerCase();
406
+ const filtered = query
407
+ ? allDomains.filter(d => d.domain.toLowerCase().includes(query))
408
+ : allDomains;
409
+
410
+ if (filtered.length === 0) {
411
+ $sourceDomains.innerHTML = '<div class="imported-empty">' +
412
+ (query ? 'No matching domains' : 'No cookie domains found') + '</div>';
413
+ $sourceFooter.textContent = '';
414
+ return;
415
+ }
416
+
417
+ let html = '';
418
+ for (const d of filtered) {
419
+ const isImported = d.domain in importedSet;
420
+ const isInflight = inflight[d.domain];
421
+ html += '<div class="domain-row">';
422
+ html += '<span class="domain-name">' + escHtml(d.domain) + '</span>';
423
+ html += '<span class="domain-count">' + d.count + '</span>';
424
+ if (isInflight) {
425
+ html += '<span class="btn-add" disabled><span class="spinner" style="width:12px;height:12px;border-width:1.5px;"></span></span>';
426
+ } else if (isImported) {
427
+ html += '<span class="btn-add imported">&#10003;</span>';
428
+ } else {
429
+ html += '<button class="btn-add" data-domain="' + escHtml(d.domain) + '" title="Import">+</button>';
430
+ }
431
+ html += '</div>';
432
+ }
433
+ $sourceDomains.innerHTML = html;
434
+
435
+ // Total counts
436
+ const totalDomains = allDomains.length;
437
+ const totalCookies = allDomains.reduce((s, d) => s + d.count, 0);
438
+ $sourceFooter.textContent = totalDomains + ' domains · ' + totalCookies.toLocaleString() + ' cookies';
439
+
440
+ // Click handlers
441
+ $sourceDomains.querySelectorAll('.btn-add[data-domain]').forEach(btn => {
442
+ btn.addEventListener('click', () => importDomain(btn.dataset.domain));
443
+ });
444
+ }
445
+
446
+ // ─── Import Domain ─────────────────────
447
+ async function importDomain(domain) {
448
+ if (inflight[domain] || domain in importedSet) return;
449
+ inflight[domain] = true;
450
+ renderSourceDomains();
451
+
452
+ try {
453
+ const data = await api('/import', {
454
+ method: 'POST',
455
+ headers: { 'Content-Type': 'application/json' },
456
+ body: JSON.stringify({ browser: activeBrowser, domains: [domain] }),
457
+ });
458
+
459
+ if (data.domainCounts) {
460
+ for (const [d, count] of Object.entries(data.domainCounts)) {
461
+ importedSet[d] = (importedSet[d] || 0) + count;
462
+ }
463
+ }
464
+ renderImported();
465
+ } catch (err) {
466
+ showBanner('Import failed for ' + domain + ': ' + err.message, 'error',
467
+ err.action === 'retry' ? () => importDomain(domain) : null);
468
+ } finally {
469
+ delete inflight[domain];
470
+ renderSourceDomains();
471
+ }
472
+ }
473
+
474
+ // ─── Render Imported ───────────────────
475
+ function renderImported() {
476
+ const entries = Object.entries(importedSet).sort((a, b) => b[1] - a[1]);
477
+
478
+ if (entries.length === 0) {
479
+ $importedDomains.innerHTML = '<div class="imported-empty">No cookies imported yet</div>';
480
+ $importedFooter.textContent = '';
481
+ return;
482
+ }
483
+
484
+ let html = '';
485
+ for (const [domain, count] of entries) {
486
+ const isInflight = inflight['remove:' + domain];
487
+ html += '<div class="domain-row">';
488
+ html += '<span class="domain-name">' + escHtml(domain) + '</span>';
489
+ html += '<span class="domain-count">' + count + '</span>';
490
+ if (isInflight) {
491
+ html += '<span class="btn-trash" disabled><span class="spinner" style="width:12px;height:12px;border-width:1.5px;border-top-color:#f87171;"></span></span>';
492
+ } else {
493
+ html += '<button class="btn-trash" data-domain="' + escHtml(domain) + '" title="Remove">&#128465;</button>';
494
+ }
495
+ html += '</div>';
496
+ }
497
+ $importedDomains.innerHTML = html;
498
+
499
+ const totalCookies = entries.reduce((s, e) => s + e[1], 0);
500
+ $importedFooter.textContent = entries.length + ' domains · ' + totalCookies.toLocaleString() + ' cookies imported';
501
+
502
+ // Click handlers
503
+ $importedDomains.querySelectorAll('.btn-trash[data-domain]').forEach(btn => {
504
+ btn.addEventListener('click', () => removeDomain(btn.dataset.domain));
505
+ });
506
+ }
507
+
508
+ // ─── Remove Domain ─────────────────────
509
+ async function removeDomain(domain) {
510
+ if (inflight['remove:' + domain]) return;
511
+ inflight['remove:' + domain] = true;
512
+ renderImported();
513
+
514
+ try {
515
+ await api('/remove', {
516
+ method: 'POST',
517
+ headers: { 'Content-Type': 'application/json' },
518
+ body: JSON.stringify({ domains: [domain] }),
519
+ });
520
+ delete importedSet[domain];
521
+ renderImported();
522
+ renderSourceDomains(); // update checkmarks
523
+ } catch (err) {
524
+ showBanner('Remove failed for ' + domain + ': ' + err.message, 'error',
525
+ err.action === 'retry' ? () => removeDomain(domain) : null);
526
+ } finally {
527
+ delete inflight['remove:' + domain];
528
+ renderImported();
529
+ }
530
+ }
531
+
532
+ // ─── Search ────────────────────────────
533
+ $search.addEventListener('input', renderSourceDomains);
534
+
535
+ // ─── Start ─────────────────────────────
536
+ init();
537
+ })();
538
+ </script>
539
+ </body>
540
+ </html>`;
541
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * find-browse — locate the gstack browse binary.
3
+ *
4
+ * Compiled to browse/dist/find-browse (standalone binary, no bun runtime needed).
5
+ * Outputs the absolute path to the browse binary on stdout, or exits 1 if not found.
6
+ */
7
+
8
+ import { existsSync } from 'fs';
9
+ import { join } from 'path';
10
+ import { homedir } from 'os';
11
+
12
+ // ─── Binary Discovery ───────────────────────────────────────────
13
+
14
+ function getGitRoot(): string | null {
15
+ try {
16
+ const proc = Bun.spawnSync(['git', 'rev-parse', '--show-toplevel'], {
17
+ stdout: 'pipe',
18
+ stderr: 'pipe',
19
+ });
20
+ if (proc.exitCode !== 0) return null;
21
+ return proc.stdout.toString().trim();
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ export function locateBinary(): string | null {
28
+ const root = getGitRoot();
29
+ const home = homedir();
30
+ const markers = ['.codex', '.agents', '.claude'];
31
+
32
+ // Workspace-local takes priority (for development)
33
+ if (root) {
34
+ for (const m of markers) {
35
+ const local = join(root, m, 'skills', 'gstack', 'browse', 'dist', 'browse');
36
+ if (existsSync(local)) return local;
37
+ }
38
+ }
39
+
40
+ // Global fallback
41
+ for (const m of markers) {
42
+ const global = join(home, m, 'skills', 'gstack', 'browse', 'dist', 'browse');
43
+ if (existsSync(global)) return global;
44
+ }
45
+
46
+ return null;
47
+ }
48
+
49
+ // ─── Main ───────────────────────────────────────────────────────
50
+
51
+ function main() {
52
+ const bin = locateBinary();
53
+ if (!bin) {
54
+ process.stderr.write('ERROR: browse binary not found. Run: cd <skill-dir> && ./setup\n');
55
+ process.exit(1);
56
+ }
57
+
58
+ console.log(bin);
59
+ }
60
+
61
+ main();