@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,558 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { DEFAULT_DIAGNOSTICS_FILE } from '@cursor-pool/shared/runtime';
5
+
6
+ export type AgentCanaryDiagnostic = {
7
+ kind: 'agent-canary';
8
+ requestId: string;
9
+ requestType: 'agent';
10
+ source: 'cursor-agent-exec';
11
+ model: string;
12
+ receivedAt: string;
13
+ runtimeId: string;
14
+ gate: RequestGateDiagnostic;
15
+ };
16
+
17
+ export type AgentRequestCheckDiagnostic = {
18
+ kind: 'agent-request-check';
19
+ requestId: string;
20
+ requestType: 'agent';
21
+ source: 'cursor-agent-exec' | 'manual-check';
22
+ model: string;
23
+ receivedAt: string;
24
+ runtimeId: string;
25
+ decision: RequestGateDiagnostic;
26
+ route: RouteDiagnostic;
27
+ };
28
+
29
+ export type AgentRequestGatewayDiagnostic = {
30
+ kind: 'agent-request-gateway';
31
+ requestId: string;
32
+ requestType: 'agent';
33
+ source: 'cursor-agent-exec' | 'manual-check' | 'unknown';
34
+ model: string;
35
+ receivedAt: string;
36
+ runtimeId: string;
37
+ decision: GatewayDiagnostic;
38
+ forward: ForwardDiagnostic;
39
+ };
40
+
41
+ export type DiagnosticEntry =
42
+ | AgentCanaryDiagnostic
43
+ | AgentRequestCheckDiagnostic
44
+ | AgentRequestGatewayDiagnostic;
45
+
46
+ export type RequestGateDiagnostic =
47
+ | { state: 'allowed'; productId: string; modeStartedAt: string }
48
+ | { state: 'blocked'; reason: string; releaseReason?: string; releasedAt?: string }
49
+ | { state: 'unknown' };
50
+
51
+ export type RouteDiagnostic =
52
+ | { state: 'ready'; expiresAt: string }
53
+ | { state: 'expired'; expiresAt: string }
54
+ | { state: 'missing' }
55
+ | { state: 'unknown' };
56
+
57
+ export type GatewayDiagnostic =
58
+ | { state: 'accepted'; productId: string; modeStartedAt: string; route: RouteDiagnostic }
59
+ | { state: 'route-missing'; productId: string; modeStartedAt: string; route: RouteDiagnostic }
60
+ | { state: 'route-expired'; productId: string; modeStartedAt: string; route: RouteDiagnostic }
61
+ | {
62
+ state: 'blocked';
63
+ reason: string;
64
+ releaseReason?: string;
65
+ releasedAt?: string;
66
+ route: { state: 'missing' };
67
+ }
68
+ | { state: 'unknown' };
69
+
70
+ export type ForwardDiagnostic =
71
+ | { state: 'skipped'; reason: 'not-accepted' }
72
+ | { state: 'forwarded'; upstreamRequestId: string; acceptedAt: string }
73
+ | { state: 'rejected'; reason: string; retryAfterMs?: number }
74
+ | { state: 'not-configured' }
75
+ | { state: 'timeout' }
76
+ | { state: 'network-error' }
77
+ | { state: 'unknown' };
78
+
79
+ export type DiagnosticsOptions = {
80
+ diagnosticsFile?: string;
81
+ limit?: number;
82
+ json?: boolean;
83
+ };
84
+
85
+ export type ClearDiagnosticsOptions = {
86
+ diagnosticsFile?: string;
87
+ };
88
+
89
+ const DEFAULT_LIMIT = 10;
90
+ const MAX_FIELD_LENGTH = 256;
91
+
92
+ function resolveDiagnosticsFile(diagnosticsFile = DEFAULT_DIAGNOSTICS_FILE) {
93
+ if (diagnosticsFile.startsWith('~/')) {
94
+ return join(homedir(), diagnosticsFile.slice(2));
95
+ }
96
+
97
+ return diagnosticsFile;
98
+ }
99
+
100
+ function assertPositiveLimit(limit: number) {
101
+ if (!Number.isInteger(limit) || limit <= 0) {
102
+ throw new Error('--limit must be a positive integer');
103
+ }
104
+ }
105
+
106
+ function isSafeDiagnosticString(value: unknown): value is string {
107
+ return (
108
+ typeof value === 'string' &&
109
+ value.length > 0 &&
110
+ value.length <= MAX_FIELD_LENGTH &&
111
+ !/[\u0000-\u001f\u007f]/.test(value)
112
+ );
113
+ }
114
+
115
+ function isSafeTimestamp(value: unknown): value is string {
116
+ return isSafeDiagnosticString(value) && !Number.isNaN(Date.parse(value));
117
+ }
118
+
119
+ function isSafeGateToken(value: unknown): value is string {
120
+ return isSafeDiagnosticString(value) && !/[\s=]/.test(value);
121
+ }
122
+
123
+ function isSafeGateTimestamp(value: unknown): value is string {
124
+ return isSafeTimestamp(value) && isSafeGateToken(value);
125
+ }
126
+
127
+ function isSafeMetadataToken(value: unknown): value is string {
128
+ return isSafeGateToken(value) && !isSecretLike(value);
129
+ }
130
+
131
+ function isSecretLike(value: string) {
132
+ return /secret|token|key|authorization|cookie|cursor/i.test(value);
133
+ }
134
+
135
+ function sanitizeRequestCheckModel(value: unknown) {
136
+ if (!isSafeDiagnosticString(value) || isSecretLike(value)) {
137
+ return 'unknown';
138
+ }
139
+
140
+ return value;
141
+ }
142
+
143
+ function sanitizeGateDiagnostic(value: unknown): RequestGateDiagnostic {
144
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
145
+ return { state: 'unknown' };
146
+ }
147
+
148
+ const gate = value as Record<string, unknown>;
149
+ if (gate.state === 'allowed') {
150
+ if (!isSafeGateToken(gate.productId) || !isSafeGateTimestamp(gate.modeStartedAt)) {
151
+ return { state: 'unknown' };
152
+ }
153
+
154
+ return {
155
+ state: 'allowed',
156
+ productId: gate.productId,
157
+ modeStartedAt: gate.modeStartedAt,
158
+ };
159
+ }
160
+
161
+ if (gate.state === 'blocked') {
162
+ if (!isSafeGateToken(gate.reason)) {
163
+ return { state: 'unknown' };
164
+ }
165
+
166
+ return {
167
+ state: 'blocked',
168
+ reason: gate.reason,
169
+ ...(isSafeGateToken(gate.releaseReason)
170
+ ? { releaseReason: gate.releaseReason }
171
+ : {}),
172
+ ...(isSafeGateTimestamp(gate.releasedAt) ? { releasedAt: gate.releasedAt } : {}),
173
+ };
174
+ }
175
+
176
+ return { state: 'unknown' };
177
+ }
178
+
179
+ function sanitizeRouteDiagnostic(value: unknown): RouteDiagnostic {
180
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
181
+ return { state: 'unknown' };
182
+ }
183
+
184
+ const route = value as Record<string, unknown>;
185
+ if (route.state === 'missing') {
186
+ return { state: 'missing' };
187
+ }
188
+
189
+ if (route.state === 'ready' || route.state === 'expired') {
190
+ if (!isSafeGateTimestamp(route.expiresAt)) {
191
+ return { state: 'unknown' };
192
+ }
193
+
194
+ return {
195
+ state: route.state,
196
+ expiresAt: route.expiresAt,
197
+ };
198
+ }
199
+
200
+ return { state: 'unknown' };
201
+ }
202
+
203
+ function sanitizeGatewayDiagnostic(value: unknown): GatewayDiagnostic {
204
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
205
+ return { state: 'unknown' };
206
+ }
207
+
208
+ const decision = value as Record<string, unknown>;
209
+ if (
210
+ decision.state === 'accepted' ||
211
+ decision.state === 'route-missing' ||
212
+ decision.state === 'route-expired'
213
+ ) {
214
+ if (!isSafeGateToken(decision.productId) || !isSafeGateTimestamp(decision.modeStartedAt)) {
215
+ return { state: 'unknown' };
216
+ }
217
+
218
+ const route = sanitizeRouteDiagnostic(decision.route);
219
+ if (decision.state === 'accepted' && route.state === 'ready') {
220
+ return {
221
+ state: 'accepted',
222
+ productId: decision.productId,
223
+ modeStartedAt: decision.modeStartedAt,
224
+ route,
225
+ };
226
+ }
227
+
228
+ if (decision.state === 'route-missing' && route.state === 'missing') {
229
+ return {
230
+ state: 'route-missing',
231
+ productId: decision.productId,
232
+ modeStartedAt: decision.modeStartedAt,
233
+ route,
234
+ };
235
+ }
236
+
237
+ if (decision.state === 'route-expired' && route.state === 'expired') {
238
+ return {
239
+ state: 'route-expired',
240
+ productId: decision.productId,
241
+ modeStartedAt: decision.modeStartedAt,
242
+ route,
243
+ };
244
+ }
245
+
246
+ return { state: 'unknown' };
247
+ }
248
+
249
+ if (decision.state === 'blocked') {
250
+ const blocked = sanitizeGateDiagnostic(decision);
251
+ if (blocked.state === 'blocked') {
252
+ return { ...blocked, route: { state: 'missing' } };
253
+ }
254
+ }
255
+
256
+ return { state: 'unknown' };
257
+ }
258
+
259
+ function sanitizeForwardDiagnostic(value: unknown): ForwardDiagnostic {
260
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
261
+ return { state: 'unknown' };
262
+ }
263
+
264
+ const forward = value as Record<string, unknown>;
265
+ if (forward.state === 'skipped') {
266
+ return forward.reason === 'not-accepted' ? { state: 'skipped', reason: 'not-accepted' } : { state: 'unknown' };
267
+ }
268
+
269
+ if (forward.state === 'forwarded') {
270
+ if (!isSafeGateToken(forward.upstreamRequestId) || !isSafeGateTimestamp(forward.acceptedAt)) {
271
+ return { state: 'unknown' };
272
+ }
273
+ return {
274
+ state: 'forwarded',
275
+ upstreamRequestId: forward.upstreamRequestId,
276
+ acceptedAt: forward.acceptedAt,
277
+ };
278
+ }
279
+
280
+ if (forward.state === 'rejected') {
281
+ if (!isSafeGateToken(forward.reason)) {
282
+ return { state: 'unknown' };
283
+ }
284
+ const retryAfterMs = forward.retryAfterMs;
285
+ return {
286
+ state: 'rejected',
287
+ reason: forward.reason,
288
+ ...(Number.isInteger(retryAfterMs) && retryAfterMs >= 0 && retryAfterMs <= 86_400_000
289
+ ? { retryAfterMs }
290
+ : {}),
291
+ };
292
+ }
293
+
294
+ if (forward.state === 'not-configured' || forward.state === 'timeout' || forward.state === 'network-error') {
295
+ return { state: forward.state };
296
+ }
297
+
298
+ return { state: 'unknown' };
299
+ }
300
+
301
+ function sanitizeRequestCheckDiagnostic(record: Record<string, unknown>): AgentRequestCheckDiagnostic | null {
302
+ if (
303
+ !isSafeMetadataToken(record.requestId) ||
304
+ record.requestType !== 'agent' ||
305
+ (record.source !== 'cursor-agent-exec' && record.source !== 'manual-check') ||
306
+ !isSafeTimestamp(record.receivedAt) ||
307
+ !isSafeMetadataToken(record.runtimeId)
308
+ ) {
309
+ return null;
310
+ }
311
+
312
+ return {
313
+ kind: 'agent-request-check',
314
+ requestId: record.requestId,
315
+ requestType: 'agent',
316
+ source: record.source,
317
+ model: sanitizeRequestCheckModel(record.model),
318
+ receivedAt: record.receivedAt,
319
+ runtimeId: record.runtimeId,
320
+ decision: sanitizeGateDiagnostic(record.decision),
321
+ route: sanitizeRouteDiagnostic(record.route),
322
+ };
323
+ }
324
+
325
+ function sanitizeRequestGatewayDiagnostic(record: Record<string, unknown>): AgentRequestGatewayDiagnostic | null {
326
+ if (
327
+ !isSafeMetadataToken(record.requestId) ||
328
+ record.requestType !== 'agent' ||
329
+ (record.source !== 'cursor-agent-exec' &&
330
+ record.source !== 'manual-check' &&
331
+ record.source !== 'unknown') ||
332
+ !isSafeTimestamp(record.receivedAt) ||
333
+ !isSafeMetadataToken(record.runtimeId)
334
+ ) {
335
+ return null;
336
+ }
337
+
338
+ return {
339
+ kind: 'agent-request-gateway',
340
+ requestId: record.requestId,
341
+ requestType: 'agent',
342
+ source: record.source,
343
+ model: sanitizeRequestCheckModel(record.model),
344
+ receivedAt: record.receivedAt,
345
+ runtimeId: record.runtimeId,
346
+ decision: sanitizeGatewayDiagnostic(record.decision),
347
+ forward: sanitizeForwardDiagnostic(record.forward),
348
+ };
349
+ }
350
+
351
+ function sanitizeCanaryDiagnostic(record: Record<string, unknown>): AgentCanaryDiagnostic | null {
352
+ if (
353
+ !isSafeMetadataToken(record.requestId) ||
354
+ record.requestType !== 'agent' ||
355
+ record.source !== 'cursor-agent-exec' ||
356
+ !isSafeTimestamp(record.receivedAt) ||
357
+ !isSafeMetadataToken(record.runtimeId)
358
+ ) {
359
+ return null;
360
+ }
361
+
362
+ return {
363
+ kind: 'agent-canary',
364
+ requestId: record.requestId,
365
+ requestType: 'agent',
366
+ source: 'cursor-agent-exec',
367
+ model: sanitizeRequestCheckModel(record.model),
368
+ receivedAt: record.receivedAt,
369
+ runtimeId: record.runtimeId,
370
+ gate: sanitizeGateDiagnostic(record.gate),
371
+ };
372
+ }
373
+
374
+ function sanitizeDiagnostic(input: unknown): DiagnosticEntry | null {
375
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
376
+ return null;
377
+ }
378
+
379
+ const record = input as Record<string, unknown>;
380
+
381
+ if (record.kind === 'agent-request-check') {
382
+ return sanitizeRequestCheckDiagnostic(record);
383
+ }
384
+
385
+ if (record.kind === 'agent-request-gateway') {
386
+ return sanitizeRequestGatewayDiagnostic(record);
387
+ }
388
+
389
+ return sanitizeCanaryDiagnostic(record);
390
+ }
391
+
392
+ async function readDiagnosticsEntries(diagnosticsFile: string) {
393
+ try {
394
+ const content = await readFile(diagnosticsFile, 'utf8');
395
+
396
+ return content
397
+ .split('\n')
398
+ .map((line) => line.trim())
399
+ .filter(Boolean)
400
+ .flatMap((line) => {
401
+ try {
402
+ const diagnostic = sanitizeDiagnostic(JSON.parse(line));
403
+ return diagnostic ? [diagnostic] : [];
404
+ } catch {
405
+ return [];
406
+ }
407
+ });
408
+ } catch (error) {
409
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
410
+ return null;
411
+ }
412
+
413
+ throw error;
414
+ }
415
+ }
416
+
417
+ function formatHuman(entries: DiagnosticEntry[]) {
418
+ if (entries.length === 0) {
419
+ return 'diagnostics: empty';
420
+ }
421
+
422
+ return [
423
+ `diagnostics: ${entries.length} ${entries.length === 1 ? 'entry' : 'entries'}`,
424
+ ...entries.map((entry) => {
425
+ const state =
426
+ entry.kind === 'agent-canary'
427
+ ? formatGate(entry.gate)
428
+ : entry.kind === 'agent-request-check'
429
+ ? `${formatDecision(entry.decision)} ${formatRoute(entry.route)}`
430
+ : `${formatGateway(entry.decision)} ${formatForward(entry.forward)}`;
431
+ return `${entry.receivedAt} kind=${entry.kind} requestId=${entry.requestId} source=${entry.source} requestType=${entry.requestType} model=${entry.model} runtimeId=${entry.runtimeId} ${state}`;
432
+ }),
433
+ ].join('\n');
434
+ }
435
+
436
+ function formatGate(gate: RequestGateDiagnostic) {
437
+ if (gate.state === 'allowed') {
438
+ return `gate=allowed productId=${gate.productId}`;
439
+ }
440
+
441
+ if (gate.state === 'blocked') {
442
+ return [
443
+ `gate=blocked reason=${gate.reason}`,
444
+ ...(gate.releaseReason !== undefined ? [`releaseReason=${gate.releaseReason}`] : []),
445
+ ...(gate.releasedAt !== undefined ? [`releasedAt=${gate.releasedAt}`] : []),
446
+ ].join(' ');
447
+ }
448
+
449
+ return 'gate=unknown';
450
+ }
451
+
452
+ function formatDecision(decision: RequestGateDiagnostic) {
453
+ if (decision.state === 'allowed') {
454
+ return `decision=allowed productId=${decision.productId}`;
455
+ }
456
+
457
+ if (decision.state === 'blocked') {
458
+ return [
459
+ `decision=blocked reason=${decision.reason}`,
460
+ ...(decision.releaseReason !== undefined ? [`releaseReason=${decision.releaseReason}`] : []),
461
+ ...(decision.releasedAt !== undefined ? [`releasedAt=${decision.releasedAt}`] : []),
462
+ ].join(' ');
463
+ }
464
+
465
+ return 'decision=unknown';
466
+ }
467
+
468
+ function formatRoute(route: RouteDiagnostic) {
469
+ if (route.state === 'ready' || route.state === 'expired') {
470
+ return `route=${route.state} expiresAt=${route.expiresAt}`;
471
+ }
472
+
473
+ return `route=${route.state}`;
474
+ }
475
+
476
+ function formatGateway(decision: GatewayDiagnostic) {
477
+ if (
478
+ decision.state === 'accepted' ||
479
+ decision.state === 'route-missing' ||
480
+ decision.state === 'route-expired'
481
+ ) {
482
+ return [
483
+ `decision=${decision.state}`,
484
+ `productId=${decision.productId}`,
485
+ formatRoute(decision.route),
486
+ ].join(' ');
487
+ }
488
+
489
+ if (decision.state === 'blocked') {
490
+ return [
491
+ `decision=blocked reason=${decision.reason}`,
492
+ ...(decision.releaseReason !== undefined ? [`releaseReason=${decision.releaseReason}`] : []),
493
+ ...(decision.releasedAt !== undefined ? [`releasedAt=${decision.releasedAt}`] : []),
494
+ 'route=missing',
495
+ ].join(' ');
496
+ }
497
+
498
+ return 'decision=unknown';
499
+ }
500
+
501
+ function formatForward(forward: ForwardDiagnostic) {
502
+ if (forward.state === 'skipped') {
503
+ return 'forward=skipped reason=not-accepted';
504
+ }
505
+
506
+ if (forward.state === 'forwarded') {
507
+ return `forward=forwarded upstreamRequestId=${forward.upstreamRequestId} acceptedAt=${forward.acceptedAt}`;
508
+ }
509
+
510
+ if (forward.state === 'rejected') {
511
+ return [
512
+ `forward=rejected reason=${forward.reason}`,
513
+ ...(forward.retryAfterMs !== undefined ? [`retryAfterMs=${forward.retryAfterMs}`] : []),
514
+ ].join(' ');
515
+ }
516
+
517
+ return `forward=${forward.state}`;
518
+ }
519
+
520
+ export async function diagnostics(options: DiagnosticsOptions = {}) {
521
+ const limit = options.limit ?? DEFAULT_LIMIT;
522
+ assertPositiveLimit(limit);
523
+
524
+ const diagnosticsFile = resolveDiagnosticsFile(options.diagnosticsFile);
525
+ const entries = await readDiagnosticsEntries(diagnosticsFile);
526
+
527
+ if (entries === null) {
528
+ return options.json ? '[]' : 'diagnostics: missing';
529
+ }
530
+
531
+ const limitedEntries = entries.slice(-limit).map((entry) => sanitizeDiagnostic(entry));
532
+ const sanitizedEntries = limitedEntries.filter(
533
+ (entry): entry is DiagnosticEntry => entry !== null,
534
+ );
535
+
536
+ if (options.json) {
537
+ return JSON.stringify(sanitizedEntries, null, 2);
538
+ }
539
+
540
+ return formatHuman(sanitizedEntries);
541
+ }
542
+
543
+ export async function clearDiagnostics(options: ClearDiagnosticsOptions = {}) {
544
+ const diagnosticsFile = resolveDiagnosticsFile(options.diagnosticsFile);
545
+
546
+ try {
547
+ await readFile(diagnosticsFile, 'utf8');
548
+ } catch (error) {
549
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
550
+ return 'diagnostics: missing';
551
+ }
552
+
553
+ throw error;
554
+ }
555
+
556
+ await writeFile(diagnosticsFile, '', 'utf8');
557
+ return 'diagnostics: cleared';
558
+ }
@@ -0,0 +1,18 @@
1
+ import { arch, platform } from 'node:process';
2
+
3
+ export type CliEnvironment = {
4
+ platform: NodeJS.Platform;
5
+ arch: string;
6
+ };
7
+
8
+ export type DetectEnvironmentOptions = {
9
+ platform?: NodeJS.Platform;
10
+ arch?: string;
11
+ };
12
+
13
+ export function detectEnvironment(options: DetectEnvironmentOptions = {}): CliEnvironment {
14
+ return {
15
+ platform: options.platform ?? platform,
16
+ arch: options.arch ?? arch,
17
+ };
18
+ }
@@ -0,0 +1,111 @@
1
+ import { cp, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+ import { createRequire } from 'node:module';
5
+ import type { ExtensionState } from './trial';
6
+
7
+ export type BundleExtensionOptions = {
8
+ sourceDir?: string;
9
+ installPath?: string;
10
+ };
11
+
12
+ export type BundleExtensionResult = {
13
+ state: ExtensionState;
14
+ installPath: string;
15
+ };
16
+
17
+ export const DEFAULT_EXTENSION_INSTALL_PATH = '~/.cursor-pool/extensions/cursor-pool-status';
18
+ const require = createRequire(import.meta.url);
19
+ const DEFAULT_EXTENSION_SOURCE_DIR = dirname(require.resolve('@cursor-pool/extension/package.json'));
20
+
21
+ function resolveHomePath(path: string) {
22
+ return path.startsWith('~/') ? join(homedir(), path.slice(2)) : path;
23
+ }
24
+
25
+ export function resolveExtensionInstallPath(
26
+ installPath = DEFAULT_EXTENSION_INSTALL_PATH,
27
+ ) {
28
+ return resolveHomePath(installPath);
29
+ }
30
+
31
+ function assertSafeExtensionBundlePath(installPath: string) {
32
+ const resolvedInstallPath = resolve(installPath);
33
+ const parentSegments = dirname(resolvedInstallPath).split(/[\\/]+/).filter(Boolean);
34
+ const hasExtensionsParent = parentSegments.includes('extensions');
35
+
36
+ if (basename(resolvedInstallPath) !== 'cursor-pool-status' || !hasExtensionsParent) {
37
+ throw new Error(
38
+ `Unsafe extension install path for recursive removal: ${installPath}. Expected an extensions/cursor-pool-status bundle path.`,
39
+ );
40
+ }
41
+ }
42
+
43
+ async function exists(path: string) {
44
+ try {
45
+ await stat(path);
46
+ return true;
47
+ } catch (error) {
48
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
49
+ return false;
50
+ }
51
+ throw error;
52
+ }
53
+ }
54
+
55
+ function buildRuntimeManifest(sourceManifest: Record<string, unknown>) {
56
+ return {
57
+ name: 'cursorpool',
58
+ version: typeof sourceManifest.version === 'string' ? sourceManifest.version : '0.5.6',
59
+ displayName: 'Cursor Pool 平台模式',
60
+ publisher: 'cursor-pool',
61
+ type: sourceManifest.type,
62
+ engines: sourceManifest.engines ?? { vscode: '^1.90.0' },
63
+ activationEvents: sourceManifest.activationEvents,
64
+ main: sourceManifest.main,
65
+ contributes: sourceManifest.contributes,
66
+ };
67
+ }
68
+
69
+ export async function getExtensionState(installPath = DEFAULT_EXTENSION_INSTALL_PATH) {
70
+ const resolvedInstallPath = resolveExtensionInstallPath(installPath);
71
+ const manifestPath = join(resolvedInstallPath, 'package.json');
72
+ const entryPath = join(resolvedInstallPath, 'dist/extension.js');
73
+ return (await exists(manifestPath)) && (await exists(entryPath)) ? 'bundled' : 'missing';
74
+ }
75
+
76
+ export async function bundleExtension(
77
+ options: BundleExtensionOptions = {},
78
+ ): Promise<BundleExtensionResult> {
79
+ const sourceDir = options.sourceDir ?? DEFAULT_EXTENSION_SOURCE_DIR;
80
+ const installPath = resolveExtensionInstallPath(options.installPath);
81
+ const manifestPath = join(sourceDir, 'package.json');
82
+ const entryPath = join(sourceDir, 'dist/extension.js');
83
+ const resourcesPath = join(sourceDir, 'resources');
84
+
85
+ if (!(await exists(manifestPath)) || !(await exists(entryPath))) {
86
+ return { state: 'missing', installPath };
87
+ }
88
+
89
+ assertSafeExtensionBundlePath(installPath);
90
+ await rm(installPath, { recursive: true, force: true });
91
+ await mkdir(installPath, { recursive: true });
92
+ const sourceManifest = JSON.parse(await readFile(manifestPath, 'utf8')) as Record<string, unknown>;
93
+ await writeFile(
94
+ join(installPath, 'package.json'),
95
+ `${JSON.stringify(buildRuntimeManifest(sourceManifest), null, 2)}\n`,
96
+ 'utf8',
97
+ );
98
+ await mkdir(join(installPath, 'dist'), { recursive: true });
99
+ await cp(entryPath, join(installPath, 'dist/extension.js'));
100
+ if (await exists(resourcesPath)) {
101
+ await cp(resourcesPath, join(installPath, 'resources'), { recursive: true });
102
+ }
103
+
104
+ return { state: 'bundled', installPath };
105
+ }
106
+
107
+ export async function removeExtensionBundle(installPath = DEFAULT_EXTENSION_INSTALL_PATH) {
108
+ const resolvedInstallPath = resolveExtensionInstallPath(installPath);
109
+ assertSafeExtensionBundlePath(resolvedInstallPath);
110
+ await rm(resolvedInstallPath, { recursive: true, force: true });
111
+ }