@cursorpool-dev/cli 0.5.6

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 (105) hide show
  1. package/bin/cursor-pool.mjs +9 -0
  2. package/bin/cursor-pool.ts +169 -0
  3. package/node_modules/@cursor-pool/extension/dist/extension.js +2910 -0
  4. package/node_modules/@cursor-pool/extension/package.json +64 -0
  5. package/node_modules/@cursor-pool/extension/resources/cursor-pool.svg +6 -0
  6. package/node_modules/@cursor-pool/extension/src/api.ts +545 -0
  7. package/node_modules/@cursor-pool/extension/src/extension.ts +104 -0
  8. package/node_modules/@cursor-pool/extension/src/index.ts +1 -0
  9. package/node_modules/@cursor-pool/extension/src/panel.ts +569 -0
  10. package/node_modules/@cursor-pool/extension/src/runtime.ts +22 -0
  11. package/node_modules/@cursor-pool/extension/test/panel.test.ts +1785 -0
  12. package/node_modules/@cursor-pool/patcher/package.json +17 -0
  13. package/node_modules/@cursor-pool/patcher/src/alwaysLocalMarker.ts +86 -0
  14. package/node_modules/@cursor-pool/patcher/src/hash.ts +7 -0
  15. package/node_modules/@cursor-pool/patcher/src/index.ts +55 -0
  16. package/node_modules/@cursor-pool/patcher/src/marker.ts +159 -0
  17. package/node_modules/@cursor-pool/patcher/src/patchCursorAgentExec.ts +154 -0
  18. package/node_modules/@cursor-pool/patcher/src/patchCursorAlwaysLocal.ts +142 -0
  19. package/node_modules/@cursor-pool/patcher/src/patchCursorWorkbenchAuthGate.ts +140 -0
  20. package/node_modules/@cursor-pool/patcher/src/restoreCursorAgentExec.ts +52 -0
  21. package/node_modules/@cursor-pool/patcher/src/restoreCursorAlwaysLocal.ts +52 -0
  22. package/node_modules/@cursor-pool/patcher/src/restoreCursorWorkbenchAuthGate.ts +70 -0
  23. package/node_modules/@cursor-pool/patcher/src/workbenchAuthGateMarker.ts +243 -0
  24. package/node_modules/@cursor-pool/patcher/test/patchCursorAgentExec.test.ts +630 -0
  25. package/node_modules/@cursor-pool/patcher/test/patchCursorAlwaysLocal.test.ts +144 -0
  26. package/node_modules/@cursor-pool/patcher/test/patchCursorWorkbench.test.ts +770 -0
  27. package/node_modules/@cursor-pool/patcher/test/restoreCursorAgentExec.test.ts +139 -0
  28. package/node_modules/@cursor-pool/service/package.json +17 -0
  29. package/node_modules/@cursor-pool/service/src/canary.ts +61 -0
  30. package/node_modules/@cursor-pool/service/src/diagnostics.ts +385 -0
  31. package/node_modules/@cursor-pool/service/src/entry.ts +161 -0
  32. package/node_modules/@cursor-pool/service/src/health.ts +10 -0
  33. package/node_modules/@cursor-pool/service/src/index.ts +29 -0
  34. package/node_modules/@cursor-pool/service/src/metadata.ts +22 -0
  35. package/node_modules/@cursor-pool/service/src/platformSession.ts +1178 -0
  36. package/node_modules/@cursor-pool/service/src/requestCheck.ts +81 -0
  37. package/node_modules/@cursor-pool/service/src/requestGate.ts +100 -0
  38. package/node_modules/@cursor-pool/service/src/requestGateway.ts +441 -0
  39. package/node_modules/@cursor-pool/service/src/runtime.ts +48 -0
  40. package/node_modules/@cursor-pool/service/src/server.ts +939 -0
  41. package/node_modules/@cursor-pool/service/src/takeover.ts +111 -0
  42. package/node_modules/@cursor-pool/service/test/canary.test.ts +140 -0
  43. package/node_modules/@cursor-pool/service/test/diagnostics.test.ts +506 -0
  44. package/node_modules/@cursor-pool/service/test/metadata.test.ts +63 -0
  45. package/node_modules/@cursor-pool/service/test/platformSession.test.ts +2428 -0
  46. package/node_modules/@cursor-pool/service/test/requestCheck.test.ts +152 -0
  47. package/node_modules/@cursor-pool/service/test/requestGate.test.ts +207 -0
  48. package/node_modules/@cursor-pool/service/test/requestGateway.test.ts +466 -0
  49. package/node_modules/@cursor-pool/service/test/runtime.test.ts +47 -0
  50. package/node_modules/@cursor-pool/service/test/server.test.ts +2570 -0
  51. package/node_modules/@cursor-pool/shared/package.json +17 -0
  52. package/node_modules/@cursor-pool/shared/src/clientConfig.ts +49 -0
  53. package/node_modules/@cursor-pool/shared/src/index.ts +14 -0
  54. package/node_modules/@cursor-pool/shared/src/manifest.ts +36 -0
  55. package/node_modules/@cursor-pool/shared/src/metadata.ts +19 -0
  56. package/node_modules/@cursor-pool/shared/src/paths.ts +5 -0
  57. package/node_modules/@cursor-pool/shared/src/runtime.ts +3 -0
  58. package/node_modules/@cursor-pool/shared/test/index.test.ts +56 -0
  59. package/node_modules/@cursor-pool/shared/test/manifest.test.ts +65 -0
  60. package/node_modules/@cursor-pool/shared/test/metadata.test.ts +25 -0
  61. package/node_modules/@cursor-pool/shared/test/runtime.test.ts +8 -0
  62. package/package.json +28 -0
  63. package/src/adHocResign.ts +65 -0
  64. package/src/autostart.ts +240 -0
  65. package/src/compat.ts +282 -0
  66. package/src/confirm.ts +76 -0
  67. package/src/cursor.ts +94 -0
  68. package/src/diagnostics.ts +558 -0
  69. package/src/environment.ts +18 -0
  70. package/src/extensionBundle.ts +111 -0
  71. package/src/extensionLink.ts +168 -0
  72. package/src/index.ts +23 -0
  73. package/src/install.ts +614 -0
  74. package/src/installRecord.ts +105 -0
  75. package/src/launch.ts +182 -0
  76. package/src/patchSet.ts +182 -0
  77. package/src/platform.ts +132 -0
  78. package/src/repair.ts +383 -0
  79. package/src/restore.ts +153 -0
  80. package/src/serviceCommands.ts +79 -0
  81. package/src/serviceProcess.ts +188 -0
  82. package/src/status.ts +241 -0
  83. package/src/target.ts +37 -0
  84. package/src/trial.ts +133 -0
  85. package/src/uninstall.ts +213 -0
  86. package/test/autostart.test.ts +151 -0
  87. package/test/compat.test.ts +192 -0
  88. package/test/confirm.test.ts +114 -0
  89. package/test/cursor-pool-bin.test.ts +658 -0
  90. package/test/cursor.test.ts +20 -0
  91. package/test/diagnostics.test.ts +709 -0
  92. package/test/e2e-install.test.ts +773 -0
  93. package/test/extensionBundle.test.ts +161 -0
  94. package/test/extensionLink.test.ts +209 -0
  95. package/test/install.test.ts +862 -0
  96. package/test/installRecord.test.ts +107 -0
  97. package/test/launch.test.ts +138 -0
  98. package/test/platform.test.ts +226 -0
  99. package/test/repair.test.ts +575 -0
  100. package/test/restore.test.ts +211 -0
  101. package/test/serviceCommands.test.ts +135 -0
  102. package/test/serviceProcess.test.ts +280 -0
  103. package/test/status.test.ts +615 -0
  104. package/test/target.test.ts +49 -0
  105. package/test/trial.test.ts +146 -0
@@ -0,0 +1,709 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import test from 'node:test';
6
+ import { clearDiagnostics, diagnostics } from '../src/diagnostics';
7
+
8
+ const blockedGate = { state: 'blocked' as const, reason: 'logged-out' };
9
+
10
+ const record = (requestId: string) => ({
11
+ requestId,
12
+ requestType: 'agent',
13
+ source: 'cursor-agent-exec',
14
+ model: 'unknown',
15
+ receivedAt: `2026-05-30T00:00:0${requestId}.000Z`,
16
+ runtimeId: 'runtime-1',
17
+ gate: blockedGate,
18
+ });
19
+
20
+ const requestCheckRecord = (requestId: string) => ({
21
+ kind: 'agent-request-check',
22
+ requestId,
23
+ requestType: 'agent',
24
+ source: 'cursor-agent-exec',
25
+ model: 'unknown',
26
+ receivedAt: `2026-05-31T00:00:0${requestId}.000Z`,
27
+ runtimeId: 'runtime-1',
28
+ decision: blockedGate,
29
+ route: { state: 'missing' as const },
30
+ });
31
+
32
+ const gatewayRecord = (requestId: string) => ({
33
+ kind: 'agent-request-gateway',
34
+ requestId,
35
+ requestType: 'agent',
36
+ source: 'cursor-agent-exec',
37
+ model: 'gpt-test',
38
+ receivedAt: `2026-05-31T00:20:0${requestId}.000Z`,
39
+ runtimeId: 'runtime-1',
40
+ decision: {
41
+ state: 'accepted',
42
+ productId: 'prod_basic',
43
+ modeStartedAt: '2026-05-31T00:05:00.000Z',
44
+ route: { state: 'ready', expiresAt: '2999-05-31T00:10:00.000Z' },
45
+ },
46
+ forward: { state: 'not-configured' },
47
+ });
48
+
49
+ const legacyRecord = (requestId: string) => {
50
+ const { gate: _gate, ...legacy } = record(requestId);
51
+ return legacy;
52
+ };
53
+
54
+ async function writeDiagnosticsFile(diagnosticsFile: string, lines: string[]) {
55
+ await mkdir(dirname(diagnosticsFile), { recursive: true });
56
+ await writeFile(diagnosticsFile, `${lines.join('\n')}\n`, 'utf8');
57
+ }
58
+
59
+ test('diagnostics reports missing when the diagnostics file does not exist', async () => {
60
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
61
+ const diagnosticsFile = join(tempDir, 'missing.jsonl');
62
+
63
+ try {
64
+ assert.equal(await diagnostics({ diagnosticsFile }), 'diagnostics: missing');
65
+ } finally {
66
+ await rm(tempDir, { recursive: true, force: true });
67
+ }
68
+ });
69
+
70
+ test('diagnostics reports empty when the diagnostics file has no valid entries', async () => {
71
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
72
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
73
+
74
+ try {
75
+ await writeDiagnosticsFile(diagnosticsFile, [
76
+ '',
77
+ 'not-json',
78
+ JSON.stringify({ requestId: 1 }),
79
+ ]);
80
+
81
+ assert.equal(await diagnostics({ diagnosticsFile }), 'diagnostics: empty');
82
+ } finally {
83
+ await rm(tempDir, { recursive: true, force: true });
84
+ }
85
+ });
86
+
87
+ test('diagnostics displays the most recent sanitized entries', async () => {
88
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
89
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
90
+
91
+ try {
92
+ await writeDiagnosticsFile(diagnosticsFile, [
93
+ JSON.stringify(record('1')),
94
+ JSON.stringify({ ...record('2'), prompt: 'do not print', apiKey: 'secret' }),
95
+ JSON.stringify(record('3')),
96
+ ]);
97
+
98
+ const output = await diagnostics({ diagnosticsFile, limit: 2 });
99
+
100
+ assert.match(output, /^diagnostics: 2 entries\n/);
101
+ assert.doesNotMatch(output, /req-1|requestId=1/);
102
+ assert.match(
103
+ output,
104
+ /requestId=2 source=cursor-agent-exec requestType=agent model=unknown runtimeId=runtime-1 gate=blocked reason=logged-out/,
105
+ );
106
+ assert.match(
107
+ output,
108
+ /requestId=3 source=cursor-agent-exec requestType=agent model=unknown runtimeId=runtime-1 gate=blocked reason=logged-out/,
109
+ );
110
+ assert.doesNotMatch(output, /do not print|apiKey|secret|prompt/);
111
+ } finally {
112
+ await rm(tempDir, { recursive: true, force: true });
113
+ }
114
+ });
115
+
116
+ test('diagnostics displays allowed gate details in human and JSON output', async () => {
117
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
118
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
119
+ const gate = {
120
+ state: 'allowed' as const,
121
+ productId: 'prod_basic',
122
+ modeStartedAt: '2026-05-31T00:05:00.000Z',
123
+ };
124
+
125
+ try {
126
+ await writeDiagnosticsFile(diagnosticsFile, [JSON.stringify({ ...record('1'), gate })]);
127
+
128
+ const humanOutput = await diagnostics({ diagnosticsFile });
129
+ const jsonOutput = await diagnostics({ diagnosticsFile, json: true });
130
+ const parsed = JSON.parse(jsonOutput) as Array<Record<string, unknown>>;
131
+
132
+ assert.match(humanOutput, /gate=allowed productId=prod_basic/);
133
+ assert.deepEqual(parsed, [{ kind: 'agent-canary', ...record('1'), gate }]);
134
+ } finally {
135
+ await rm(tempDir, { recursive: true, force: true });
136
+ }
137
+ });
138
+
139
+ test('diagnostics can emit sanitized JSON output', async () => {
140
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
141
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
142
+
143
+ try {
144
+ await writeDiagnosticsFile(diagnosticsFile, [
145
+ JSON.stringify({ ...record('1'), messages: [{ role: 'user', content: 'secret' }] }),
146
+ ]);
147
+
148
+ const output = await diagnostics({ diagnosticsFile, json: true });
149
+ const parsed = JSON.parse(output) as Array<Record<string, unknown>>;
150
+
151
+ assert.deepEqual(parsed, [{ kind: 'agent-canary', ...record('1') }]);
152
+ assert.equal(Object.hasOwn(parsed[0], 'messages'), false);
153
+ } finally {
154
+ await rm(tempDir, { recursive: true, force: true });
155
+ }
156
+ });
157
+
158
+ test('diagnostics accepts old-format records without gate as unknown', async () => {
159
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
160
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
161
+
162
+ try {
163
+ await writeDiagnosticsFile(diagnosticsFile, [JSON.stringify(legacyRecord('1'))]);
164
+
165
+ const output = await diagnostics({ diagnosticsFile });
166
+ const parsed = JSON.parse(await diagnostics({ diagnosticsFile, json: true }));
167
+
168
+ assert.match(output, /gate=unknown/);
169
+ assert.deepEqual(parsed, [
170
+ { kind: 'agent-canary', ...legacyRecord('1'), gate: { state: 'unknown' } },
171
+ ]);
172
+ } finally {
173
+ await rm(tempDir, { recursive: true, force: true });
174
+ }
175
+ });
176
+
177
+ test('diagnostics rejects malicious allowlisted values before output', async () => {
178
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
179
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
180
+
181
+ try {
182
+ await writeDiagnosticsFile(diagnosticsFile, [
183
+ JSON.stringify({
184
+ ...record('1'),
185
+ requestId: 'req-1\nheaders=secret body=secret filePath=/private prompt=leak',
186
+ }),
187
+ JSON.stringify({
188
+ ...record('2'),
189
+ model: 'unknown\rauthorization=secret cookie=secret',
190
+ }),
191
+ JSON.stringify(record('3')),
192
+ ]);
193
+
194
+ const output = await diagnostics({ diagnosticsFile });
195
+ const parsed = JSON.parse(await diagnostics({ diagnosticsFile, json: true }));
196
+
197
+ assert.match(output, /^diagnostics: 2 entries\n/);
198
+ assert.match(output, /requestId=2 .*model=unknown/);
199
+ assert.match(output, /requestId=3/);
200
+ assert.doesNotMatch(
201
+ output,
202
+ /headers|body|filePath|prompt|authorization|cookie|secret|private|leak/,
203
+ );
204
+ assert.deepEqual(parsed, [
205
+ { kind: 'agent-canary', ...record('2'), model: 'unknown' },
206
+ { kind: 'agent-canary', ...record('3') },
207
+ ]);
208
+ } finally {
209
+ await rm(tempDir, { recursive: true, force: true });
210
+ }
211
+ });
212
+
213
+ test('diagnostics rejects same-line injected identity metadata before output', async () => {
214
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
215
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
216
+
217
+ try {
218
+ await writeDiagnosticsFile(diagnosticsFile, [
219
+ JSON.stringify({
220
+ ...requestCheckRecord('1'),
221
+ requestId: 'req-1 apiKey=secret',
222
+ }),
223
+ JSON.stringify({
224
+ ...gatewayRecord('2'),
225
+ runtimeId: 'runtime-1 token=secret',
226
+ }),
227
+ JSON.stringify(record('3')),
228
+ ]);
229
+
230
+ const output = await diagnostics({ diagnosticsFile });
231
+ const parsed = JSON.parse(await diagnostics({ diagnosticsFile, json: true }));
232
+
233
+ assert.match(output, /^diagnostics: 1 entry\n/);
234
+ assert.match(output, /requestId=3/);
235
+ assert.doesNotMatch(output, /req-1 apiKey=secret|runtime-1 token=secret|apiKey|token|secret/);
236
+ assert.deepEqual(parsed, [{ kind: 'agent-canary', ...record('3') }]);
237
+ } finally {
238
+ await rm(tempDir, { recursive: true, force: true });
239
+ }
240
+ });
241
+
242
+ test('diagnostics sanitizes secret-like model metadata before output', async () => {
243
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
244
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
245
+
246
+ try {
247
+ await writeDiagnosticsFile(diagnosticsFile, [
248
+ JSON.stringify({
249
+ ...record('1'),
250
+ model: 'sk-secret-token',
251
+ }),
252
+ JSON.stringify({
253
+ ...requestCheckRecord('2'),
254
+ model: 'cursor-key',
255
+ }),
256
+ JSON.stringify({
257
+ ...gatewayRecord('3'),
258
+ model: 'provider-secret',
259
+ }),
260
+ ]);
261
+
262
+ const output = await diagnostics({ diagnosticsFile });
263
+ const parsed = JSON.parse(await diagnostics({ diagnosticsFile, json: true })) as Array<{
264
+ model: string;
265
+ }>;
266
+
267
+ assert.match(output, /^diagnostics: 3 entries\n/);
268
+ assert.match(output, /kind=agent-canary requestId=1 .*model=unknown/);
269
+ assert.match(output, /kind=agent-request-check requestId=2 .*model=unknown/);
270
+ assert.match(output, /kind=agent-request-gateway requestId=3 .*model=unknown/);
271
+ assert.doesNotMatch(output, /sk-secret-token|cursor-key|provider-secret|secret|token|key/);
272
+ assert.deepEqual(
273
+ parsed.map((entry) => entry.model),
274
+ ['unknown', 'unknown', 'unknown'],
275
+ );
276
+ } finally {
277
+ await rm(tempDir, { recursive: true, force: true });
278
+ }
279
+ });
280
+
281
+ test('diagnostics sanitizes malicious gate values before output', async () => {
282
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
283
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
284
+
285
+ try {
286
+ await writeDiagnosticsFile(diagnosticsFile, [
287
+ JSON.stringify({
288
+ ...record('1'),
289
+ gate: {
290
+ state: 'allowed',
291
+ productId: 'prod_basic\nprompt=leak',
292
+ modeStartedAt: '2026-05-31T00:05:00.000Z',
293
+ prompt: 'do not print',
294
+ apiKey: 'secret',
295
+ extra: 'discard-me',
296
+ },
297
+ }),
298
+ JSON.stringify({
299
+ ...record('2'),
300
+ gate: {
301
+ state: 'blocked',
302
+ reason: 'logged-out\rapiKey=secret',
303
+ releaseReason: 'invalid-token',
304
+ releasedAt: '2026-05-31T00:10:00.000Z',
305
+ prompt: 'do not print',
306
+ apiKey: 'secret',
307
+ extra: 'discard-me',
308
+ },
309
+ }),
310
+ JSON.stringify({
311
+ ...record('3'),
312
+ gate: {
313
+ state: 'blocked',
314
+ reason: 'logged-out',
315
+ releaseReason: 'invalid-token',
316
+ releasedAt: '2026-05-31T00:10:00.000Z',
317
+ prompt: 'do not print',
318
+ apiKey: 'secret',
319
+ extra: 'discard-me',
320
+ },
321
+ }),
322
+ JSON.stringify({
323
+ ...record('4'),
324
+ gate: {
325
+ state: 'allowed',
326
+ productId: 'prod_basic apiKey=secret',
327
+ modeStartedAt: '2026-05-31T00:05:00.000Z',
328
+ },
329
+ }),
330
+ JSON.stringify({
331
+ ...record('5'),
332
+ gate: {
333
+ state: 'blocked',
334
+ reason: 'logged-out prompt=leak',
335
+ },
336
+ }),
337
+ JSON.stringify({
338
+ ...record('6'),
339
+ gate: {
340
+ state: 'blocked',
341
+ reason: 'logged-out',
342
+ releaseReason: 'invalid-token apiKey=secret',
343
+ releasedAt: '2026-05-31T00:10:00.000Z',
344
+ },
345
+ }),
346
+ ]);
347
+
348
+ const output = await diagnostics({ diagnosticsFile });
349
+ const parsed = JSON.parse(await diagnostics({ diagnosticsFile, json: true }));
350
+
351
+ assert.match(output, /^diagnostics: 6 entries\n/);
352
+ assert.match(output, /requestId=1 .*gate=unknown/);
353
+ assert.match(output, /requestId=2 .*gate=unknown/);
354
+ assert.match(
355
+ output,
356
+ /requestId=3 .*gate=blocked reason=logged-out releaseReason=invalid-token releasedAt=2026-05-31T00:10:00.000Z/,
357
+ );
358
+ assert.match(output, /requestId=4 .*gate=unknown/);
359
+ assert.match(output, /requestId=5 .*gate=unknown/);
360
+ assert.match(
361
+ output,
362
+ /requestId=6 .*gate=blocked reason=logged-out releasedAt=2026-05-31T00:10:00.000Z/,
363
+ );
364
+ assert.doesNotMatch(output, /prompt|apiKey|secret|extra|discard-me|leak/);
365
+ assert.deepEqual(parsed, [
366
+ { kind: 'agent-canary', ...record('1'), gate: { state: 'unknown' } },
367
+ { kind: 'agent-canary', ...record('2'), gate: { state: 'unknown' } },
368
+ {
369
+ kind: 'agent-canary',
370
+ ...record('3'),
371
+ gate: {
372
+ state: 'blocked',
373
+ reason: 'logged-out',
374
+ releaseReason: 'invalid-token',
375
+ releasedAt: '2026-05-31T00:10:00.000Z',
376
+ },
377
+ },
378
+ { kind: 'agent-canary', ...record('4'), gate: { state: 'unknown' } },
379
+ { kind: 'agent-canary', ...record('5'), gate: { state: 'unknown' } },
380
+ {
381
+ kind: 'agent-canary',
382
+ ...record('6'),
383
+ gate: {
384
+ state: 'blocked',
385
+ reason: 'logged-out',
386
+ releasedAt: '2026-05-31T00:10:00.000Z',
387
+ },
388
+ },
389
+ ]);
390
+ for (const entry of parsed as Array<{ gate: Record<string, unknown> }>) {
391
+ assert.equal(Object.hasOwn(entry.gate, 'prompt'), false);
392
+ assert.equal(Object.hasOwn(entry.gate, 'apiKey'), false);
393
+ assert.equal(Object.hasOwn(entry.gate, 'extra'), false);
394
+ }
395
+ } finally {
396
+ await rm(tempDir, { recursive: true, force: true });
397
+ }
398
+ });
399
+
400
+ test('diagnostics displays mixed canary and request-check entries', async () => {
401
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
402
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
403
+
404
+ try {
405
+ await writeDiagnosticsFile(diagnosticsFile, [
406
+ JSON.stringify(record('1')),
407
+ JSON.stringify(requestCheckRecord('2')),
408
+ ]);
409
+
410
+ const output = await diagnostics({ diagnosticsFile });
411
+ const parsed = JSON.parse(await diagnostics({ diagnosticsFile, json: true }));
412
+
413
+ assert.match(output, /^diagnostics: 2 entries\n/);
414
+ assert.match(
415
+ output,
416
+ /kind=agent-canary requestId=1 source=cursor-agent-exec requestType=agent model=unknown runtimeId=runtime-1 gate=blocked reason=logged-out/,
417
+ );
418
+ assert.match(
419
+ output,
420
+ /kind=agent-request-check requestId=2 source=cursor-agent-exec requestType=agent model=unknown runtimeId=runtime-1 decision=blocked reason=logged-out route=missing/,
421
+ );
422
+ assert.deepEqual(parsed, [
423
+ { kind: 'agent-canary', ...record('1') },
424
+ requestCheckRecord('2'),
425
+ ]);
426
+ } finally {
427
+ await rm(tempDir, { recursive: true, force: true });
428
+ }
429
+ });
430
+
431
+ test('diagnostics displays request-gateway entries in human and JSON output', async () => {
432
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
433
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
434
+
435
+ try {
436
+ await writeDiagnosticsFile(diagnosticsFile, [
437
+ JSON.stringify({
438
+ ...gatewayRecord('1'),
439
+ forward: {
440
+ state: 'rejected',
441
+ reason: 'rate-limited',
442
+ retryAfterMs: 1000,
443
+ },
444
+ }),
445
+ ]);
446
+
447
+ const humanOutput = await diagnostics({ diagnosticsFile });
448
+ const jsonOutput = await diagnostics({ diagnosticsFile, json: true });
449
+ const parsed = JSON.parse(jsonOutput);
450
+
451
+ assert.match(
452
+ humanOutput,
453
+ /kind=agent-request-gateway requestId=1 source=cursor-agent-exec requestType=agent model=gpt-test runtimeId=runtime-1 decision=accepted productId=prod_basic route=ready expiresAt=2999-05-31T00:10:00.000Z forward=rejected reason=rate-limited retryAfterMs=1000/,
454
+ );
455
+ assert.deepEqual(parsed, [{
456
+ ...gatewayRecord('1'),
457
+ forward: {
458
+ state: 'rejected',
459
+ reason: 'rate-limited',
460
+ retryAfterMs: 1000,
461
+ },
462
+ }]);
463
+ } finally {
464
+ await rm(tempDir, { recursive: true, force: true });
465
+ }
466
+ });
467
+
468
+ test('diagnostics sanitizes malicious request-gateway entries before output', async () => {
469
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
470
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
471
+
472
+ try {
473
+ await writeDiagnosticsFile(diagnosticsFile, [
474
+ JSON.stringify({
475
+ ...gatewayRecord('1'),
476
+ model: 'sk-secret-token',
477
+ prompt: 'do not print',
478
+ apiKey: 'secret',
479
+ routeToken: 'rt_secret_value',
480
+ forward: {
481
+ state: 'forwarded',
482
+ upstreamRequestId: 'gw_req_1 token=rt_secret_value',
483
+ acceptedAt: 'not-a-date',
484
+ },
485
+ decision: {
486
+ state: 'accepted',
487
+ productId: 'prod_basic apiKey=secret',
488
+ modeStartedAt: '2026-05-31T00:05:00.000Z',
489
+ route: {
490
+ state: 'ready',
491
+ expiresAt: 'not a date token=rt_secret',
492
+ routeToken: 'rt_secret_value',
493
+ },
494
+ },
495
+ }),
496
+ ]);
497
+
498
+ const output = await diagnostics({ diagnosticsFile });
499
+ const parsed = JSON.parse(await diagnostics({ diagnosticsFile, json: true }));
500
+
501
+ assert.match(output, /kind=agent-request-gateway requestId=1 .*model=unknown .*decision=unknown/);
502
+ assert.doesNotMatch(output, /prompt|apiKey|secret|sk-secret-token|routeToken|rt_secret/);
503
+ assert.deepEqual(parsed, [
504
+ {
505
+ ...gatewayRecord('1'),
506
+ model: 'unknown',
507
+ decision: { state: 'unknown' },
508
+ forward: { state: 'unknown' },
509
+ },
510
+ ]);
511
+ } finally {
512
+ await rm(tempDir, { recursive: true, force: true });
513
+ }
514
+ });
515
+
516
+ test('diagnostics formats request-gateway non-accepted decisions', async () => {
517
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
518
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
519
+
520
+ try {
521
+ await writeDiagnosticsFile(diagnosticsFile, [
522
+ JSON.stringify({
523
+ ...gatewayRecord('1'),
524
+ decision: {
525
+ state: 'route-missing',
526
+ productId: 'prod_basic',
527
+ modeStartedAt: '2026-05-31T00:05:00.000Z',
528
+ route: { state: 'missing' },
529
+ },
530
+ }),
531
+ JSON.stringify({
532
+ ...gatewayRecord('2'),
533
+ decision: {
534
+ state: 'route-expired',
535
+ productId: 'prod_basic',
536
+ modeStartedAt: '2026-05-31T00:05:00.000Z',
537
+ route: { state: 'expired', expiresAt: '2026-05-31T00:10:00.000Z' },
538
+ },
539
+ }),
540
+ JSON.stringify({
541
+ ...gatewayRecord('3'),
542
+ decision: {
543
+ state: 'blocked',
544
+ reason: 'mode-released',
545
+ releaseReason: 'invalid-token',
546
+ releasedAt: '2026-05-31T00:10:00.000Z',
547
+ route: { state: 'missing' },
548
+ },
549
+ }),
550
+ JSON.stringify({
551
+ ...gatewayRecord('4'),
552
+ decision: { state: 'unknown' },
553
+ }),
554
+ ]);
555
+
556
+ const output = await diagnostics({ diagnosticsFile });
557
+
558
+ assert.match(output, /requestId=1 .*decision=route-missing productId=prod_basic route=missing/);
559
+ assert.match(
560
+ output,
561
+ /requestId=2 .*decision=route-expired productId=prod_basic route=expired expiresAt=2026-05-31T00:10:00.000Z/,
562
+ );
563
+ assert.match(
564
+ output,
565
+ /requestId=3 .*decision=blocked reason=mode-released releaseReason=invalid-token releasedAt=2026-05-31T00:10:00.000Z route=missing/,
566
+ );
567
+ assert.match(output, /requestId=4 .*decision=unknown/);
568
+ } finally {
569
+ await rm(tempDir, { recursive: true, force: true });
570
+ }
571
+ });
572
+
573
+ test('diagnostics sanitizes malicious request-check entries before output', async () => {
574
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
575
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
576
+
577
+ try {
578
+ await writeDiagnosticsFile(diagnosticsFile, [
579
+ JSON.stringify({
580
+ ...requestCheckRecord('1'),
581
+ model: 'sk-secret-token',
582
+ prompt: 'do not print',
583
+ apiKey: 'secret',
584
+ decision: {
585
+ state: 'allowed',
586
+ productId: 'prod_basic apiKey=secret',
587
+ modeStartedAt: '2026-05-31T00:05:00.000Z',
588
+ prompt: 'do not print',
589
+ },
590
+ route: {
591
+ state: 'ready',
592
+ expiresAt: 'not a date token=rt_secret',
593
+ routeToken: 'rt_secret_value',
594
+ },
595
+ }),
596
+ JSON.stringify({
597
+ ...requestCheckRecord('2'),
598
+ decision: {
599
+ state: 'blocked',
600
+ reason: 'logged-out',
601
+ releaseReason: 'invalid-token',
602
+ releasedAt: '2026-05-31T00:10:00.000Z',
603
+ apiKey: 'secret',
604
+ },
605
+ }),
606
+ ]);
607
+
608
+ const output = await diagnostics({ diagnosticsFile });
609
+ const parsed = JSON.parse(await diagnostics({ diagnosticsFile, json: true }));
610
+
611
+ assert.match(output, /^diagnostics: 2 entries\n/);
612
+ assert.match(output, /kind=agent-request-check requestId=1 .*model=unknown .*decision=unknown route=unknown/);
613
+ assert.match(
614
+ output,
615
+ /kind=agent-request-check requestId=2 .*decision=blocked reason=logged-out releaseReason=invalid-token releasedAt=2026-05-31T00:10:00.000Z route=missing/,
616
+ );
617
+ assert.doesNotMatch(output, /prompt|apiKey|secret|sk-secret-token|do not print|routeToken|rt_secret/);
618
+ assert.deepEqual(parsed, [
619
+ {
620
+ ...requestCheckRecord('1'),
621
+ model: 'unknown',
622
+ decision: { state: 'unknown' },
623
+ route: { state: 'unknown' },
624
+ },
625
+ {
626
+ ...requestCheckRecord('2'),
627
+ decision: {
628
+ state: 'blocked',
629
+ reason: 'logged-out',
630
+ releaseReason: 'invalid-token',
631
+ releasedAt: '2026-05-31T00:10:00.000Z',
632
+ },
633
+ },
634
+ ]);
635
+ } finally {
636
+ await rm(tempDir, { recursive: true, force: true });
637
+ }
638
+ });
639
+
640
+ test('diagnostics displays safe request-check route state without token', async () => {
641
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
642
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
643
+ const route = {
644
+ state: 'ready' as const,
645
+ expiresAt: '2999-05-31T00:10:00.000Z',
646
+ };
647
+
648
+ try {
649
+ await writeDiagnosticsFile(diagnosticsFile, [
650
+ JSON.stringify({
651
+ ...requestCheckRecord('1'),
652
+ route: {
653
+ ...route,
654
+ routeToken: 'rt_secret_value',
655
+ token: 'secret',
656
+ },
657
+ }),
658
+ ]);
659
+
660
+ const output = await diagnostics({ diagnosticsFile });
661
+ const parsed = JSON.parse(await diagnostics({ diagnosticsFile, json: true }));
662
+
663
+ assert.match(output, /decision=blocked reason=logged-out route=ready expiresAt=2999-05-31T00:10:00.000Z/);
664
+ assert.doesNotMatch(output, /routeToken|rt_secret_value|token|secret/);
665
+ assert.deepEqual(parsed, [
666
+ {
667
+ ...requestCheckRecord('1'),
668
+ route,
669
+ },
670
+ ]);
671
+ } finally {
672
+ await rm(tempDir, { recursive: true, force: true });
673
+ }
674
+ });
675
+
676
+ test('diagnostics rejects non-positive limits', async () => {
677
+ await assert.rejects(
678
+ diagnostics({ diagnosticsFile: '/tmp/unused.jsonl', limit: 0 }),
679
+ /--limit must be a positive integer/,
680
+ );
681
+ });
682
+
683
+ test('clearDiagnostics clears only the diagnostics file', async () => {
684
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
685
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
686
+ const runtimeFile = join(tempDir, 'runtime.json');
687
+
688
+ try {
689
+ await writeDiagnosticsFile(diagnosticsFile, [JSON.stringify(record('1'))]);
690
+ await writeFile(runtimeFile, '{"runtimeId":"runtime-1"}\n', 'utf8');
691
+
692
+ assert.equal(await clearDiagnostics({ diagnosticsFile }), 'diagnostics: cleared');
693
+ assert.equal(await readFile(diagnosticsFile, 'utf8'), '');
694
+ assert.equal(await readFile(runtimeFile, 'utf8'), '{"runtimeId":"runtime-1"}\n');
695
+ } finally {
696
+ await rm(tempDir, { recursive: true, force: true });
697
+ }
698
+ });
699
+
700
+ test('clearDiagnostics does not fail when the diagnostics file is missing', async () => {
701
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-diagnostics-'));
702
+ const diagnosticsFile = join(tempDir, 'missing.jsonl');
703
+
704
+ try {
705
+ assert.equal(await clearDiagnostics({ diagnosticsFile }), 'diagnostics: missing');
706
+ } finally {
707
+ await rm(tempDir, { recursive: true, force: true });
708
+ }
709
+ });