@cursorpool-dev/cli 0.5.9 → 0.5.11

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 (33) hide show
  1. package/node_modules/@cursor-pool/extension/dist/extension.js +1 -1
  2. package/node_modules/@cursor-pool/extension/package.json +3 -3
  3. package/node_modules/@cursor-pool/extension/src/api.ts +1 -1
  4. package/node_modules/@cursor-pool/extension/test/panel.test.ts +1 -1
  5. package/node_modules/@cursor-pool/patcher/package.json +2 -2
  6. package/node_modules/@cursor-pool/patcher/src/workbenchAuthGateMarker.ts +28 -8
  7. package/node_modules/@cursor-pool/patcher/test/patchCursorWorkbench.test.ts +11 -0
  8. package/node_modules/@cursor-pool/service/package.json +2 -2
  9. package/node_modules/@cursor-pool/shared/package.json +1 -1
  10. package/node_modules/@cursor-pool/shared/test/manifest.test.ts +6 -6
  11. package/node_modules/@esbuild/linux-arm64/README.md +3 -0
  12. package/node_modules/@esbuild/linux-arm64/bin/esbuild +0 -0
  13. package/node_modules/@esbuild/linux-arm64/package.json +20 -0
  14. package/package.json +5 -5
  15. package/src/compat.ts +94 -15
  16. package/src/cursor.ts +45 -4
  17. package/src/extensionBundle.ts +1 -1
  18. package/src/extensionLink.ts +2 -2
  19. package/src/install.ts +2 -1
  20. package/src/patchSet.ts +46 -4
  21. package/src/platform.ts +3 -3
  22. package/src/repair.ts +9 -5
  23. package/src/restore.ts +13 -6
  24. package/src/status.ts +5 -3
  25. package/src/target.ts +12 -0
  26. package/src/uninstall.ts +6 -2
  27. package/test/compat.test.ts +90 -0
  28. package/test/cursor.test.ts +54 -0
  29. package/test/e2e-install.test.ts +36 -0
  30. package/test/patchSet.test.ts +71 -0
  31. package/test/repair.test.ts +131 -0
  32. package/test/restore.test.ts +59 -3
  33. package/test/target.test.ts +28 -0
@@ -207,6 +207,137 @@ test('repair restarts stopped service and updates install record', async () => {
207
207
  }
208
208
  });
209
209
 
210
+ test('repair accepts version-family compatibility entries with wildcard commits', async () => {
211
+ const fixture = await createFixtureApp();
212
+ const familyCompatEntry: CompatibilityManifestEntry = {
213
+ ...fixture.compatEntry,
214
+ cursorVersion: '3.5',
215
+ cursorCommit: '*',
216
+ };
217
+
218
+ try {
219
+ await patchFullSet(fixture.appPath, fixture.backupDir);
220
+ await bundleExtension({ installPath: fixture.extensionInstallPath });
221
+ await seedInstallRecord(fixture);
222
+
223
+ const output = await repair({
224
+ realAppPath: fixture.appPath,
225
+ yes: true,
226
+ runtimeFile: fixture.runtimeFile,
227
+ backupDir: fixture.backupDir,
228
+ installRecordFile: fixture.installRecordFile,
229
+ extensionInstallPath: fixture.extensionInstallPath,
230
+ compatEntries: [familyCompatEntry],
231
+ startDetachedService: async () => {
232
+ await writeRuntimeInfo(
233
+ { host: '127.0.0.1', port: 45207, runtimeId: 'runtime-repaired-family' },
234
+ { runtimeFile: fixture.runtimeFile },
235
+ );
236
+ return { host: '127.0.0.1', port: 45207, runtimeId: 'runtime-repaired-family' };
237
+ },
238
+ });
239
+
240
+ assert.match(output, /compat: supported/);
241
+ assert.match(output, /repair: ok/);
242
+ } finally {
243
+ await rm(fixture.tempDir, { recursive: true, force: true });
244
+ }
245
+ });
246
+
247
+ test('repair passes platform through when checking and repairing the full patch set', async () => {
248
+ const fixture = await createFixtureApp();
249
+ let workbenchTargetRelativePath: string | undefined;
250
+ const linuxDebTargetRelativePath =
251
+ 'resources/app/extensions/cursor-agent-exec/dist/main.js';
252
+ const linuxDebWorkbenchRelativePath =
253
+ 'resources/app/out/vs/workbench/workbench.desktop.main.js';
254
+
255
+ try {
256
+ await mkdir(join(fixture.appPath, 'resources/app/extensions/cursor-agent-exec/dist'), {
257
+ recursive: true,
258
+ });
259
+ await mkdir(join(fixture.appPath, 'resources/app/out/vs/workbench'), { recursive: true });
260
+ await writeFile(
261
+ join(fixture.appPath, 'resources/app/product.json'),
262
+ JSON.stringify({
263
+ version: '3.5.38',
264
+ commit: '009bb5a3600dd98fe1c1f25798f767f686e14750',
265
+ }),
266
+ 'utf8',
267
+ );
268
+ await writeFile(
269
+ join(fixture.appPath, linuxDebTargetRelativePath),
270
+ `${fixture.targetContent}${CURSOR_POOL_PATCH_MARKER}\n`,
271
+ 'utf8',
272
+ );
273
+ await writeFile(
274
+ join(fixture.appPath, linuxDebWorkbenchRelativePath),
275
+ 'function workbenchWithoutMarker(){}\n',
276
+ 'utf8',
277
+ );
278
+ await bundleExtension({ installPath: fixture.extensionInstallPath });
279
+ await writeInstallRecord(
280
+ createInstallRecordFixture(fixture, {
281
+ targetRelativePath: linuxDebTargetRelativePath,
282
+ originalSha256: '*',
283
+ }),
284
+ { installRecordFile: fixture.installRecordFile },
285
+ );
286
+
287
+ await repair({
288
+ realAppPath: fixture.appPath,
289
+ productRelativePath: 'resources/app/product.json',
290
+ platform: 'linux',
291
+ arch: process.arch,
292
+ yes: true,
293
+ runtimeFile: fixture.runtimeFile,
294
+ backupDir: fixture.backupDir,
295
+ installRecordFile: fixture.installRecordFile,
296
+ extensionInstallPath: fixture.extensionInstallPath,
297
+ compatEntries: [
298
+ {
299
+ ...fixture.compatEntry,
300
+ platform: 'linux',
301
+ arch: process.arch,
302
+ targetRelativePath: linuxDebTargetRelativePath,
303
+ expectedSha256: '*',
304
+ },
305
+ ],
306
+ startDetachedService: async () => {
307
+ await writeRuntimeInfo(
308
+ { host: '127.0.0.1', port: 45208, runtimeId: 'runtime-repaired-linux-platform' },
309
+ { runtimeFile: fixture.runtimeFile },
310
+ );
311
+ return { host: '127.0.0.1', port: 45208, runtimeId: 'runtime-repaired-linux-platform' };
312
+ },
313
+ patchCursorAgentExec: async (_appPath, options = {}) => ({
314
+ targetPath: join(fixture.appPath, options.targetRelativePath ?? targetRelativePath),
315
+ backupPath: join(fixture.backupDir, 'agent.bak'),
316
+ beforeHash: 'before',
317
+ afterHash: 'after',
318
+ markerPresent: true,
319
+ }),
320
+ patchCursorWorkbenchAuthGate: async (_appPath, options = {}) => {
321
+ workbenchTargetRelativePath = options.targetRelativePath;
322
+ return {
323
+ targetPath: join(fixture.appPath, options.targetRelativePath ?? workbenchRelativePath),
324
+ backupPath: join(fixture.backupDir, 'workbench.bak'),
325
+ beforeHash: 'before',
326
+ afterHash: 'after',
327
+ markerPresent: true,
328
+ };
329
+ },
330
+ });
331
+
332
+ assert.equal(
333
+ workbenchTargetRelativePath,
334
+ linuxDebWorkbenchRelativePath,
335
+ );
336
+ } finally {
337
+ await rm(fixture.tempDir, { recursive: true, force: true });
338
+ }
339
+ });
340
+
210
341
  test('repair restores missing extension bundle', async () => {
211
342
  const fixture = await createFixtureApp();
212
343
 
@@ -5,6 +5,8 @@ import { join, normalize } from 'node:path';
5
5
  import test from 'node:test';
6
6
  import { CURSOR_POOL_AGENT_EXEC_PROVIDER_REGISTER_ANCHOR } from '../../patcher/src/marker';
7
7
  import { patchCursorAgentExec } from '../../patcher/src/patchCursorAgentExec';
8
+ import { patchCursorAlwaysLocal } from '../../patcher/src/patchCursorAlwaysLocal';
9
+ import { patchCursorWorkbenchAuthGate } from '../../patcher/src/patchCursorWorkbenchAuthGate';
8
10
  import {
9
11
  installIdForAppPath,
10
12
  readInstallRecord,
@@ -16,15 +18,57 @@ import { trialIdForAppPath, writeTrialRecord } from '../src/trial';
16
18
 
17
19
  const targetRelativePath =
18
20
  'Contents/Resources/app/extensions/cursor-agent-exec/dist/main.js';
21
+ const alwaysLocalRelativePath =
22
+ 'Contents/Resources/app/extensions/cursor-always-local/dist/main.js';
23
+ const workbenchRelativePath =
24
+ 'Contents/Resources/app/out/vs/workbench/workbench.desktop.main.js';
25
+ const composerAuthGateAnchor =
26
+ 'get when(){return p()&&!fzC},get fallback(){return he(DGC,{})}';
27
+ const composerSubmitAuthGateAnchor =
28
+ 'if(!p()){e.cursorAuthenticationService.login(),e.commandService.executeCommand(wV,"general");return}';
29
+ const agentLoopRunAnchor =
30
+ 'await this.agentClientService.run(te,H,$e,Ne,Ce,T,ze,me,we,[],ct)';
31
+ const buildFlagsLocalModeAnchor = 'localMode:!1';
32
+ const localProviderConfigAnchor =
33
+ '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"}';
34
+ const agentClientRunLocalModeAnchor =
35
+ '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)';
36
+
37
+ function workbenchFixture() {
38
+ return [
39
+ `function composer(){return he(Mt,{${composerAuthGateAnchor},get children(){return "controls"}})}`,
40
+ `async function submit(){${composerSubmitAuthGateAnchor};return "submitted";}`,
41
+ `const flags={${buildFlagsLocalModeAnchor}}`,
42
+ localProviderConfigAnchor,
43
+ `async function runAgentLoop(){${agentLoopRunAnchor}}`,
44
+ `async function agentClientRun(){const g={};${agentClientRunLocalModeAnchor}}`,
45
+ ].join(';');
46
+ }
47
+
48
+ async function patchFullSet(appPath: string, backupDir: string) {
49
+ await patchCursorAgentExec(appPath, { backupDir });
50
+ await patchCursorAlwaysLocal(appPath, { backupDir });
51
+ await patchCursorWorkbenchAuthGate(appPath, { backupDir });
52
+ }
19
53
 
20
54
  async function createFixtureApp() {
21
55
  const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-cli-restore-'));
22
56
  const appPath = join(tempDir, 'Cursor.app');
23
57
  const targetPath = join(appPath, targetRelativePath);
58
+ const alwaysLocalPath = join(appPath, alwaysLocalRelativePath);
59
+ const workbenchPath = join(appPath, workbenchRelativePath);
24
60
  const targetContent = `function main() { return "agent"; }\n${CURSOR_POOL_AGENT_EXEC_PROVIDER_REGISTER_ANCHOR}\nmain();\n`;
61
+ const alwaysLocalContent = 'function alwaysLocal(){}\n';
62
+ const workbenchContent = workbenchFixture();
25
63
  await mkdir(join(appPath, 'Contents/Resources/app/extensions/cursor-agent-exec/dist'), {
26
64
  recursive: true,
27
65
  });
66
+ await mkdir(join(appPath, 'Contents/Resources/app/extensions/cursor-always-local/dist'), {
67
+ recursive: true,
68
+ });
69
+ await mkdir(join(appPath, 'Contents/Resources/app/out/vs/workbench'), {
70
+ recursive: true,
71
+ });
28
72
  await writeFile(
29
73
  join(appPath, 'Contents/Resources/app/product.json'),
30
74
  JSON.stringify({
@@ -34,12 +78,18 @@ async function createFixtureApp() {
34
78
  'utf8',
35
79
  );
36
80
  await writeFile(targetPath, targetContent, 'utf8');
81
+ await writeFile(alwaysLocalPath, alwaysLocalContent, 'utf8');
82
+ await writeFile(workbenchPath, workbenchContent, 'utf8');
37
83
 
38
84
  return {
39
85
  appPath,
40
86
  tempDir,
41
87
  targetPath,
88
+ alwaysLocalPath,
89
+ workbenchPath,
42
90
  targetContent,
91
+ alwaysLocalContent,
92
+ workbenchContent,
43
93
  backupDir: join(tempDir, 'backups'),
44
94
  trialRecordDir: join(tempDir, 'trials'),
45
95
  };
@@ -49,7 +99,7 @@ test('restore reports ok and replaces patched fixture bytes with backup bytes',
49
99
  const fixture = await createFixtureApp();
50
100
 
51
101
  try {
52
- await patchCursorAgentExec(fixture.appPath, { backupDir: fixture.backupDir });
102
+ await patchFullSet(fixture.appPath, fixture.backupDir);
53
103
 
54
104
  const output = await restore({
55
105
  appPath: fixture.appPath,
@@ -59,6 +109,8 @@ test('restore reports ok and replaces patched fixture bytes with backup bytes',
59
109
  assert.match(output, /mode: disposable/);
60
110
  assert.match(output, /restore: ok/);
61
111
  assert.equal(await readFile(fixture.targetPath, 'utf8'), fixture.targetContent);
112
+ assert.equal(await readFile(fixture.alwaysLocalPath, 'utf8'), fixture.alwaysLocalContent);
113
+ assert.equal(await readFile(fixture.workbenchPath, 'utf8'), fixture.workbenchContent);
62
114
  } finally {
63
115
  await rm(fixture.tempDir, { recursive: true, force: true });
64
116
  }
@@ -68,7 +120,7 @@ test('restore discovers backup directory from the trial record', async () => {
68
120
  const fixture = await createFixtureApp();
69
121
 
70
122
  try {
71
- await patchCursorAgentExec(fixture.appPath, { backupDir: fixture.backupDir });
123
+ await patchFullSet(fixture.appPath, fixture.backupDir);
72
124
  await writeTrialRecord(
73
125
  {
74
126
  trialId: trialIdForAppPath(fixture.appPath),
@@ -96,6 +148,8 @@ test('restore discovers backup directory from the trial record', async () => {
96
148
  assert.match(output, /mode: disposable/);
97
149
  assert.match(output, /restore: ok/);
98
150
  assert.equal(await readFile(fixture.targetPath, 'utf8'), fixture.targetContent);
151
+ assert.equal(await readFile(fixture.alwaysLocalPath, 'utf8'), fixture.alwaysLocalContent);
152
+ assert.equal(await readFile(fixture.workbenchPath, 'utf8'), fixture.workbenchContent);
99
153
  } finally {
100
154
  await rm(fixture.tempDir, { recursive: true, force: true });
101
155
  }
@@ -136,7 +190,7 @@ test('real-mode restore uses install record backup and keeps install record', as
136
190
  const runtimeFile = join(fixture.tempDir, 'runtime.json');
137
191
 
138
192
  try {
139
- await patchCursorAgentExec(fixture.appPath, { backupDir: fixture.backupDir });
193
+ await patchFullSet(fixture.appPath, fixture.backupDir);
140
194
  const record = createInstallRecordFixture(fixture, { runtimeFile });
141
195
  await writeInstallRecord(record, { installRecordFile });
142
196
 
@@ -150,6 +204,8 @@ test('real-mode restore uses install record backup and keeps install record', as
150
204
  assert.match(output, /install-record: recorded/);
151
205
  assert.match(output, /restore: ok/);
152
206
  assert.equal(await readFile(fixture.targetPath, 'utf8'), fixture.targetContent);
207
+ assert.equal(await readFile(fixture.alwaysLocalPath, 'utf8'), fixture.alwaysLocalContent);
208
+ assert.equal(await readFile(fixture.workbenchPath, 'utf8'), fixture.workbenchContent);
153
209
  assert.deepEqual(await readInstallRecord({ installRecordFile }), record);
154
210
  } finally {
155
211
  await rm(fixture.tempDir, { recursive: true, force: true });
@@ -16,6 +16,34 @@ test('resolveCursorTarget treats explicit appPath as disposable mode', () => {
16
16
  );
17
17
  });
18
18
 
19
+ test('resolveCursorTarget treats Linux deb Cursor root as real mode', () => {
20
+ assert.deepEqual(
21
+ resolveCursorTarget({
22
+ appPath: '/usr/share/cursor/',
23
+ platform: 'linux',
24
+ }),
25
+ {
26
+ mode: 'real',
27
+ appPath: '/usr/share/cursor',
28
+ requiresConfirmation: true,
29
+ },
30
+ );
31
+ });
32
+
33
+ test('resolveCursorTarget keeps Linux AppImage extraction roots disposable', () => {
34
+ assert.deepEqual(
35
+ resolveCursorTarget({
36
+ appPath: '/home/debian/.cursor-pool/appimages/Cursor-abc/squashfs-root',
37
+ platform: 'linux',
38
+ }),
39
+ {
40
+ mode: 'disposable',
41
+ appPath: '/home/debian/.cursor-pool/appimages/Cursor-abc/squashfs-root',
42
+ requiresConfirmation: false,
43
+ },
44
+ );
45
+ });
46
+
19
47
  test('resolveCursorTarget rejects the real Cursor app as disposable appPath', () => {
20
48
  assert.throws(
21
49
  () => resolveCursorTarget({ appPath: '/Applications/Cursor.app' }),