opengstack 0.13.10 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/{skills/land-and-deploy/SKILL.md → commands/autoplan.md} +0 -16
  2. package/{skills/benchmark/SKILL.md → commands/benchmark.md} +0 -17
  3. package/{skills/browse/SKILL.md → commands/browse.md} +0 -17
  4. package/{skills/ship/SKILL.md → commands/canary.md} +0 -18
  5. package/{skills/careful/SKILL.md → commands/careful.md} +0 -20
  6. package/{skills/canary/SKILL.md → commands/codex.md} +0 -17
  7. package/{skills/connect-chrome/SKILL.md → commands/connect-chrome.md} +0 -15
  8. package/commands/cso.md +72 -0
  9. package/commands/design-consultation.md +72 -0
  10. package/commands/design-review.md +72 -0
  11. package/commands/design-shotgun.md +72 -0
  12. package/commands/document-release.md +72 -0
  13. package/{skills/freeze/SKILL.md → commands/freeze.md} +0 -26
  14. package/{skills/gstack-upgrade/SKILL.md → commands/gstack-upgrade.md} +0 -14
  15. package/{skills/guard/SKILL.md → commands/guard.md} +0 -31
  16. package/commands/investigate.md +72 -0
  17. package/commands/land-and-deploy.md +72 -0
  18. package/commands/office-hours.md +72 -0
  19. package/commands/plan-ceo-review.md +72 -0
  20. package/commands/plan-design-review.md +72 -0
  21. package/commands/plan-eng-review.md +72 -0
  22. package/commands/qa-only.md +72 -0
  23. package/commands/qa.md +72 -0
  24. package/commands/retro.md +72 -0
  25. package/commands/review.md +72 -0
  26. package/{skills/setup-browser-cookies/SKILL.md → commands/setup-browser-cookies.md} +0 -14
  27. package/commands/setup-deploy.md +72 -0
  28. package/commands/ship.md +72 -0
  29. package/{skills/unfreeze/SKILL.md → commands/unfreeze.md} +0 -12
  30. package/package.json +4 -4
  31. package/scripts/install-commands.js +45 -0
  32. package/skills/autoplan/SKILL.md +0 -96
  33. package/skills/autoplan/SKILL.md.tmpl +0 -694
  34. package/skills/benchmark/SKILL.md.tmpl +0 -222
  35. package/skills/browse/SKILL.md.tmpl +0 -131
  36. package/skills/browse/bin/find-browse +0 -21
  37. package/skills/browse/bin/remote-slug +0 -14
  38. package/skills/browse/scripts/build-node-server.sh +0 -48
  39. package/skills/browse/src/activity.ts +0 -208
  40. package/skills/browse/src/browser-manager.ts +0 -959
  41. package/skills/browse/src/buffers.ts +0 -137
  42. package/skills/browse/src/bun-polyfill.cjs +0 -109
  43. package/skills/browse/src/cli.ts +0 -678
  44. package/skills/browse/src/commands.ts +0 -128
  45. package/skills/browse/src/config.ts +0 -150
  46. package/skills/browse/src/cookie-import-browser.ts +0 -625
  47. package/skills/browse/src/cookie-picker-routes.ts +0 -230
  48. package/skills/browse/src/cookie-picker-ui.ts +0 -688
  49. package/skills/browse/src/find-browse.ts +0 -61
  50. package/skills/browse/src/meta-commands.ts +0 -550
  51. package/skills/browse/src/platform.ts +0 -17
  52. package/skills/browse/src/read-commands.ts +0 -358
  53. package/skills/browse/src/server.ts +0 -1192
  54. package/skills/browse/src/sidebar-agent.ts +0 -280
  55. package/skills/browse/src/sidebar-utils.ts +0 -21
  56. package/skills/browse/src/snapshot.ts +0 -407
  57. package/skills/browse/src/url-validation.ts +0 -95
  58. package/skills/browse/src/write-commands.ts +0 -364
  59. package/skills/browse/test/activity.test.ts +0 -120
  60. package/skills/browse/test/adversarial-security.test.ts +0 -32
  61. package/skills/browse/test/browser-manager-unit.test.ts +0 -17
  62. package/skills/browse/test/bun-polyfill.test.ts +0 -72
  63. package/skills/browse/test/commands.test.ts +0 -2075
  64. package/skills/browse/test/compare-board.test.ts +0 -342
  65. package/skills/browse/test/config.test.ts +0 -316
  66. package/skills/browse/test/cookie-import-browser.test.ts +0 -519
  67. package/skills/browse/test/cookie-picker-routes.test.ts +0 -260
  68. package/skills/browse/test/file-drop.test.ts +0 -271
  69. package/skills/browse/test/find-browse.test.ts +0 -50
  70. package/skills/browse/test/findport.test.ts +0 -191
  71. package/skills/browse/test/fixtures/basic.html +0 -33
  72. package/skills/browse/test/fixtures/cursor-interactive.html +0 -22
  73. package/skills/browse/test/fixtures/dialog.html +0 -15
  74. package/skills/browse/test/fixtures/empty.html +0 -2
  75. package/skills/browse/test/fixtures/forms.html +0 -55
  76. package/skills/browse/test/fixtures/iframe.html +0 -30
  77. package/skills/browse/test/fixtures/network-idle.html +0 -30
  78. package/skills/browse/test/fixtures/qa-eval-checkout.html +0 -108
  79. package/skills/browse/test/fixtures/qa-eval-spa.html +0 -98
  80. package/skills/browse/test/fixtures/qa-eval.html +0 -51
  81. package/skills/browse/test/fixtures/responsive.html +0 -49
  82. package/skills/browse/test/fixtures/snapshot.html +0 -55
  83. package/skills/browse/test/fixtures/spa.html +0 -24
  84. package/skills/browse/test/fixtures/states.html +0 -17
  85. package/skills/browse/test/fixtures/upload.html +0 -25
  86. package/skills/browse/test/gstack-config.test.ts +0 -138
  87. package/skills/browse/test/gstack-update-check.test.ts +0 -514
  88. package/skills/browse/test/handoff.test.ts +0 -235
  89. package/skills/browse/test/path-validation.test.ts +0 -91
  90. package/skills/browse/test/platform.test.ts +0 -37
  91. package/skills/browse/test/server-auth.test.ts +0 -65
  92. package/skills/browse/test/sidebar-agent-roundtrip.test.ts +0 -226
  93. package/skills/browse/test/sidebar-agent.test.ts +0 -199
  94. package/skills/browse/test/sidebar-integration.test.ts +0 -320
  95. package/skills/browse/test/sidebar-unit.test.ts +0 -96
  96. package/skills/browse/test/snapshot.test.ts +0 -467
  97. package/skills/browse/test/state-ttl.test.ts +0 -35
  98. package/skills/browse/test/test-server.ts +0 -57
  99. package/skills/browse/test/url-validation.test.ts +0 -72
  100. package/skills/browse/test/watch.test.ts +0 -129
  101. package/skills/canary/SKILL.md.tmpl +0 -212
  102. package/skills/careful/SKILL.md.tmpl +0 -56
  103. package/skills/careful/bin/check-careful.sh +0 -112
  104. package/skills/codex/SKILL.md +0 -90
  105. package/skills/codex/SKILL.md.tmpl +0 -417
  106. package/skills/connect-chrome/SKILL.md.tmpl +0 -195
  107. package/skills/cso/ACKNOWLEDGEMENTS.md +0 -14
  108. package/skills/cso/SKILL.md +0 -93
  109. package/skills/cso/SKILL.md.tmpl +0 -606
  110. package/skills/design-consultation/SKILL.md +0 -94
  111. package/skills/design-consultation/SKILL.md.tmpl +0 -415
  112. package/skills/design-review/SKILL.md +0 -94
  113. package/skills/design-review/SKILL.md.tmpl +0 -290
  114. package/skills/design-shotgun/SKILL.md +0 -91
  115. package/skills/design-shotgun/SKILL.md.tmpl +0 -285
  116. package/skills/document-release/SKILL.md +0 -91
  117. package/skills/document-release/SKILL.md.tmpl +0 -359
  118. package/skills/freeze/SKILL.md.tmpl +0 -77
  119. package/skills/freeze/bin/check-freeze.sh +0 -79
  120. package/skills/gstack-upgrade/SKILL.md.tmpl +0 -222
  121. package/skills/guard/SKILL.md.tmpl +0 -77
  122. package/skills/investigate/SKILL.md +0 -105
  123. package/skills/investigate/SKILL.md.tmpl +0 -194
  124. package/skills/land-and-deploy/SKILL.md.tmpl +0 -881
  125. package/skills/office-hours/SKILL.md +0 -96
  126. package/skills/office-hours/SKILL.md.tmpl +0 -645
  127. package/skills/plan-ceo-review/SKILL.md +0 -94
  128. package/skills/plan-ceo-review/SKILL.md.tmpl +0 -811
  129. package/skills/plan-design-review/SKILL.md +0 -92
  130. package/skills/plan-design-review/SKILL.md.tmpl +0 -446
  131. package/skills/plan-eng-review/SKILL.md +0 -93
  132. package/skills/plan-eng-review/SKILL.md.tmpl +0 -303
  133. package/skills/qa/SKILL.md +0 -95
  134. package/skills/qa/SKILL.md.tmpl +0 -316
  135. package/skills/qa/references/issue-taxonomy.md +0 -85
  136. package/skills/qa/templates/qa-report-template.md +0 -126
  137. package/skills/qa-only/SKILL.md +0 -89
  138. package/skills/qa-only/SKILL.md.tmpl +0 -101
  139. package/skills/retro/SKILL.md +0 -89
  140. package/skills/retro/SKILL.md.tmpl +0 -820
  141. package/skills/review/SKILL.md +0 -92
  142. package/skills/review/SKILL.md.tmpl +0 -281
  143. package/skills/review/TODOS-format.md +0 -62
  144. package/skills/review/checklist.md +0 -220
  145. package/skills/review/design-checklist.md +0 -132
  146. package/skills/review/greptile-triage.md +0 -220
  147. package/skills/setup-browser-cookies/SKILL.md.tmpl +0 -81
  148. package/skills/setup-deploy/SKILL.md +0 -92
  149. package/skills/setup-deploy/SKILL.md.tmpl +0 -215
  150. package/skills/ship/SKILL.md.tmpl +0 -636
  151. package/skills/unfreeze/SKILL.md.tmpl +0 -36
@@ -1,260 +0,0 @@
1
- /**
2
- * Tests for cookie-picker route handler
3
- *
4
- * Tests the HTTP glue layer directly with mock BrowserManager objects.
5
- * Verifies that all routes return valid JSON (not HTML) with correct CORS headers.
6
- */
7
-
8
- import { describe, test, expect } from 'bun:test';
9
- import { handleCookiePickerRoute } from '../src/cookie-picker-routes';
10
-
11
- // ─── Mock BrowserManager ──────────────────────────────────────
12
-
13
- function mockBrowserManager() {
14
- const addedCookies: any[] = [];
15
- const clearedDomains: string[] = [];
16
- return {
17
- bm: {
18
- getPage: () => ({
19
- context: () => ({
20
- addCookies: (cookies: any[]) => { addedCookies.push(...cookies); },
21
- clearCookies: (opts: { domain: string }) => { clearedDomains.push(opts.domain); },
22
- }),
23
- }),
24
- } as any,
25
- addedCookies,
26
- clearedDomains,
27
- };
28
- }
29
-
30
- function makeUrl(path: string, port = 9470): URL {
31
- return new URL(`http://127.0.0.1:${port}${path}`);
32
- }
33
-
34
- function makeReq(method: string, body?: any): Request {
35
- const opts: RequestInit = { method };
36
- if (body) {
37
- opts.body = JSON.stringify(body);
38
- opts.headers = { 'Content-Type': 'application/json' };
39
- }
40
- return new Request('http://127.0.0.1:9470', opts);
41
- }
42
-
43
- // ─── Tests ──────────────────────────────────────────────────────
44
-
45
- describe('cookie-picker-routes', () => {
46
- describe('CORS', () => {
47
- test('OPTIONS returns 204 with correct CORS headers', async () => {
48
- const { bm } = mockBrowserManager();
49
- const url = makeUrl('/cookie-picker/browsers');
50
- const req = new Request('http://127.0.0.1:9470', { method: 'OPTIONS' });
51
-
52
- const res = await handleCookiePickerRoute(url, req, bm);
53
-
54
- expect(res.status).toBe(204);
55
- expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://127.0.0.1:9470');
56
- expect(res.headers.get('Access-Control-Allow-Methods')).toContain('POST');
57
- });
58
-
59
- test('JSON responses include correct CORS origin with port', async () => {
60
- const { bm } = mockBrowserManager();
61
- const url = makeUrl('/cookie-picker/browsers', 9450);
62
- const req = new Request('http://127.0.0.1:9450', { method: 'GET' });
63
-
64
- const res = await handleCookiePickerRoute(url, req, bm);
65
-
66
- expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://127.0.0.1:9450');
67
- });
68
- });
69
-
70
- describe('JSON responses (not HTML)', () => {
71
- test('GET /cookie-picker/browsers returns JSON', async () => {
72
- const { bm } = mockBrowserManager();
73
- const url = makeUrl('/cookie-picker/browsers');
74
- const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
75
-
76
- const res = await handleCookiePickerRoute(url, req, bm);
77
-
78
- expect(res.status).toBe(200);
79
- expect(res.headers.get('Content-Type')).toBe('application/json');
80
- const body = await res.json();
81
- expect(body).toHaveProperty('browsers');
82
- expect(Array.isArray(body.browsers)).toBe(true);
83
- });
84
-
85
- test('GET /cookie-picker/domains without browser param returns JSON error', async () => {
86
- const { bm } = mockBrowserManager();
87
- const url = makeUrl('/cookie-picker/domains');
88
- const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
89
-
90
- const res = await handleCookiePickerRoute(url, req, bm);
91
-
92
- expect(res.status).toBe(400);
93
- expect(res.headers.get('Content-Type')).toBe('application/json');
94
- const body = await res.json();
95
- expect(body).toHaveProperty('error');
96
- expect(body).toHaveProperty('code', 'missing_param');
97
- });
98
-
99
- test('POST /cookie-picker/import with invalid JSON returns JSON error', async () => {
100
- const { bm } = mockBrowserManager();
101
- const url = makeUrl('/cookie-picker/import');
102
- const req = new Request('http://127.0.0.1:9470', {
103
- method: 'POST',
104
- body: 'not json',
105
- headers: { 'Content-Type': 'application/json' },
106
- });
107
-
108
- const res = await handleCookiePickerRoute(url, req, bm);
109
-
110
- expect(res.status).toBe(400);
111
- expect(res.headers.get('Content-Type')).toBe('application/json');
112
- const body = await res.json();
113
- expect(body.code).toBe('bad_request');
114
- });
115
-
116
- test('POST /cookie-picker/import missing browser field returns JSON error', async () => {
117
- const { bm } = mockBrowserManager();
118
- const url = makeUrl('/cookie-picker/import');
119
- const req = makeReq('POST', { domains: ['.example.com'] });
120
-
121
- const res = await handleCookiePickerRoute(url, req, bm);
122
-
123
- expect(res.status).toBe(400);
124
- const body = await res.json();
125
- expect(body.code).toBe('missing_param');
126
- });
127
-
128
- test('POST /cookie-picker/import missing domains returns JSON error', async () => {
129
- const { bm } = mockBrowserManager();
130
- const url = makeUrl('/cookie-picker/import');
131
- const req = makeReq('POST', { browser: 'Chrome' });
132
-
133
- const res = await handleCookiePickerRoute(url, req, bm);
134
-
135
- expect(res.status).toBe(400);
136
- const body = await res.json();
137
- expect(body.code).toBe('missing_param');
138
- });
139
-
140
- test('POST /cookie-picker/remove with invalid JSON returns JSON error', async () => {
141
- const { bm } = mockBrowserManager();
142
- const url = makeUrl('/cookie-picker/remove');
143
- const req = new Request('http://127.0.0.1:9470', {
144
- method: 'POST',
145
- body: '{bad',
146
- headers: { 'Content-Type': 'application/json' },
147
- });
148
-
149
- const res = await handleCookiePickerRoute(url, req, bm);
150
-
151
- expect(res.status).toBe(400);
152
- expect(res.headers.get('Content-Type')).toBe('application/json');
153
- });
154
-
155
- test('POST /cookie-picker/remove missing domains returns JSON error', async () => {
156
- const { bm } = mockBrowserManager();
157
- const url = makeUrl('/cookie-picker/remove');
158
- const req = makeReq('POST', {});
159
-
160
- const res = await handleCookiePickerRoute(url, req, bm);
161
-
162
- expect(res.status).toBe(400);
163
- const body = await res.json();
164
- expect(body.code).toBe('missing_param');
165
- });
166
-
167
- test('GET /cookie-picker/imported returns JSON with domain list', async () => {
168
- const { bm } = mockBrowserManager();
169
- const url = makeUrl('/cookie-picker/imported');
170
- const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
171
-
172
- const res = await handleCookiePickerRoute(url, req, bm);
173
-
174
- expect(res.status).toBe(200);
175
- expect(res.headers.get('Content-Type')).toBe('application/json');
176
- const body = await res.json();
177
- expect(body).toHaveProperty('domains');
178
- expect(body).toHaveProperty('totalDomains');
179
- expect(body).toHaveProperty('totalCookies');
180
- });
181
- });
182
-
183
- describe('routing', () => {
184
- test('GET /cookie-picker returns HTML', async () => {
185
- const { bm } = mockBrowserManager();
186
- const url = makeUrl('/cookie-picker');
187
- const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
188
-
189
- const res = await handleCookiePickerRoute(url, req, bm);
190
-
191
- expect(res.status).toBe(200);
192
- expect(res.headers.get('Content-Type')).toContain('text/html');
193
- });
194
-
195
- test('unknown path returns 404', async () => {
196
- const { bm } = mockBrowserManager();
197
- const url = makeUrl('/cookie-picker/nonexistent');
198
- const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
199
-
200
- const res = await handleCookiePickerRoute(url, req, bm);
201
-
202
- expect(res.status).toBe(404);
203
- });
204
- });
205
-
206
- describe('auth gate security', () => {
207
- test('GET /cookie-picker HTML page works without auth token', async () => {
208
- const { bm } = mockBrowserManager();
209
- const url = makeUrl('/cookie-picker');
210
- // Request with no Authorization header, but authToken is set on the server
211
- const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
212
-
213
- const res = await handleCookiePickerRoute(url, req, bm, 'test-secret-token');
214
-
215
- expect(res.status).toBe(200);
216
- expect(res.headers.get('Content-Type')).toContain('text/html');
217
- });
218
-
219
- test('GET /cookie-picker/browsers returns 401 without auth', async () => {
220
- const { bm } = mockBrowserManager();
221
- const url = makeUrl('/cookie-picker/browsers');
222
- // No Authorization header
223
- const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
224
-
225
- const res = await handleCookiePickerRoute(url, req, bm, 'test-secret-token');
226
-
227
- expect(res.status).toBe(401);
228
- const body = await res.json();
229
- expect(body.error).toBe('Unauthorized');
230
- });
231
-
232
- test('POST /cookie-picker/import returns 401 without auth', async () => {
233
- const { bm } = mockBrowserManager();
234
- const url = makeUrl('/cookie-picker/import');
235
- const req = makeReq('POST', { browser: 'Chrome', domains: ['.example.com'] });
236
-
237
- const res = await handleCookiePickerRoute(url, req, bm, 'test-secret-token');
238
-
239
- expect(res.status).toBe(401);
240
- const body = await res.json();
241
- expect(body.error).toBe('Unauthorized');
242
- });
243
-
244
- test('GET /cookie-picker/browsers works with valid auth', async () => {
245
- const { bm } = mockBrowserManager();
246
- const url = makeUrl('/cookie-picker/browsers');
247
- const req = new Request('http://127.0.0.1:9470', {
248
- method: 'GET',
249
- headers: { 'Authorization': 'Bearer test-secret-token' },
250
- });
251
-
252
- const res = await handleCookiePickerRoute(url, req, bm, 'test-secret-token');
253
-
254
- expect(res.status).toBe(200);
255
- expect(res.headers.get('Content-Type')).toBe('application/json');
256
- const body = await res.json();
257
- expect(body).toHaveProperty('browsers');
258
- });
259
- });
260
- });
@@ -1,271 +0,0 @@
1
- /**
2
- * Tests for the inbox meta-command handler (file drop relay).
3
- *
4
- * Tests the inbox display, --clear flag, and edge cases by creating
5
- * temp directories with test JSON files and calling handleMetaCommand.
6
- */
7
-
8
- import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
9
- import * as fs from 'fs';
10
- import * as path from 'path';
11
- import * as os from 'os';
12
- import { handleMetaCommand } from '../src/meta-commands';
13
- import { BrowserManager } from '../src/browser-manager';
14
-
15
- let tmpDir: string;
16
- let bm: BrowserManager;
17
-
18
- // We need a BrowserManager instance for handleMetaCommand, but inbox
19
- // doesn't use it. We also need to mock git rev-parse to point to our
20
- // temp directory. We'll test the inbox logic directly by manipulating
21
- // the filesystem and using child_process.execSync override.
22
-
23
- // ─── Direct filesystem tests (bypassing handleMetaCommand) ──────
24
- // The inbox handler in meta-commands.ts calls `git rev-parse --show-toplevel`
25
- // to find the inbox directory. Since we can't easily mock that in unit tests,
26
- // we test the inbox parsing logic directly.
27
-
28
- interface InboxMessage {
29
- timestamp: string;
30
- url: string;
31
- userMessage: string;
32
- }
33
-
34
- /** Replicate the inbox file reading logic from meta-commands.ts */
35
- function readInbox(inboxDir: string): InboxMessage[] {
36
- if (!fs.existsSync(inboxDir)) return [];
37
-
38
- const files = fs.readdirSync(inboxDir)
39
- .filter(f => f.endsWith('.json') && !f.startsWith('.'))
40
- .sort()
41
- .reverse();
42
-
43
- if (files.length === 0) return [];
44
-
45
- const messages: InboxMessage[] = [];
46
- for (const file of files) {
47
- try {
48
- const data = JSON.parse(fs.readFileSync(path.join(inboxDir, file), 'utf-8'));
49
- messages.push({
50
- timestamp: data.timestamp || '',
51
- url: data.page?.url || 'unknown',
52
- userMessage: data.userMessage || '',
53
- });
54
- } catch {
55
- // Skip malformed files
56
- }
57
- }
58
- return messages;
59
- }
60
-
61
- /** Replicate the inbox formatting logic from meta-commands.ts */
62
- function formatInbox(messages: InboxMessage[]): string {
63
- if (messages.length === 0) return 'Inbox empty.';
64
-
65
- const lines: string[] = [];
66
- lines.push(`SIDEBAR INBOX (${messages.length} message${messages.length === 1 ? '' : 's'})`);
67
- lines.push('────────────────────────────────');
68
-
69
- for (const msg of messages) {
70
- const ts = msg.timestamp ? `[${msg.timestamp}]` : '[unknown]';
71
- lines.push(`${ts} ${msg.url}`);
72
- lines.push(` "${msg.userMessage}"`);
73
- lines.push('');
74
- }
75
-
76
- lines.push('────────────────────────────────');
77
- return lines.join('\n');
78
- }
79
-
80
- /** Replicate the --clear logic from meta-commands.ts */
81
- function clearInbox(inboxDir: string): number {
82
- const files = fs.readdirSync(inboxDir)
83
- .filter(f => f.endsWith('.json') && !f.startsWith('.'));
84
- for (const file of files) {
85
- try { fs.unlinkSync(path.join(inboxDir, file)); } catch {}
86
- }
87
- return files.length;
88
- }
89
-
90
- function writeTestInboxFile(
91
- inboxDir: string,
92
- message: string,
93
- pageUrl: string,
94
- timestamp: string,
95
- ): string {
96
- fs.mkdirSync(inboxDir, { recursive: true });
97
- const filename = `${timestamp.replace(/:/g, '-')}-observation.json`;
98
- const filePath = path.join(inboxDir, filename);
99
- fs.writeFileSync(filePath, JSON.stringify({
100
- type: 'observation',
101
- timestamp,
102
- page: { url: pageUrl, title: '' },
103
- userMessage: message,
104
- sidebarSessionId: 'test-session',
105
- }, null, 2));
106
- return filePath;
107
- }
108
-
109
- beforeEach(() => {
110
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'file-drop-test-'));
111
- });
112
-
113
- afterEach(() => {
114
- fs.rmSync(tmpDir, { recursive: true, force: true });
115
- });
116
-
117
- // ─── Empty Inbox ─────────────────────────────────────────────────
118
-
119
- describe('inbox — empty states', () => {
120
- test('no .context/sidebar-inbox directory returns empty', () => {
121
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
122
- const messages = readInbox(inboxDir);
123
- expect(messages.length).toBe(0);
124
- expect(formatInbox(messages)).toBe('Inbox empty.');
125
- });
126
-
127
- test('empty inbox directory returns empty', () => {
128
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
129
- fs.mkdirSync(inboxDir, { recursive: true });
130
- const messages = readInbox(inboxDir);
131
- expect(messages.length).toBe(0);
132
- expect(formatInbox(messages)).toBe('Inbox empty.');
133
- });
134
-
135
- test('directory with only dotfiles returns empty', () => {
136
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
137
- fs.mkdirSync(inboxDir, { recursive: true });
138
- fs.writeFileSync(path.join(inboxDir, '.tmp-file.json'), '{}');
139
- const messages = readInbox(inboxDir);
140
- expect(messages.length).toBe(0);
141
- });
142
- });
143
-
144
- // ─── Valid Messages ──────────────────────────────────────────────
145
-
146
- describe('inbox — valid messages', () => {
147
- test('displays formatted output with timestamps and URLs', () => {
148
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
149
- writeTestInboxFile(inboxDir, 'This button is broken', 'https://example.com/page', '2024-06-15T10:30:00.000Z');
150
- writeTestInboxFile(inboxDir, 'Login form fails', 'https://example.com/login', '2024-06-15T10:31:00.000Z');
151
-
152
- const messages = readInbox(inboxDir);
153
- expect(messages.length).toBe(2);
154
-
155
- const output = formatInbox(messages);
156
- expect(output).toContain('SIDEBAR INBOX (2 messages)');
157
- expect(output).toContain('https://example.com/page');
158
- expect(output).toContain('https://example.com/login');
159
- expect(output).toContain('"This button is broken"');
160
- expect(output).toContain('"Login form fails"');
161
- expect(output).toContain('[2024-06-15T10:30:00.000Z]');
162
- expect(output).toContain('[2024-06-15T10:31:00.000Z]');
163
- });
164
-
165
- test('single message uses singular form', () => {
166
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
167
- writeTestInboxFile(inboxDir, 'Just one', 'https://example.com', '2024-06-15T10:30:00.000Z');
168
-
169
- const messages = readInbox(inboxDir);
170
- const output = formatInbox(messages);
171
- expect(output).toContain('1 message)');
172
- expect(output).not.toContain('messages)');
173
- });
174
-
175
- test('messages sorted newest first', () => {
176
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
177
- writeTestInboxFile(inboxDir, 'older', 'https://example.com', '2024-06-15T10:00:00.000Z');
178
- writeTestInboxFile(inboxDir, 'newer', 'https://example.com', '2024-06-15T11:00:00.000Z');
179
-
180
- const messages = readInbox(inboxDir);
181
- // Filenames sort lexicographically, reversed = newest first
182
- expect(messages[0].userMessage).toBe('newer');
183
- expect(messages[1].userMessage).toBe('older');
184
- });
185
- });
186
-
187
- // ─── Malformed Files ─────────────────────────────────────────────
188
-
189
- describe('inbox — malformed files', () => {
190
- test('malformed JSON files are skipped gracefully', () => {
191
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
192
- fs.mkdirSync(inboxDir, { recursive: true });
193
-
194
- // Write a valid message
195
- writeTestInboxFile(inboxDir, 'valid message', 'https://example.com', '2024-06-15T10:30:00.000Z');
196
-
197
- // Write a malformed JSON file
198
- fs.writeFileSync(
199
- path.join(inboxDir, '2024-06-15T10-35-00.000Z-observation.json'),
200
- 'this is not valid json {{{',
201
- );
202
-
203
- const messages = readInbox(inboxDir);
204
- expect(messages.length).toBe(1);
205
- expect(messages[0].userMessage).toBe('valid message');
206
- });
207
-
208
- test('JSON file missing fields uses defaults', () => {
209
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
210
- fs.mkdirSync(inboxDir, { recursive: true });
211
-
212
- // Write a JSON file with missing fields
213
- fs.writeFileSync(
214
- path.join(inboxDir, '2024-06-15T10-30-00.000Z-observation.json'),
215
- JSON.stringify({ type: 'observation' }),
216
- );
217
-
218
- const messages = readInbox(inboxDir);
219
- expect(messages.length).toBe(1);
220
- expect(messages[0].timestamp).toBe('');
221
- expect(messages[0].url).toBe('unknown');
222
- expect(messages[0].userMessage).toBe('');
223
- });
224
- });
225
-
226
- // ─── Clear Flag ──────────────────────────────────────────────────
227
-
228
- describe('inbox — --clear flag', () => {
229
- test('files deleted after clear', () => {
230
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
231
- writeTestInboxFile(inboxDir, 'message 1', 'https://example.com', '2024-06-15T10:30:00.000Z');
232
- writeTestInboxFile(inboxDir, 'message 2', 'https://example.com', '2024-06-15T10:31:00.000Z');
233
-
234
- // Verify files exist
235
- const filesBefore = fs.readdirSync(inboxDir).filter(f => f.endsWith('.json') && !f.startsWith('.'));
236
- expect(filesBefore.length).toBe(2);
237
-
238
- // Clear
239
- const cleared = clearInbox(inboxDir);
240
- expect(cleared).toBe(2);
241
-
242
- // Verify files deleted
243
- const filesAfter = fs.readdirSync(inboxDir).filter(f => f.endsWith('.json') && !f.startsWith('.'));
244
- expect(filesAfter.length).toBe(0);
245
- });
246
-
247
- test('clear on empty directory does nothing', () => {
248
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
249
- fs.mkdirSync(inboxDir, { recursive: true });
250
-
251
- const cleared = clearInbox(inboxDir);
252
- expect(cleared).toBe(0);
253
- });
254
-
255
- test('clear preserves dotfiles', () => {
256
- const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
257
- fs.mkdirSync(inboxDir, { recursive: true });
258
-
259
- // Write a dotfile and a regular file
260
- fs.writeFileSync(path.join(inboxDir, '.keep'), '');
261
- writeTestInboxFile(inboxDir, 'to be cleared', 'https://example.com', '2024-06-15T10:30:00.000Z');
262
-
263
- clearInbox(inboxDir);
264
-
265
- // Dotfile should remain
266
- expect(fs.existsSync(path.join(inboxDir, '.keep'))).toBe(true);
267
- // Regular file should be gone
268
- const jsonFiles = fs.readdirSync(inboxDir).filter(f => f.endsWith('.json') && !f.startsWith('.'));
269
- expect(jsonFiles.length).toBe(0);
270
- });
271
- });
@@ -1,50 +0,0 @@
1
- /**
2
- * Tests for find-browse binary locator.
3
- */
4
-
5
- import { describe, test, expect } from 'bun:test';
6
- import { locateBinary } from '../src/find-browse';
7
- import { existsSync } from 'fs';
8
-
9
- describe('locateBinary', () => {
10
- test('returns null when no binary exists at known paths', () => {
11
- // This test depends on the test environment — if a real binary exists at
12
- // ~/.claude/skills/gstack/browse/dist/browse, it will find it.
13
- // We mainly test that the function doesn't throw.
14
- const result = locateBinary();
15
- expect(result === null || typeof result === 'string').toBe(true);
16
- });
17
-
18
- test('returns string path when binary exists', () => {
19
- const result = locateBinary();
20
- if (result !== null) {
21
- expect(existsSync(result)).toBe(true);
22
- }
23
- });
24
-
25
- test('priority chain checks .codex, .agents, .claude markers', () => {
26
- // Verify the source code implements the correct priority order.
27
- // We read the function source to confirm the markers array order.
28
- const src = require('fs').readFileSync(require('path').join(__dirname, '../src/find-browse.ts'), 'utf-8');
29
- // The markers array should list .codex first, then .agents, then .claude
30
- const markersMatch = src.match(/const markers = \[([^\]]+)\]/);
31
- expect(markersMatch).not.toBeNull();
32
- const markers = markersMatch![1];
33
- const codexIdx = markers.indexOf('.codex');
34
- const agentsIdx = markers.indexOf('.agents');
35
- const claudeIdx = markers.indexOf('.claude');
36
- // All three must be present
37
- expect(codexIdx).toBeGreaterThanOrEqual(0);
38
- expect(agentsIdx).toBeGreaterThanOrEqual(0);
39
- expect(claudeIdx).toBeGreaterThanOrEqual(0);
40
- // .codex before .agents before .claude
41
- expect(codexIdx).toBeLessThan(agentsIdx);
42
- expect(agentsIdx).toBeLessThan(claudeIdx);
43
- });
44
-
45
- test('function signature accepts no arguments', () => {
46
- // locateBinary should be callable with no arguments
47
- expect(typeof locateBinary).toBe('function');
48
- expect(locateBinary.length).toBe(0);
49
- });
50
- });