@cursorpool-dev/cli 0.5.8 → 0.5.10

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 (49) hide show
  1. package/node_modules/@cursor-pool/extension/dist/extension.js +46 -116
  2. package/node_modules/@cursor-pool/extension/package.json +3 -3
  3. package/node_modules/@cursor-pool/extension/src/api.ts +2 -17
  4. package/node_modules/@cursor-pool/extension/src/panel.ts +3 -26
  5. package/node_modules/@cursor-pool/extension/test/panel.test.ts +1 -34
  6. package/node_modules/@cursor-pool/patcher/package.json +2 -2
  7. package/node_modules/@cursor-pool/patcher/src/marker.ts +72 -7
  8. package/node_modules/@cursor-pool/patcher/src/workbenchAuthGateMarker.ts +102 -19
  9. package/node_modules/@cursor-pool/patcher/test/patchCursorAgentExec.test.ts +88 -13
  10. package/node_modules/@cursor-pool/patcher/test/patchCursorWorkbench.test.ts +162 -149
  11. package/node_modules/@cursor-pool/service/package.json +2 -2
  12. package/node_modules/@cursor-pool/service/src/platformSession.ts +7 -30
  13. package/node_modules/@cursor-pool/service/src/server.ts +1 -1
  14. package/node_modules/@cursor-pool/service/test/platformSession.test.ts +4 -5
  15. package/node_modules/@cursor-pool/service/test/server.test.ts +1 -130
  16. package/node_modules/@cursor-pool/shared/package.json +1 -1
  17. package/node_modules/@cursor-pool/shared/src/manifest.ts +0 -35
  18. package/node_modules/@cursor-pool/shared/test/manifest.test.ts +9 -43
  19. package/package.json +5 -7
  20. package/src/compat.ts +201 -194
  21. package/src/cursor.ts +45 -4
  22. package/src/extensionBundle.ts +1 -1
  23. package/src/extensionLink.ts +8 -29
  24. package/src/install.ts +10 -62
  25. package/src/installRecord.ts +0 -2
  26. package/src/patchSet.ts +49 -13
  27. package/src/platform.ts +3 -3
  28. package/src/repair.ts +9 -13
  29. package/src/restore.ts +11 -12
  30. package/src/status.ts +5 -9
  31. package/src/target.ts +12 -0
  32. package/src/trial.ts +2 -3
  33. package/src/uninstall.ts +6 -2
  34. package/test/compat.test.ts +146 -192
  35. package/test/cursor.test.ts +54 -0
  36. package/test/e2e-install.test.ts +29 -46
  37. package/test/extensionLink.test.ts +26 -49
  38. package/test/install.test.ts +4 -64
  39. package/test/patchSet.test.ts +71 -0
  40. package/test/repair.test.ts +131 -1
  41. package/test/restore.test.ts +59 -3
  42. package/test/status.test.ts +0 -1
  43. package/test/target.test.ts +28 -0
  44. package/test/trial.test.ts +15 -1
  45. package/node_modules/@cursor-pool/takeover-plans/package.json +0 -12
  46. package/node_modules/@cursor-pool/takeover-plans/src/index.ts +0 -22
  47. package/node_modules/@cursor-pool/takeover-plans/src/plans.ts +0 -37
  48. package/node_modules/@cursor-pool/takeover-plans/src/types.ts +0 -9
  49. package/node_modules/@cursor-pool/takeover-plans/test/registry.test.ts +0 -23
@@ -15,34 +15,12 @@ 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
- };
24
18
  return {
25
19
  platform: 'darwin',
26
20
  arch: 'arm64',
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,
21
+ cursorVersion,
32
22
  cursorCommit,
33
23
  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
- ],
46
24
  targetRelativePath: CURSOR_AGENT_EXEC_RELATIVE_PATH,
47
25
  expectedSha256: 'remote-sha256',
48
26
  structureSignature: 'remote-structure',
@@ -68,7 +46,7 @@ function signedEnvelope(version: number, rules: CompatibilityManifestEntry[]) {
68
46
  };
69
47
  }
70
48
 
71
- test('default compatibility manifest supports Cursor 3.6.21 on macOS arm64', () => {
49
+ test('default compatibility manifest supports the Cursor 3.6 macOS arm64 version family', () => {
72
50
  const entry = resolveCompatEntry(
73
51
  {
74
52
  appPath: '/Users/example/Desktop/Cursor-Pool-Agent-Canary.app',
@@ -85,166 +63,208 @@ test('default compatibility manifest supports Cursor 3.6.21 on macOS arm64', ()
85
63
  assert.equal(entry.supportStatus, 'supported');
86
64
  assert.equal(entry.requiresAdHocResign, true);
87
65
  assert.equal(entry.targetRelativePath, CURSOR_AGENT_EXEC_RELATIVE_PATH);
88
- assert.equal(
89
- entry.expectedSha256,
90
- '222512631b78fddcdca3fa76c0dd458a7a86751dde19c998ceb31b3fe1905ebf',
91
- );
66
+ assert.equal(entry.cursorVersion, '3.6');
67
+ assert.equal(entry.cursorCommit, '*');
68
+ assert.equal(entry.expectedSha256, '*');
92
69
  assert.equal(
93
70
  DEFAULT_COMPAT_ENTRIES.some(
94
71
  (candidate) =>
72
+ candidate.platform === 'darwin' &&
73
+ candidate.arch === 'arm64' &&
95
74
  candidate.cursorVersion === '3.6' &&
96
- candidate.cursorCommit === 'e7a7e93f4d75f8272503ecf33cedbaae10114a10',
75
+ candidate.cursorCommit === '*',
97
76
  ),
98
77
  true,
99
78
  );
100
79
  });
101
80
 
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'],
81
+ test('default compatibility manifest supports the Cursor 3.4 macOS arm64 version family', () => {
82
+ const entry = resolveCompatEntry(
83
+ {
84
+ appPath: '/Users/example/Desktop/Cursor-3.4.0.app',
85
+ version: '3.4.0',
86
+ commit: 'cursor-3-4-family-commit',
87
+ },
88
+ {
89
+ platform: 'darwin',
90
+ arch: 'arm64',
91
+ nodeVersion: process.version,
92
+ },
106
93
  );
94
+
95
+ assert.equal(entry.supportStatus, 'supported');
96
+ assert.equal(entry.requiresAdHocResign, true);
97
+ assert.equal(entry.targetRelativePath, CURSOR_AGENT_EXEC_RELATIVE_PATH);
98
+ assert.equal(entry.cursorVersion, '3.4');
99
+ assert.equal(entry.cursorCommit, '*');
100
+ assert.equal(entry.expectedSha256, '*');
107
101
  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
- ),
102
+ DEFAULT_COMPAT_ENTRIES.some(
103
+ (candidate) =>
104
+ candidate.platform === 'darwin' &&
105
+ candidate.arch === 'arm64' &&
106
+ candidate.cursorVersion === '3.4' &&
107
+ candidate.cursorCommit === '*',
112
108
  ),
113
109
  true,
114
110
  );
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
111
  });
124
112
 
125
- test('default compatibility manifest supports Cursor 3.4.20 on macOS arm64 with the v0.4 adapter', () => {
113
+ test('default compatibility manifest accepts later Cursor 3.6 patch versions on macOS arm64', () => {
126
114
  const entry = resolveCompatEntry(
127
115
  {
128
- appPath: '/Applications/Cursor.app',
129
- version: '3.4.20',
130
- commit: '0cf8b06883f54e26bb4f0fb8647c9500ccb43310',
116
+ appPath: '/Users/example/Desktop/Cursor-3.6.31.app',
117
+ version: '3.6.31',
118
+ commit: '81fcf2931d7687b4ff3f3017858d0c6dee7e2a60',
131
119
  },
132
120
  {
133
121
  platform: 'darwin',
134
122
  arch: 'arm64',
135
123
  nodeVersion: process.version,
136
124
  },
137
- { adapterVersion: '0.4.8' },
138
125
  );
139
126
 
140
127
  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');
128
+ assert.equal(entry.cursorVersion, '3.6');
129
+ assert.equal(entry.cursorCommit, '*');
130
+ assert.equal(entry.expectedSha256, '*');
145
131
  });
146
132
 
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/,
133
+ test('default compatibility manifest supports the Cursor 3.7 macOS arm64 version family', () => {
134
+ const entry = resolveCompatEntry(
135
+ {
136
+ appPath: '/Users/example/Desktop/Cursor-3.7.12.app',
137
+ version: '3.7.12',
138
+ commit: 'b887a26c4f70bd8136bfffeda812b24194ec9ce0',
139
+ },
140
+ {
141
+ platform: 'darwin',
142
+ arch: 'arm64',
143
+ nodeVersion: process.version,
144
+ },
164
145
  );
146
+
147
+ assert.equal(entry.supportStatus, 'supported');
148
+ assert.equal(entry.targetRelativePath, CURSOR_AGENT_EXEC_RELATIVE_PATH);
149
+ assert.equal(entry.cursorVersion, '3.7');
150
+ assert.equal(entry.cursorCommit, '*');
151
+ assert.equal(entry.expectedSha256, '*');
165
152
  });
166
153
 
167
- test('default compatibility manifest supports Cursor 3.6.31 on macOS arm64 with the v0.5 adapter', () => {
154
+ test('default compatibility manifest supports Cursor 3.6.31 Linux x64 AppImage', () => {
168
155
  const entry = resolveCompatEntry(
169
156
  {
170
- appPath: '/Applications/Cursor.app',
157
+ appPath: '/home/xubuntu/.cursor-pool/appimages/Cursor-89aa899ccc46fb58/squashfs-root',
171
158
  version: '3.6.31',
172
159
  commit: '81fcf2931d7687b4ff3f3017858d0c6dee7e2a60',
173
160
  },
174
161
  {
175
- platform: 'darwin',
176
- arch: 'arm64',
162
+ platform: 'linux',
163
+ arch: 'x64',
177
164
  nodeVersion: process.version,
178
165
  },
179
- { adapterVersion: '0.5.8' },
180
166
  );
181
167
 
182
168
  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');
169
+ assert.equal(entry.requiresAdHocResign, false);
170
+ assert.equal(
171
+ entry.targetRelativePath,
172
+ 'usr/share/cursor/resources/app/extensions/cursor-agent-exec/dist/main.js',
173
+ );
174
+ assert.equal(
175
+ entry.expectedSha256,
176
+ '05bfa29eacb8271c378765ead4bf881f806b97549dd13367183aa7a9331c1131',
177
+ );
187
178
  });
188
179
 
189
- test('default compatibility manifest supports Cursor 3.7.12 on macOS arm64 with the v0.6 adapter', () => {
180
+ test('default compatibility manifest supports Cursor 3.7.12 Linux arm64 deb installs', () => {
190
181
  const entry = resolveCompatEntry(
191
182
  {
192
- appPath: '/Applications/Cursor.app',
183
+ appPath: '/usr/share/cursor',
193
184
  version: '3.7.12',
194
185
  commit: 'b887a26c4f70bd8136bfffeda812b24194ec9ce0',
195
186
  },
196
187
  {
197
- platform: 'darwin',
188
+ platform: 'linux',
198
189
  arch: 'arm64',
199
190
  nodeVersion: process.version,
200
191
  },
201
- { adapterVersion: '0.6.0' },
202
192
  );
203
193
 
204
194
  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');
195
+ assert.equal(entry.requiresAdHocResign, false);
196
+ assert.equal(
197
+ entry.targetRelativePath,
198
+ 'resources/app/extensions/cursor-agent-exec/dist/main.js',
199
+ );
200
+ assert.equal(
201
+ entry.expectedSha256,
202
+ '9ce7a2f40a98a27eb1b609a79e0e1707bad5fbb02493693f6f18945a7640dde4',
203
+ );
209
204
  });
210
205
 
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/,
206
+ test('default compatibility manifest supports verified Cursor 3.4 Linux arm64 deb installs', () => {
207
+ const entry = resolveCompatEntry(
208
+ {
209
+ appPath: '/usr/share/cursor',
210
+ version: '3.4.20',
211
+ commit: '0cf8b06883f54e26bb4f0fb8647c9500ccb43310',
212
+ },
213
+ {
214
+ platform: 'linux',
215
+ arch: 'arm64',
216
+ nodeVersion: process.version,
217
+ },
228
218
  );
229
- });
230
219
 
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/,
220
+ assert.equal(entry.supportStatus, 'supported');
221
+ assert.equal(entry.requiresAdHocResign, false);
222
+ assert.equal(
223
+ entry.targetRelativePath,
224
+ 'resources/app/extensions/cursor-agent-exec/dist/main.js',
247
225
  );
226
+ assert.equal(
227
+ entry.expectedSha256,
228
+ '4ccb7526e5ece8f6a98a99724a048977698cbf52cb2033ec06f2b5a0a085fe71',
229
+ );
230
+ });
231
+
232
+ test('default compatibility manifest supports verified Cursor 3.5 and 3.6 Linux arm64 deb installs', () => {
233
+ const cases = [
234
+ {
235
+ version: '3.5.38',
236
+ commit: '009bb5a3600dd98fe1c1f25798f767f686e14750',
237
+ expectedSha256: 'cb18f0237278884a39e2ce2b8664255e12689ad0803c20096c38e86c36acc51f',
238
+ },
239
+ {
240
+ version: '3.6.31',
241
+ commit: '81fcf2931d7687b4ff3f3017858d0c6dee7e2a60',
242
+ expectedSha256: '05bfa29eacb8271c378765ead4bf881f806b97549dd13367183aa7a9331c1131',
243
+ },
244
+ ];
245
+
246
+ for (const fixture of cases) {
247
+ const entry = resolveCompatEntry(
248
+ {
249
+ appPath: '/usr/share/cursor',
250
+ version: fixture.version,
251
+ commit: fixture.commit,
252
+ },
253
+ {
254
+ platform: 'linux',
255
+ arch: 'arm64',
256
+ nodeVersion: process.version,
257
+ },
258
+ );
259
+
260
+ assert.equal(entry.supportStatus, 'supported');
261
+ assert.equal(entry.requiresAdHocResign, false);
262
+ assert.equal(
263
+ entry.targetRelativePath,
264
+ 'resources/app/extensions/cursor-agent-exec/dist/main.js',
265
+ );
266
+ assert.equal(entry.expectedSha256, fixture.expectedSha256);
267
+ }
248
268
  });
249
269
 
250
270
  test('default compatibility manifest supports Cursor 3.5.38 on macOS x64 for Rosetta-launched installers', () => {
@@ -263,12 +283,10 @@ test('default compatibility manifest supports Cursor 3.5.38 on macOS x64 for Ros
263
283
 
264
284
  assert.equal(entry.supportStatus, 'supported');
265
285
  assert.equal(entry.requiresAdHocResign, true);
266
- assert.equal(entry.cursorVersion, '3.5');
267
286
  assert.equal(entry.targetRelativePath, CURSOR_AGENT_EXEC_RELATIVE_PATH);
268
- assert.equal(
269
- entry.expectedSha256,
270
- 'cb18f0237278884a39e2ce2b8664255e12689ad0803c20096c38e86c36acc51f',
271
- );
287
+ assert.equal(entry.cursorVersion, '3.5');
288
+ assert.equal(entry.cursorCommit, '*');
289
+ assert.equal(entry.expectedSha256, '*');
272
290
  });
273
291
 
274
292
  test('verifies a remote compatibility manifest envelope with the development signature', () => {
@@ -317,8 +335,7 @@ test('loads remote compatibility entries from api base url when signature is val
317
335
  },
318
336
  });
319
337
 
320
- assert.equal(entries[0]?.cursorVersion, '3.7');
321
- assert.equal(entries[0]?.verifiedCursorVersion, '3.7.0');
338
+ assert.equal(entries[0]?.cursorVersion, '3.7.0');
322
339
  });
323
340
 
324
341
  test('loads compatibility entries from a local file URL for disposable client validation', async () => {
@@ -362,66 +379,3 @@ test('falls back to injected compatibility entries before fetching remote manife
362
379
 
363
380
  assert.equal(entries, injected);
364
381
  });
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
- });
@@ -22,6 +22,60 @@ test('defaultCursorAppPath gives a helpful Windows error when LOCALAPPDATA is un
22
22
  );
23
23
  });
24
24
 
25
+ test('defaultCursorAppPath auto-detects Linux deb installs from the cursor launcher on PATH', () => {
26
+ assert.equal(
27
+ defaultCursorAppPath({
28
+ platform: 'linux',
29
+ execFileSync: (command, args) => {
30
+ if (command === 'which' && args[0] === 'cursor') {
31
+ return '/usr/bin/cursor\n';
32
+ }
33
+ if (command === 'readlink' && args.join(' ') === '-f /usr/bin/cursor') {
34
+ return '/usr/share/cursor/bin/cursor\n';
35
+ }
36
+ throw new Error(`unexpected command: ${command} ${args.join(' ')}`);
37
+ },
38
+ }),
39
+ '/usr/share/cursor',
40
+ );
41
+ });
42
+
43
+ test('findCursor auto-detects Linux deb installs from the cursor launcher on PATH', async () => {
44
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-linux-deb-'));
45
+ const appPath = join(tempDir, 'usr/share/cursor');
46
+ const launcherPath = join(appPath, 'bin/cursor');
47
+
48
+ try {
49
+ await mkdir(join(appPath, 'resources/app'), { recursive: true });
50
+ await mkdir(join(appPath, 'bin'), { recursive: true });
51
+ await writeFile(launcherPath, '#!/bin/sh\n', 'utf8');
52
+ await writeFile(
53
+ join(appPath, 'resources/app/product.json'),
54
+ JSON.stringify({ version: '3.7.12', commit: 'linux-arm64-commit' }),
55
+ 'utf8',
56
+ );
57
+
58
+ const cursor = await findCursor({
59
+ platform: 'linux',
60
+ execFile: async (command, args) => {
61
+ if (command === 'which' && args[0] === 'cursor') {
62
+ return { stdout: '/usr/bin/cursor\n' };
63
+ }
64
+ if (command === 'readlink' && args.join(' ') === '-f /usr/bin/cursor') {
65
+ return { stdout: `${launcherPath}\n` };
66
+ }
67
+ throw new Error(`unexpected command: ${command} ${args.join(' ')}`);
68
+ },
69
+ });
70
+
71
+ assert.equal(cursor.appPath, appPath);
72
+ assert.equal(cursor.version, '3.7.12');
73
+ assert.equal(cursor.commit, 'linux-arm64-commit');
74
+ } finally {
75
+ await rm(tempDir, { recursive: true, force: true });
76
+ }
77
+ });
78
+
25
79
  test('findCursor extracts Linux AppImage files before reading product metadata', async () => {
26
80
  const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-linux-appimage-'));
27
81
  const appImagePath = join(tempDir, 'Cursor.AppImage');
@@ -13,16 +13,6 @@ 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';
26
16
  import type { CompatibilityManifestEntry } from '../../shared/src/manifest';
27
17
  import { getExtensionState } from '../src/extensionBundle';
28
18
  import { getLinkedExtensionState, linkedExtensionPathForDir } from '../src/extensionLink';
@@ -42,20 +32,32 @@ const cursorVersion = '3.5.38';
42
32
  const cursorCommit = '009bb5a3600dd98fe1c1f25798f767f686e14750';
43
33
  const targetRelativePath =
44
34
  'Contents/Resources/app/extensions/cursor-agent-exec/dist/main.js';
35
+ const alwaysLocalRelativePath =
36
+ 'Contents/Resources/app/extensions/cursor-always-local/dist/main.js';
37
+ const workbenchRelativePath =
38
+ 'Contents/Resources/app/out/vs/workbench/workbench.desktop.main.js';
45
39
  const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../../..');
46
40
  const serviceServerPath = resolve(repoRoot, 'packages/service/src/server.ts');
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"}';
41
+ const composerAuthGateAnchor =
42
+ 'get when(){return p()&&!fzC},get fallback(){return he(DGC,{})}';
43
+ const composerSubmitAuthGateAnchor =
44
+ 'if(!p()){e.cursorAuthenticationService.login(),e.commandService.executeCommand(wV,"general");return}';
45
+ const agentLoopRunAnchor =
46
+ 'await this.agentClientService.run(te,H,$e,Ne,Ce,T,ze,me,we,[],ct)';
47
+ const buildFlagsLocalModeAnchor = 'localMode:!1';
48
+ const localProviderConfigAnchor =
49
+ '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"}';
50
+ const agentClientRunLocalModeAnchor =
51
+ 'if(L0.localMode){try{g.onNetworkPhaseStart?.()}catch(b){this.logService.warn("[AgentClientService] onNetworkPhaseStart callback failed in local mode",b)}return this.runLocalAgentInExtensionHost(e,t,i,r,s,a,d,h,g)}return this.client.run(e,t,i,r,s,o,a,u,d,h,g)';
52
+
53
+ function workbenchFixture() {
51
54
  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}}`,
55
+ `function composer(){return he(Mt,{${composerAuthGateAnchor},get children(){return "controls"}})}`,
56
+ `async function submit(){${composerSubmitAuthGateAnchor};return "submitted";}`,
57
+ `const flags={${buildFlagsLocalModeAnchor}}`,
56
58
  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
+ `async function runAgentLoop(){${agentLoopRunAnchor}}`,
60
+ `async function agentClientRun(){const g={};${agentClientRunLocalModeAnchor}}`,
59
61
  ].join(';');
60
62
  }
61
63
 
@@ -63,12 +65,15 @@ async function createFixtureApp(prefix: string) {
63
65
  const tempDir = await mkdtemp(join(tmpdir(), prefix));
64
66
  const appPath = join(tempDir, 'Cursor.app');
65
67
  const targetPath = join(appPath, targetRelativePath);
68
+ const alwaysLocalPath = join(appPath, alwaysLocalRelativePath);
69
+ const workbenchPath = join(appPath, workbenchRelativePath);
66
70
  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();
69
71
  await mkdir(join(appPath, 'Contents/Resources/app/extensions/cursor-agent-exec/dist'), {
70
72
  recursive: true,
71
73
  });
74
+ await mkdir(join(appPath, 'Contents/Resources/app/extensions/cursor-always-local/dist'), {
75
+ recursive: true,
76
+ });
72
77
  await mkdir(join(appPath, 'Contents/Resources/app/out/vs/workbench'), {
73
78
  recursive: true,
74
79
  });
@@ -78,37 +83,18 @@ async function createFixtureApp(prefix: string) {
78
83
  'utf8',
79
84
  );
80
85
  await writeFile(targetPath, targetContent, 'utf8');
81
- await writeFile(workbenchPath, workbenchContent, 'utf8');
86
+ await writeFile(alwaysLocalPath, 'function alwaysLocal(){}\n', 'utf8');
87
+ await writeFile(workbenchPath, workbenchFixture(), 'utf8');
82
88
 
83
89
  const originalHash = createHash('sha256').update(targetContent).digest('hex');
84
- const workbenchOriginalHash = createHash('sha256').update(workbenchContent).digest('hex');
85
90
  const compatEntry: CompatibilityManifestEntry = {
86
91
  platform: process.platform,
87
92
  arch: process.arch,
88
93
  cursorVersion,
89
94
  cursorCommit,
90
95
  supportStatus: 'supported',
91
- takeoverPlanId: 'cursor-3.6-mac-agent-c-workbench-h-uv',
92
96
  targetRelativePath,
93
97
  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
- ],
112
98
  structureSignature: 'fixture',
113
99
  patchStrategy: 'cursor-agent-exec-snippet',
114
100
  verifyMarker: 'cursor-pool',
@@ -127,9 +113,6 @@ async function createFixtureApp(prefix: string) {
127
113
  targetPath,
128
114
  targetContent,
129
115
  originalHash,
130
- workbenchPath,
131
- workbenchContent,
132
- workbenchOriginalHash,
133
116
  runtimeFile: join(tempDir, 'runtime.json'),
134
117
  backupDir: join(tempDir, 'backups'),
135
118
  compatEntry,