@cursorpool-dev/cli 0.5.9 → 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.
- package/node_modules/@cursor-pool/extension/dist/extension.js +1 -1
- package/node_modules/@cursor-pool/extension/package.json +3 -3
- package/node_modules/@cursor-pool/extension/src/api.ts +1 -1
- package/node_modules/@cursor-pool/extension/test/panel.test.ts +1 -1
- package/node_modules/@cursor-pool/patcher/package.json +2 -2
- package/node_modules/@cursor-pool/patcher/src/workbenchAuthGateMarker.ts +28 -8
- package/node_modules/@cursor-pool/patcher/test/patchCursorWorkbench.test.ts +11 -0
- package/node_modules/@cursor-pool/service/package.json +2 -2
- package/node_modules/@cursor-pool/shared/package.json +1 -1
- package/node_modules/@cursor-pool/shared/test/manifest.test.ts +6 -6
- package/package.json +5 -5
- package/src/compat.ts +94 -15
- package/src/cursor.ts +45 -4
- package/src/extensionBundle.ts +1 -1
- package/src/extensionLink.ts +2 -2
- package/src/install.ts +2 -1
- package/src/patchSet.ts +46 -4
- package/src/platform.ts +3 -3
- package/src/repair.ts +9 -5
- package/src/restore.ts +13 -6
- package/src/status.ts +5 -3
- package/src/target.ts +12 -0
- package/src/uninstall.ts +6 -2
- package/test/compat.test.ts +90 -0
- package/test/cursor.test.ts +54 -0
- package/test/e2e-install.test.ts +36 -0
- package/test/patchSet.test.ts +71 -0
- package/test/repair.test.ts +131 -0
- package/test/restore.test.ts +59 -3
- package/test/target.test.ts +28 -0
package/test/repair.test.ts
CHANGED
|
@@ -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
|
|
package/test/restore.test.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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 });
|
package/test/target.test.ts
CHANGED
|
@@ -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' }),
|