@cursorpool-dev/cli 0.5.6 → 0.5.8

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 (108) hide show
  1. package/node_modules/@cursor-pool/extension/dist/extension.js +116 -46
  2. package/node_modules/@cursor-pool/extension/package.json +3 -3
  3. package/node_modules/@cursor-pool/extension/src/api.ts +17 -2
  4. package/node_modules/@cursor-pool/extension/src/panel.ts +26 -3
  5. package/node_modules/@cursor-pool/extension/test/panel.test.ts +34 -1
  6. package/node_modules/@cursor-pool/patcher/package.json +2 -2
  7. package/node_modules/@cursor-pool/patcher/src/marker.ts +5 -1
  8. package/node_modules/@cursor-pool/patcher/src/workbenchAuthGateMarker.ts +58 -7
  9. package/node_modules/@cursor-pool/patcher/test/patchCursorAgentExec.test.ts +20 -0
  10. package/node_modules/@cursor-pool/patcher/test/patchCursorWorkbench.test.ts +193 -2
  11. package/node_modules/@cursor-pool/service/package.json +2 -2
  12. package/node_modules/@cursor-pool/service/src/platformSession.ts +30 -7
  13. package/node_modules/@cursor-pool/service/src/server.ts +1 -0
  14. package/node_modules/@cursor-pool/service/test/platformSession.test.ts +5 -4
  15. package/node_modules/@cursor-pool/service/test/server.test.ts +130 -0
  16. package/node_modules/@cursor-pool/shared/package.json +1 -1
  17. package/node_modules/@cursor-pool/shared/src/manifest.ts +35 -0
  18. package/node_modules/@cursor-pool/shared/test/manifest.test.ts +43 -9
  19. package/node_modules/@cursor-pool/takeover-plans/package.json +12 -0
  20. package/node_modules/@cursor-pool/takeover-plans/src/index.ts +22 -0
  21. package/node_modules/@cursor-pool/takeover-plans/src/plans.ts +37 -0
  22. package/node_modules/@cursor-pool/takeover-plans/src/types.ts +9 -0
  23. package/node_modules/@cursor-pool/takeover-plans/test/registry.test.ts +23 -0
  24. package/node_modules/@esbuild/linux-x64/README.md +3 -0
  25. package/node_modules/@esbuild/linux-x64/bin/esbuild +0 -0
  26. package/node_modules/@esbuild/linux-x64/package.json +20 -0
  27. package/node_modules/esbuild/LICENSE.md +21 -0
  28. package/node_modules/esbuild/README.md +3 -0
  29. package/node_modules/esbuild/bin/esbuild +223 -0
  30. package/node_modules/esbuild/install.js +300 -0
  31. package/node_modules/esbuild/lib/main.d.ts +716 -0
  32. package/node_modules/esbuild/lib/main.js +2532 -0
  33. package/node_modules/esbuild/package.json +74 -0
  34. package/node_modules/tsx/LICENSE +21 -0
  35. package/node_modules/tsx/README.md +32 -0
  36. package/node_modules/tsx/dist/cjs/api/index.cjs +1 -0
  37. package/node_modules/tsx/dist/cjs/api/index.d.cts +35 -0
  38. package/node_modules/tsx/dist/cjs/api/index.d.mts +35 -0
  39. package/node_modules/tsx/dist/cjs/api/index.mjs +1 -0
  40. package/node_modules/tsx/dist/cjs/index.cjs +1 -0
  41. package/node_modules/tsx/dist/cjs/index.mjs +1 -0
  42. package/node_modules/tsx/dist/cli.cjs +54 -0
  43. package/node_modules/tsx/dist/cli.mjs +55 -0
  44. package/node_modules/tsx/dist/client-D3mGB526.cjs +1 -0
  45. package/node_modules/tsx/dist/client-D_mPDF5S.mjs +1 -0
  46. package/node_modules/tsx/dist/esm/api/index.cjs +1 -0
  47. package/node_modules/tsx/dist/esm/api/index.d.cts +35 -0
  48. package/node_modules/tsx/dist/esm/api/index.d.mts +35 -0
  49. package/node_modules/tsx/dist/esm/api/index.mjs +1 -0
  50. package/node_modules/tsx/dist/esm/index.cjs +1 -0
  51. package/node_modules/tsx/dist/esm/index.mjs +1 -0
  52. package/node_modules/tsx/dist/get-pipe-path-D4YM6rQt.cjs +1 -0
  53. package/node_modules/tsx/dist/get-pipe-path-_tAJyU_v.mjs +1 -0
  54. package/node_modules/tsx/dist/index-BWFBUo6r.cjs +1 -0
  55. package/node_modules/tsx/dist/index-D9F1FXzN.cjs +14 -0
  56. package/node_modules/tsx/dist/index-XurvG3JN.mjs +14 -0
  57. package/node_modules/tsx/dist/index-gbaejti9.mjs +1 -0
  58. package/node_modules/tsx/dist/lexer-DQCqS3nf.mjs +3 -0
  59. package/node_modules/tsx/dist/lexer-DgIbo0BU.cjs +3 -0
  60. package/node_modules/tsx/dist/loader.cjs +1 -0
  61. package/node_modules/tsx/dist/loader.mjs +1 -0
  62. package/node_modules/tsx/dist/node-features-B9BBLzwu.mjs +1 -0
  63. package/node_modules/tsx/dist/node-features-CQLdkVE6.cjs +1 -0
  64. package/node_modules/tsx/dist/package-CGdS2_oX.cjs +1 -0
  65. package/node_modules/tsx/dist/package-DyJMwVU5.mjs +1 -0
  66. package/node_modules/tsx/dist/patch-repl.cjs +1 -0
  67. package/node_modules/tsx/dist/patch-repl.mjs +1 -0
  68. package/node_modules/tsx/dist/preflight.cjs +1 -0
  69. package/node_modules/tsx/dist/preflight.mjs +1 -0
  70. package/node_modules/tsx/dist/register-BOkp8V6j.cjs +10 -0
  71. package/node_modules/tsx/dist/register-BnTWPeIB.mjs +10 -0
  72. package/node_modules/tsx/dist/register-CHVGxKtC.cjs +2 -0
  73. package/node_modules/tsx/dist/register-D_B8UL5H.mjs +2 -0
  74. package/node_modules/tsx/dist/repl.cjs +3 -0
  75. package/node_modules/tsx/dist/repl.mjs +3 -0
  76. package/node_modules/tsx/dist/require-CjvaJWEr.cjs +1 -0
  77. package/node_modules/tsx/dist/require-DzmC1hVr.mjs +1 -0
  78. package/node_modules/tsx/dist/suppress-warnings.cjs +1 -0
  79. package/node_modules/tsx/dist/suppress-warnings.mjs +1 -0
  80. package/node_modules/tsx/dist/temporary-directory-B83uKxJF.cjs +1 -0
  81. package/node_modules/tsx/dist/temporary-directory-BDDVQOvU.mjs +1 -0
  82. package/node_modules/tsx/dist/types-Cxp8y2TL.d.ts +5 -0
  83. package/node_modules/tsx/package.json +67 -0
  84. package/package.json +11 -6
  85. package/src/autostart.ts +5 -1
  86. package/src/compat.ts +193 -47
  87. package/src/cursor.ts +59 -3
  88. package/src/extensionBundle.ts +1 -1
  89. package/src/extensionLink.ts +28 -7
  90. package/src/install.ts +176 -24
  91. package/src/installRecord.ts +2 -0
  92. package/src/patchSet.ts +12 -6
  93. package/src/platform.ts +3 -3
  94. package/src/repair.ts +10 -1
  95. package/src/restore.ts +12 -4
  96. package/src/serviceProcess.ts +2 -1
  97. package/src/status.ts +6 -0
  98. package/src/trial.ts +1 -0
  99. package/test/autostart.test.ts +23 -2
  100. package/test/compat.test.ts +238 -3
  101. package/test/cursor-pool-bin.test.ts +1 -0
  102. package/test/cursor.test.ts +60 -1
  103. package/test/e2e-install.test.ts +53 -0
  104. package/test/extensionLink.test.ts +48 -2
  105. package/test/install.test.ts +191 -6
  106. package/test/repair.test.ts +1 -0
  107. package/test/serviceProcess.test.ts +10 -1
  108. package/test/status.test.ts +1 -0
@@ -15,12 +15,34 @@ import {
15
15
  } from '../src/compat';
16
16
 
17
17
  function remoteRuleFor(cursorVersion: string, cursorCommit: string): CompatibilityManifestEntry {
18
+ const cursorMajor = cursorVersion.replace(/^(\d+\.\d+).*/, '$1');
19
+ const planByMajor: Record<string, string> = {
20
+ '3.5': 'cursor-3.5-mac-agent-f-workbench-p-l0',
21
+ '3.6': 'cursor-3.6-mac-agent-c-workbench-h-uv',
22
+ '3.7': 'cursor-3.7-mac-agent-et-workbench-wv',
23
+ };
18
24
  return {
19
25
  platform: 'darwin',
20
26
  arch: 'arm64',
21
- cursorVersion,
27
+ officialSourceUrl: 'https://cursor.com/download',
28
+ officialDownloadUrl: `https://api2.cursor.sh/updates/download/golden/darwin-arm64/cursor/${cursorVersion.replace(/^(\d+\.\d+).*/, '$1')}`,
29
+ officialDownloadPlatform: 'darwin-arm64',
30
+ verifiedCursorVersion: cursorVersion,
31
+ cursorVersion: cursorMajor,
22
32
  cursorCommit,
23
33
  supportStatus: 'supported',
34
+ adapterVersion: '0.5.8',
35
+ takeoverPlanId: planByMajor[cursorMajor] ?? `cursor-${cursorMajor}-remote-plan`,
36
+ structureFamily: 'mac-agent-c-workbench-h-uv',
37
+ patchTargets: [
38
+ {
39
+ name: 'agent-exec',
40
+ targetRelativePath: CURSOR_AGENT_EXEC_RELATIVE_PATH,
41
+ expectedSha256: 'remote-sha256',
42
+ patchStrategy: 'cursor-agent-exec-snippet',
43
+ verifyMarker: 'cursor-pool',
44
+ },
45
+ ],
24
46
  targetRelativePath: CURSOR_AGENT_EXEC_RELATIVE_PATH,
25
47
  expectedSha256: 'remote-sha256',
26
48
  structureSignature: 'remote-structure',
@@ -70,13 +92,161 @@ test('default compatibility manifest supports Cursor 3.6.21 on macOS arm64', ()
70
92
  assert.equal(
71
93
  DEFAULT_COMPAT_ENTRIES.some(
72
94
  (candidate) =>
73
- candidate.cursorVersion === '3.6.21' &&
95
+ candidate.cursorVersion === '3.6' &&
74
96
  candidate.cursorCommit === 'e7a7e93f4d75f8272503ecf33cedbaae10114a10',
75
97
  ),
76
98
  true,
77
99
  );
78
100
  });
79
101
 
102
+ test('default compatibility entries are keyed by official Cursor major download versions only', () => {
103
+ assert.deepEqual(
104
+ Array.from(new Set(DEFAULT_COMPAT_ENTRIES.map((entry) => entry.cursorVersion))).sort(),
105
+ ['3.4', '3.5', '3.6', '3.7'],
106
+ );
107
+ assert.equal(
108
+ DEFAULT_COMPAT_ENTRIES.every((entry) =>
109
+ entry.officialDownloadUrl?.startsWith(
110
+ `https://api2.cursor.sh/updates/download/golden/${entry.officialDownloadPlatform}/cursor/${entry.cursorVersion}`,
111
+ ),
112
+ ),
113
+ true,
114
+ );
115
+ assert.equal(
116
+ DEFAULT_COMPAT_ENTRIES.some((entry) => entry.officialSourceUrl !== 'https://cursor.com/download'),
117
+ false,
118
+ );
119
+ assert.equal(
120
+ DEFAULT_COMPAT_ENTRIES.some((entry) => entry.platform === 'linux' || entry.platform === 'win32'),
121
+ false,
122
+ );
123
+ });
124
+
125
+ test('default compatibility manifest supports Cursor 3.4.20 on macOS arm64 with the v0.4 adapter', () => {
126
+ const entry = resolveCompatEntry(
127
+ {
128
+ appPath: '/Applications/Cursor.app',
129
+ version: '3.4.20',
130
+ commit: '0cf8b06883f54e26bb4f0fb8647c9500ccb43310',
131
+ },
132
+ {
133
+ platform: 'darwin',
134
+ arch: 'arm64',
135
+ nodeVersion: process.version,
136
+ },
137
+ { adapterVersion: '0.4.8' },
138
+ );
139
+
140
+ assert.equal(entry.supportStatus, 'supported');
141
+ assert.equal(entry.adapterVersion, '0.4.8');
142
+ assert.equal(entry.cursorVersion, '3.4');
143
+ assert.equal(entry.structureFamily, 'mac-agent-D-workbench-U0');
144
+ assert.equal(entry.patchTargets?.map((target) => target.name).join(','), 'agent-exec,workbench');
145
+ });
146
+
147
+ test('default compatibility manifest does not match Cursor 3.4.20 from the v0.5 adapter', () => {
148
+ assert.throws(
149
+ () =>
150
+ resolveCompatEntry(
151
+ {
152
+ appPath: '/Applications/Cursor.app',
153
+ version: '3.4.20',
154
+ commit: '0cf8b06883f54e26bb4f0fb8647c9500ccb43310',
155
+ },
156
+ {
157
+ platform: 'darwin',
158
+ arch: 'arm64',
159
+ nodeVersion: process.version,
160
+ },
161
+ { adapterVersion: '0.5.8' },
162
+ ),
163
+ /No compatibility entry for Cursor 3\.4\.20/,
164
+ );
165
+ });
166
+
167
+ test('default compatibility manifest supports Cursor 3.6.31 on macOS arm64 with the v0.5 adapter', () => {
168
+ const entry = resolveCompatEntry(
169
+ {
170
+ appPath: '/Applications/Cursor.app',
171
+ version: '3.6.31',
172
+ commit: '81fcf2931d7687b4ff3f3017858d0c6dee7e2a60',
173
+ },
174
+ {
175
+ platform: 'darwin',
176
+ arch: 'arm64',
177
+ nodeVersion: process.version,
178
+ },
179
+ { adapterVersion: '0.5.8' },
180
+ );
181
+
182
+ assert.equal(entry.supportStatus, 'supported');
183
+ assert.equal(entry.adapterVersion, '0.5.8');
184
+ assert.equal(entry.cursorVersion, '3.6');
185
+ assert.equal(entry.structureFamily, 'mac-agent-c-workbench-h-uv');
186
+ assert.equal(entry.patchTargets?.map((target) => target.name).join(','), 'agent-exec,workbench');
187
+ });
188
+
189
+ test('default compatibility manifest supports Cursor 3.7.12 on macOS arm64 with the v0.6 adapter', () => {
190
+ const entry = resolveCompatEntry(
191
+ {
192
+ appPath: '/Applications/Cursor.app',
193
+ version: '3.7.12',
194
+ commit: 'b887a26c4f70bd8136bfffeda812b24194ec9ce0',
195
+ },
196
+ {
197
+ platform: 'darwin',
198
+ arch: 'arm64',
199
+ nodeVersion: process.version,
200
+ },
201
+ { adapterVersion: '0.6.0' },
202
+ );
203
+
204
+ assert.equal(entry.supportStatus, 'supported');
205
+ assert.equal(entry.adapterVersion, '0.6.0');
206
+ assert.equal(entry.cursorVersion, '3.7');
207
+ assert.equal(entry.structureFamily, 'mac-3.7-agent-Et-workbench-wv');
208
+ assert.equal(entry.patchTargets?.map((target) => target.name).join(','), 'agent-exec,workbench');
209
+ });
210
+
211
+ test('default compatibility manifest does not match Cursor 3.7.12 from the v0.5 adapter', () => {
212
+ assert.throws(
213
+ () =>
214
+ resolveCompatEntry(
215
+ {
216
+ appPath: '/Applications/Cursor.app',
217
+ version: '3.7.12',
218
+ commit: 'b887a26c4f70bd8136bfffeda812b24194ec9ce0',
219
+ },
220
+ {
221
+ platform: 'darwin',
222
+ arch: 'arm64',
223
+ nodeVersion: process.version,
224
+ },
225
+ { adapterVersion: '0.5.8' },
226
+ ),
227
+ /No compatibility entry for Cursor 3\.7\.12/,
228
+ );
229
+ });
230
+
231
+ test('default compatibility manifest does not claim unverified Linux Cursor packages as supported', () => {
232
+ assert.throws(
233
+ () =>
234
+ resolveCompatEntry(
235
+ {
236
+ appPath: '/home/xubuntu/.cursor-pool/appimages/Cursor-89aa899ccc46fb58/squashfs-root',
237
+ version: '3.6.31',
238
+ commit: '81fcf2931d7687b4ff3f3017858d0c6dee7e2a60',
239
+ },
240
+ {
241
+ platform: 'linux',
242
+ arch: 'x64',
243
+ nodeVersion: process.version,
244
+ },
245
+ ),
246
+ /No compatibility entry for Cursor 3\.6\.31/,
247
+ );
248
+ });
249
+
80
250
  test('default compatibility manifest supports Cursor 3.5.38 on macOS x64 for Rosetta-launched installers', () => {
81
251
  const entry = resolveCompatEntry(
82
252
  {
@@ -93,6 +263,7 @@ test('default compatibility manifest supports Cursor 3.5.38 on macOS x64 for Ros
93
263
 
94
264
  assert.equal(entry.supportStatus, 'supported');
95
265
  assert.equal(entry.requiresAdHocResign, true);
266
+ assert.equal(entry.cursorVersion, '3.5');
96
267
  assert.equal(entry.targetRelativePath, CURSOR_AGENT_EXEC_RELATIVE_PATH);
97
268
  assert.equal(
98
269
  entry.expectedSha256,
@@ -146,7 +317,8 @@ test('loads remote compatibility entries from api base url when signature is val
146
317
  },
147
318
  });
148
319
 
149
- assert.equal(entries[0]?.cursorVersion, '3.7.0');
320
+ assert.equal(entries[0]?.cursorVersion, '3.7');
321
+ assert.equal(entries[0]?.verifiedCursorVersion, '3.7.0');
150
322
  });
151
323
 
152
324
  test('loads compatibility entries from a local file URL for disposable client validation', async () => {
@@ -190,3 +362,66 @@ test('falls back to injected compatibility entries before fetching remote manife
190
362
 
191
363
  assert.equal(entries, injected);
192
364
  });
365
+
366
+ test('resolveCompatEntry only matches the current adapter version when provided', () => {
367
+ const cursor = {
368
+ appPath: '/Applications/Cursor.app',
369
+ version: '3.7.12',
370
+ commit: 'b887a26c4f70bd8136bfffeda812b24194ec9ce0',
371
+ };
372
+ const environment = {
373
+ platform: 'darwin' as const,
374
+ arch: 'arm64',
375
+ nodeVersion: process.version,
376
+ };
377
+ const matchingRule = remoteRuleFor(cursor.version, cursor.commit);
378
+ const otherAdapterRule = {
379
+ ...matchingRule,
380
+ adapterVersion: '0.6.0',
381
+ userMessage: 'Cursor 3.7.12 needs the 0.6 adapter.',
382
+ };
383
+
384
+ assert.throws(
385
+ () =>
386
+ resolveCompatEntry(cursor, environment, {
387
+ entries: [otherAdapterRule],
388
+ adapterVersion: '0.5.8',
389
+ }),
390
+ /No compatibility entry for Cursor 3\.7\.12/,
391
+ );
392
+
393
+ const resolved = resolveCompatEntry(cursor, environment, {
394
+ entries: [otherAdapterRule],
395
+ adapterVersion: '0.6.0',
396
+ });
397
+ assert.equal(resolved.adapterVersion, '0.6.0');
398
+ });
399
+
400
+ test('resolveCompatEntry rejects legacy rules without adapterVersion when current adapter is known', () => {
401
+ const cursor = {
402
+ appPath: '/Applications/Cursor.app',
403
+ version: '3.6.21',
404
+ commit: 'e7a7e93f4d75f8272503ecf33cedbaae10114a10',
405
+ };
406
+ const environment = {
407
+ platform: 'darwin' as const,
408
+ arch: 'arm64',
409
+ nodeVersion: process.version,
410
+ };
411
+ const { adapterVersion: _adapterVersion, ...legacyRule } = remoteRuleFor(cursor.version, cursor.commit);
412
+
413
+ assert.throws(
414
+ () =>
415
+ resolveCompatEntry(cursor, environment, {
416
+ entries: [legacyRule],
417
+ adapterVersion: '0.5.8',
418
+ }),
419
+ /No compatibility entry for Cursor 3\.6\.21/,
420
+ );
421
+
422
+ const resolved = resolveCompatEntry(cursor, environment, {
423
+ entries: [legacyRule],
424
+ });
425
+ assert.equal(resolved.cursorVersion, '3.6');
426
+ assert.equal(resolved.verifiedCursorVersion, '3.6.21');
427
+ });
@@ -59,6 +59,7 @@ test('CLI common options include target, record, trial, extension, and service l
59
59
  cursorExtensionsDir: '/tmp/Cursor-Pool-Trial-Extensions',
60
60
  userDataDir: '/tmp/Cursor-Pool-Trial-UserData',
61
61
  serviceLogFile: '/tmp/service.log',
62
+ configFile: undefined,
62
63
  diagnosticsFile: '/tmp/diagnostics.jsonl',
63
64
  sessionFile: '/tmp/session.json',
64
65
  code: 'CODE-123',
@@ -1,6 +1,9 @@
1
1
  import assert from 'node:assert/strict';
2
+ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
2
5
  import test from 'node:test';
3
- import { defaultCursorAppPath } from '../src/cursor';
6
+ import { defaultCursorAppPath, findCursor } from '../src/cursor';
4
7
 
5
8
  test('defaultCursorAppPath resolves the user-level Windows Cursor install directory', () => {
6
9
  assert.equal(
@@ -18,3 +21,59 @@ test('defaultCursorAppPath gives a helpful Windows error when LOCALAPPDATA is un
18
21
  /LOCALAPPDATA is required to auto-detect Cursor on Windows/,
19
22
  );
20
23
  });
24
+
25
+ test('findCursor extracts Linux AppImage files before reading product metadata', async () => {
26
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-linux-appimage-'));
27
+ const appImagePath = join(tempDir, 'Cursor.AppImage');
28
+ const extractedRoots: string[] = [];
29
+
30
+ try {
31
+ await writeFile(appImagePath, '#!/bin/sh\n', { mode: 0o755 });
32
+
33
+ const cursor = await findCursor({
34
+ appPath: appImagePath,
35
+ platform: 'linux',
36
+ appImageExtract: async (path, outputRoot) => {
37
+ assert.equal(path, appImagePath);
38
+ extractedRoots.push(outputRoot);
39
+ const extractedPath = join(outputRoot, 'squashfs-root');
40
+ await mkdir(join(extractedPath, 'usr/share/cursor/resources/app'), { recursive: true });
41
+ await writeFile(
42
+ join(extractedPath, 'usr/share/cursor/resources/app/product.json'),
43
+ JSON.stringify({ version: '3.6.31', commit: 'linux-commit' }),
44
+ 'utf8',
45
+ );
46
+ return extractedPath;
47
+ },
48
+ });
49
+
50
+ assert.equal(cursor.version, '3.6.31');
51
+ assert.equal(cursor.commit, 'linux-commit');
52
+ assert.match(cursor.appPath, /squashfs-root$/);
53
+ assert.equal(extractedRoots.length, 1);
54
+ } finally {
55
+ await rm(tempDir, { recursive: true, force: true });
56
+ }
57
+ });
58
+
59
+ test('findCursor keeps macOS app bundle product path unchanged', async () => {
60
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-macos-app-'));
61
+ const appPath = join(tempDir, 'Cursor.app');
62
+
63
+ try {
64
+ await mkdir(join(appPath, 'Contents/Resources/app'), { recursive: true });
65
+ await writeFile(
66
+ join(appPath, 'Contents/Resources/app/product.json'),
67
+ JSON.stringify({ version: '3.5.38', commit: 'mac-commit' }),
68
+ 'utf8',
69
+ );
70
+
71
+ const cursor = await findCursor({ appPath, platform: 'darwin' });
72
+
73
+ assert.equal(cursor.appPath, appPath);
74
+ assert.equal(cursor.version, '3.5.38');
75
+ assert.equal(cursor.commit, 'mac-commit');
76
+ } finally {
77
+ await rm(tempDir, { recursive: true, force: true });
78
+ }
79
+ });
@@ -13,6 +13,16 @@ import {
13
13
  CURSOR_POOL_PATCH_MARKER,
14
14
  } from '../../patcher/src/marker';
15
15
  import { backupPathForCursorAgentExec } from '../../patcher/src/patchCursorAgentExec';
16
+ import { CURSOR_WORKBENCH_RELATIVE_PATH } from '../../patcher/src/patchCursorWorkbenchAuthGate';
17
+ import {
18
+ CURSOR_WORKBENCH_AGENT_CLIENT_RUN_LOCAL_MODE_ANCHOR,
19
+ CURSOR_WORKBENCH_AGENT_EDITOR_SEND_BUTTON_AUTH_GATE_ANCHOR,
20
+ CURSOR_WORKBENCH_AGENT_EDITOR_SEND_BUTTON_LOGIN_ANCHOR,
21
+ CURSOR_WORKBENCH_AGENT_LOOP_RUN_ANCHOR,
22
+ CURSOR_WORKBENCH_BUILD_FLAGS_LOCAL_MODE_ANCHOR,
23
+ CURSOR_WORKBENCH_COMPOSER_AUTH_GATE_ANCHOR,
24
+ CURSOR_WORKBENCH_COMPOSER_SUBMIT_AUTH_GATE_ANCHOR,
25
+ } from '../../patcher/src/workbenchAuthGateMarker';
16
26
  import type { CompatibilityManifestEntry } from '../../shared/src/manifest';
17
27
  import { getExtensionState } from '../src/extensionBundle';
18
28
  import { getLinkedExtensionState, linkedExtensionPathForDir } from '../src/extensionLink';
@@ -35,30 +45,70 @@ const targetRelativePath =
35
45
  const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../../..');
36
46
  const serviceServerPath = resolve(repoRoot, 'packages/service/src/server.ts');
37
47
 
48
+ function workbenchFixtureContent() {
49
+ const localProviderConfigAnchor =
50
+ 'async getLocalAgentProviderConfig(e){const t="[AgentClientService][getLocalAgentProviderConfig]",i=L0.localMode?await this.shellEnvironmentService.getShellEnv():{},r=e?.credentials,s=r?.case==="apiKeyCredentials"?r.value:void 0,o=this.reactiveStorageService.applicationUserPersistentStorage,a=o.useOpenAIKey===!0?this.cursorAuthenticationService.openAIKey()??void 0:void 0,u=xgS({apiKeyCandidates:[{value:s?.apiKey,source:"modelDetails.apiKeyCredentials.apiKey"},{value:a,source:"storage.openAIKey"}],baseUrlCandidates:[{value:s?.baseUrl,source:"modelDetails.apiKeyCredentials.baseUrl"},{value:o.openAIBaseUrl,source:"storage.openAIBaseUrl"}]});return{baseUrl:u.baseUrl,apiKey:u.apiKey}}createDefaultLocalModel(e){return "default"}';
51
+ return [
52
+ `function composer(){return he(Mt,{${CURSOR_WORKBENCH_COMPOSER_AUTH_GATE_ANCHOR},get children(){return "controls"}})}`,
53
+ `async function submit(){${CURSOR_WORKBENCH_COMPOSER_SUBMIT_AUTH_GATE_ANCHOR};return "submitted";}`,
54
+ `function agentEditorControls(){const T=()=>{${CURSOR_WORKBENCH_AGENT_EDITOR_SEND_BUTTON_LOGIN_ANCHOR};return n.handleSubmit()};return he(Mt,{${CURSOR_WORKBENCH_AGENT_EDITOR_SEND_BUTTON_AUTH_GATE_ANCHOR}"controls"}})}`,
55
+ `const flags={${CURSOR_WORKBENCH_BUILD_FLAGS_LOCAL_MODE_ANCHOR}}`,
56
+ localProviderConfigAnchor,
57
+ `async function runAgentLoop(){${CURSOR_WORKBENCH_AGENT_LOOP_RUN_ANCHOR}}`,
58
+ `async function agentClientRun(){const g={...p,isRunningInTest:p.isRunningInTest??this.environmentService.enableSmokeTestDriver===!0,clientSupportsInlineImages:!0};${CURSOR_WORKBENCH_AGENT_CLIENT_RUN_LOCAL_MODE_ANCHOR}}`,
59
+ ].join(';');
60
+ }
61
+
38
62
  async function createFixtureApp(prefix: string) {
39
63
  const tempDir = await mkdtemp(join(tmpdir(), prefix));
40
64
  const appPath = join(tempDir, 'Cursor.app');
41
65
  const targetPath = join(appPath, targetRelativePath);
42
66
  const targetContent = `function main() { return "agent"; }\n${CURSOR_POOL_AGENT_EXEC_PROVIDER_REGISTER_ANCHOR}\nmain();\n`;
67
+ const workbenchPath = join(appPath, CURSOR_WORKBENCH_RELATIVE_PATH);
68
+ const workbenchContent = workbenchFixtureContent();
43
69
  await mkdir(join(appPath, 'Contents/Resources/app/extensions/cursor-agent-exec/dist'), {
44
70
  recursive: true,
45
71
  });
72
+ await mkdir(join(appPath, 'Contents/Resources/app/out/vs/workbench'), {
73
+ recursive: true,
74
+ });
46
75
  await writeFile(
47
76
  join(appPath, 'Contents/Resources/app/product.json'),
48
77
  JSON.stringify({ version: cursorVersion, commit: cursorCommit }),
49
78
  'utf8',
50
79
  );
51
80
  await writeFile(targetPath, targetContent, 'utf8');
81
+ await writeFile(workbenchPath, workbenchContent, 'utf8');
52
82
 
53
83
  const originalHash = createHash('sha256').update(targetContent).digest('hex');
84
+ const workbenchOriginalHash = createHash('sha256').update(workbenchContent).digest('hex');
54
85
  const compatEntry: CompatibilityManifestEntry = {
55
86
  platform: process.platform,
56
87
  arch: process.arch,
57
88
  cursorVersion,
58
89
  cursorCommit,
59
90
  supportStatus: 'supported',
91
+ takeoverPlanId: 'cursor-3.6-mac-agent-c-workbench-h-uv',
60
92
  targetRelativePath,
61
93
  expectedSha256: originalHash,
94
+ adapterVersion: '0.5.8',
95
+ structureFamily: 'fixture-agent-exec-workbench',
96
+ patchTargets: [
97
+ {
98
+ name: 'agent-exec',
99
+ targetRelativePath,
100
+ expectedSha256: originalHash,
101
+ patchStrategy: 'cursor-agent-exec-snippet',
102
+ verifyMarker: 'cursor-pool',
103
+ },
104
+ {
105
+ name: 'workbench',
106
+ targetRelativePath: CURSOR_WORKBENCH_RELATIVE_PATH,
107
+ expectedSha256: workbenchOriginalHash,
108
+ patchStrategy: 'cursor-workbench-auth-gate',
109
+ verifyMarker: 'cursor-pool-workbench',
110
+ },
111
+ ],
62
112
  structureSignature: 'fixture',
63
113
  patchStrategy: 'cursor-agent-exec-snippet',
64
114
  verifyMarker: 'cursor-pool',
@@ -77,6 +127,9 @@ async function createFixtureApp(prefix: string) {
77
127
  targetPath,
78
128
  targetContent,
79
129
  originalHash,
130
+ workbenchPath,
131
+ workbenchContent,
132
+ workbenchOriginalHash,
80
133
  runtimeFile: join(tempDir, 'runtime.json'),
81
134
  backupDir: join(tempDir, 'backups'),
82
135
  compatEntry,
@@ -12,12 +12,12 @@ import {
12
12
  snapshotLinkedExtensionBundle,
13
13
  } from '../src/extensionLink';
14
14
 
15
- async function createSourceBundle(tempDir: string) {
15
+ async function createSourceBundle(tempDir: string, version = '0.0.0') {
16
16
  const sourceBundlePath = join(tempDir, 'source/extensions/cursor-pool-status');
17
17
  await mkdir(join(sourceBundlePath, 'dist'), { recursive: true });
18
18
  await writeFile(
19
19
  join(sourceBundlePath, 'package.json'),
20
- JSON.stringify({ name: 'cursorpool', publisher: 'cursor-pool', version: '0.0.0' }),
20
+ JSON.stringify({ name: 'cursorpool', publisher: 'cursor-pool', version }),
21
21
  'utf8',
22
22
  );
23
23
  await writeFile(join(sourceBundlePath, 'dist/extension.js'), 'export function activate() {}\n', 'utf8');
@@ -54,6 +54,52 @@ test('linkExtensionBundle copies source bundle into Cursor extensions directory'
54
54
  }
55
55
  });
56
56
 
57
+ test('linkExtensionBundle uses the source manifest version for the linked directory and index', async () => {
58
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-link-version-'));
59
+ const sourceBundlePath = await createSourceBundle(tempDir, '0.5.6');
60
+ const cursorExtensionsDir = join(tempDir, 'Extensions');
61
+ await mkdir(cursorExtensionsDir, { recursive: true });
62
+ await writeFile(join(cursorExtensionsDir, 'extensions.json'), '[]', 'utf8');
63
+
64
+ try {
65
+ const result = await linkExtensionBundle({ sourceBundlePath, cursorExtensionsDir });
66
+ const index = JSON.parse(await readFile(join(cursorExtensionsDir, 'extensions.json'), 'utf8'));
67
+
68
+ assert.deepEqual(result, {
69
+ state: 'linked',
70
+ linkedPath: join(cursorExtensionsDir, 'cursor-pool.extension-0.5.6'),
71
+ });
72
+ assert.equal(await getLinkedExtensionState(result.linkedPath), 'linked');
73
+ assert.deepEqual(index.at(-1), {
74
+ identifier: { id: 'cursor-pool.cursorpool' },
75
+ location: {
76
+ $mid: 1,
77
+ path: result.linkedPath,
78
+ scheme: 'file',
79
+ },
80
+ relativeLocation: 'cursor-pool.extension-0.5.6',
81
+ version: '0.5.6',
82
+ });
83
+ } finally {
84
+ await rm(tempDir, { recursive: true, force: true });
85
+ }
86
+ });
87
+
88
+ test('linkExtensionBundle rejects unsafe source manifest versions before linking', async () => {
89
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-link-version-unsafe-'));
90
+ const sourceBundlePath = await createSourceBundle(tempDir, '../../bad');
91
+ const cursorExtensionsDir = join(tempDir, 'Extensions');
92
+
93
+ try {
94
+ await assert.rejects(
95
+ linkExtensionBundle({ sourceBundlePath, cursorExtensionsDir }),
96
+ /Unsafe extension version/,
97
+ );
98
+ } finally {
99
+ await rm(tempDir, { recursive: true, force: true });
100
+ }
101
+ });
102
+
57
103
  test('linkExtensionBundle refreshes Cursor extensions index with runtime extension id', async () => {
58
104
  const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-link-index-'));
59
105
  const sourceBundlePath = await createSourceBundle(tempDir);