@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.
- package/node_modules/@cursor-pool/extension/dist/extension.js +46 -116
- package/node_modules/@cursor-pool/extension/package.json +3 -3
- package/node_modules/@cursor-pool/extension/src/api.ts +2 -17
- package/node_modules/@cursor-pool/extension/src/panel.ts +3 -26
- package/node_modules/@cursor-pool/extension/test/panel.test.ts +1 -34
- package/node_modules/@cursor-pool/patcher/package.json +2 -2
- package/node_modules/@cursor-pool/patcher/src/marker.ts +72 -7
- package/node_modules/@cursor-pool/patcher/src/workbenchAuthGateMarker.ts +102 -19
- package/node_modules/@cursor-pool/patcher/test/patchCursorAgentExec.test.ts +88 -13
- package/node_modules/@cursor-pool/patcher/test/patchCursorWorkbench.test.ts +162 -149
- package/node_modules/@cursor-pool/service/package.json +2 -2
- package/node_modules/@cursor-pool/service/src/platformSession.ts +7 -30
- package/node_modules/@cursor-pool/service/src/server.ts +1 -1
- package/node_modules/@cursor-pool/service/test/platformSession.test.ts +4 -5
- package/node_modules/@cursor-pool/service/test/server.test.ts +1 -130
- package/node_modules/@cursor-pool/shared/package.json +1 -1
- package/node_modules/@cursor-pool/shared/src/manifest.ts +0 -35
- package/node_modules/@cursor-pool/shared/test/manifest.test.ts +9 -43
- package/package.json +5 -7
- package/src/compat.ts +201 -194
- package/src/cursor.ts +45 -4
- package/src/extensionBundle.ts +1 -1
- package/src/extensionLink.ts +8 -29
- package/src/install.ts +10 -62
- package/src/installRecord.ts +0 -2
- package/src/patchSet.ts +49 -13
- package/src/platform.ts +3 -3
- package/src/repair.ts +9 -13
- package/src/restore.ts +11 -12
- package/src/status.ts +5 -9
- package/src/target.ts +12 -0
- package/src/trial.ts +2 -3
- package/src/uninstall.ts +6 -2
- package/test/compat.test.ts +146 -192
- package/test/cursor.test.ts +54 -0
- package/test/e2e-install.test.ts +29 -46
- package/test/extensionLink.test.ts +26 -49
- package/test/install.test.ts +4 -64
- package/test/patchSet.test.ts +71 -0
- package/test/repair.test.ts +131 -1
- package/test/restore.test.ts +59 -3
- package/test/status.test.ts +0 -1
- package/test/target.test.ts +28 -0
- package/test/trial.test.ts +15 -1
- package/node_modules/@cursor-pool/takeover-plans/package.json +0 -12
- package/node_modules/@cursor-pool/takeover-plans/src/index.ts +0 -22
- package/node_modules/@cursor-pool/takeover-plans/src/plans.ts +0 -37
- package/node_modules/@cursor-pool/takeover-plans/src/types.ts +0 -9
- package/node_modules/@cursor-pool/takeover-plans/test/registry.test.ts +0 -23
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
4
|
+
import { isAbsolute, join, relative } from 'node:path';
|
|
5
5
|
import test from 'node:test';
|
|
6
6
|
import {
|
|
7
7
|
getLinkedExtensionState,
|
|
@@ -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) {
|
|
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 }),
|
|
20
|
+
JSON.stringify({ name: 'cursorpool', publisher: 'cursor-pool', version: '0.0.0' }),
|
|
21
21
|
'utf8',
|
|
22
22
|
);
|
|
23
23
|
await writeFile(join(sourceBundlePath, 'dist/extension.js'), 'export function activate() {}\n', 'utf8');
|
|
@@ -54,52 +54,6 @@ 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
|
-
|
|
103
57
|
test('linkExtensionBundle refreshes Cursor extensions index with runtime extension id', async () => {
|
|
104
58
|
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-link-index-'));
|
|
105
59
|
const sourceBundlePath = await createSourceBundle(tempDir);
|
|
@@ -169,6 +123,29 @@ test('linkExtensionBundle refreshes Cursor extensions index with runtime extensi
|
|
|
169
123
|
}
|
|
170
124
|
});
|
|
171
125
|
|
|
126
|
+
test('linkExtensionBundle writes an absolute linked extension location for relative cursor extensions dir', async () => {
|
|
127
|
+
const tempDir = await mkdtemp(join(process.cwd(), '.cursor-pool-extension-link-relative-'));
|
|
128
|
+
const sourceBundlePath = await createSourceBundle(tempDir);
|
|
129
|
+
const cursorExtensionsDir = join(tempDir, 'Extensions');
|
|
130
|
+
const relativeCursorExtensionsDir = relative(process.cwd(), cursorExtensionsDir);
|
|
131
|
+
await mkdir(cursorExtensionsDir, { recursive: true });
|
|
132
|
+
await writeFile(join(cursorExtensionsDir, 'extensions.json'), '[]\n', 'utf8');
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const result = await linkExtensionBundle({
|
|
136
|
+
sourceBundlePath,
|
|
137
|
+
cursorExtensionsDir: relativeCursorExtensionsDir,
|
|
138
|
+
});
|
|
139
|
+
const index = JSON.parse(await readFile(join(cursorExtensionsDir, 'extensions.json'), 'utf8'));
|
|
140
|
+
|
|
141
|
+
assert.equal(isAbsolute(result.linkedPath), true);
|
|
142
|
+
assert.equal(isAbsolute(index.at(-1).location.path), true);
|
|
143
|
+
assert.equal(index.at(-1).location.path, result.linkedPath);
|
|
144
|
+
} finally {
|
|
145
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
172
149
|
test('linkExtensionBundle reports missing when source bundle is absent', async () => {
|
|
173
150
|
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-extension-link-missing-'));
|
|
174
151
|
const cursorExtensionsDir = join(tempDir, 'Extensions');
|
package/test/install.test.ts
CHANGED
|
@@ -69,36 +69,15 @@ async function createFixtureApp() {
|
|
|
69
69
|
);
|
|
70
70
|
await writeFile(targetPath, targetContent, 'utf8');
|
|
71
71
|
await writeFile(join(appPath, alwaysLocalRelativePath), 'function alwaysLocal(){}\n', 'utf8');
|
|
72
|
-
|
|
73
|
-
await writeFile(join(appPath, workbenchRelativePath), workbenchContent, 'utf8');
|
|
72
|
+
await writeFile(join(appPath, workbenchRelativePath), workbenchFixture(), 'utf8');
|
|
74
73
|
|
|
75
74
|
const expectedSha256 = createHash('sha256').update(targetContent).digest('hex');
|
|
76
|
-
const workbenchSha256 = createHash('sha256').update(workbenchContent).digest('hex');
|
|
77
75
|
const compatEntry: CompatibilityManifestEntry = {
|
|
78
76
|
platform: process.platform,
|
|
79
77
|
arch: process.arch,
|
|
80
|
-
cursorVersion: '3.5',
|
|
78
|
+
cursorVersion: '3.5.38',
|
|
81
79
|
cursorCommit: '009bb5a3600dd98fe1c1f25798f767f686e14750',
|
|
82
80
|
supportStatus: 'supported',
|
|
83
|
-
adapterVersion: '0.5.8',
|
|
84
|
-
takeoverPlanId: 'cursor-3.5-mac-agent-f-workbench-p-l0',
|
|
85
|
-
structureFamily: 'mac-agent-F-workbench-p-L0',
|
|
86
|
-
patchTargets: [
|
|
87
|
-
{
|
|
88
|
-
name: 'agent-exec',
|
|
89
|
-
targetRelativePath,
|
|
90
|
-
expectedSha256,
|
|
91
|
-
patchStrategy: 'cursor-agent-exec-snippet',
|
|
92
|
-
verifyMarker: 'cursor-pool',
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
name: 'workbench',
|
|
96
|
-
targetRelativePath: workbenchRelativePath,
|
|
97
|
-
expectedSha256: workbenchSha256,
|
|
98
|
-
patchStrategy: 'cursor-workbench-auth-gate',
|
|
99
|
-
verifyMarker: 'cursor-pool-workbench',
|
|
100
|
-
},
|
|
101
|
-
],
|
|
102
81
|
targetRelativePath,
|
|
103
82
|
expectedSha256,
|
|
104
83
|
structureSignature: 'fixture',
|
|
@@ -162,10 +141,9 @@ async function createLinuxFixtureApp() {
|
|
|
162
141
|
const compatEntry: CompatibilityManifestEntry = {
|
|
163
142
|
platform: 'linux',
|
|
164
143
|
arch: process.arch,
|
|
165
|
-
cursorVersion: '3.6',
|
|
144
|
+
cursorVersion: '3.6.31',
|
|
166
145
|
cursorCommit: 'linux-commit',
|
|
167
146
|
supportStatus: 'supported',
|
|
168
|
-
takeoverPlanId: 'cursor-3.6-mac-agent-c-workbench-h-uv',
|
|
169
147
|
targetRelativePath,
|
|
170
148
|
expectedSha256,
|
|
171
149
|
structureSignature: 'linux-fixture',
|
|
@@ -210,7 +188,7 @@ async function createRemoteOnlyFixtureApp() {
|
|
|
210
188
|
);
|
|
211
189
|
const compatEntry = {
|
|
212
190
|
...fixture.compatEntry,
|
|
213
|
-
cursorVersion: '3.7',
|
|
191
|
+
cursorVersion: '3.7.0',
|
|
214
192
|
cursorCommit: 'remote-commit',
|
|
215
193
|
userMessage: 'remote fixture supported',
|
|
216
194
|
};
|
|
@@ -234,7 +212,6 @@ test('install reports Cursor version, simulated extension, service, patch, and h
|
|
|
234
212
|
assert.match(output, /Cursor 3\.5\.38/);
|
|
235
213
|
assert.match(output, /mode: disposable/);
|
|
236
214
|
assert.match(output, /app: .*Cursor\.app/);
|
|
237
|
-
assert.match(output, /takeover-plan: cursor-3\.5-mac-agent-f-workbench-p-l0/);
|
|
238
215
|
assert.match(output, /extension: bundled/);
|
|
239
216
|
assert.match(output, /trial: recorded/);
|
|
240
217
|
assert.match(output, /service: running/);
|
|
@@ -310,36 +287,6 @@ test('install can use a signed remote compatibility manifest from api base url',
|
|
|
310
287
|
}
|
|
311
288
|
});
|
|
312
289
|
|
|
313
|
-
test('install rejects when a secondary patch target hash does not match', async () => {
|
|
314
|
-
const fixture = await createFixtureApp();
|
|
315
|
-
const compatEntry: CompatibilityManifestEntry = {
|
|
316
|
-
...fixture.compatEntry,
|
|
317
|
-
patchTargets: fixture.compatEntry.patchTargets?.map((target) =>
|
|
318
|
-
target.name === 'workbench'
|
|
319
|
-
? { ...target, expectedSha256: 'f'.repeat(64) }
|
|
320
|
-
: target,
|
|
321
|
-
),
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
try {
|
|
325
|
-
await assert.rejects(
|
|
326
|
-
() =>
|
|
327
|
-
install({
|
|
328
|
-
appPath: fixture.appPath,
|
|
329
|
-
runtimeFile: fixture.runtimeFile,
|
|
330
|
-
backupDir: fixture.backupDir,
|
|
331
|
-
trialRecordDir: join(fixture.tempDir, 'trials'),
|
|
332
|
-
extensionInstallPath: join(fixture.tempDir, 'extensions/cursor-pool-status'),
|
|
333
|
-
compatEntries: [compatEntry],
|
|
334
|
-
stopServiceAfterInstall: true,
|
|
335
|
-
}),
|
|
336
|
-
/Patch target hash mismatch/,
|
|
337
|
-
);
|
|
338
|
-
} finally {
|
|
339
|
-
await rm(fixture.tempDir, { recursive: true, force: true });
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
|
|
343
290
|
test('install passes API base URL to detached service startup', async () => {
|
|
344
291
|
const fixture = await createFixtureApp();
|
|
345
292
|
const serviceCalls: Record<string, unknown>[] = [];
|
|
@@ -548,7 +495,6 @@ test('real-mode install writes install record and reports real mode', async () =
|
|
|
548
495
|
});
|
|
549
496
|
|
|
550
497
|
assert.match(output, /mode: real/);
|
|
551
|
-
assert.match(output, /takeover-plan: cursor-3\.5-mac-agent-f-workbench-p-l0/);
|
|
552
498
|
assert.match(output, /patch: applied/);
|
|
553
499
|
assert.match(output, /install-record: recorded/);
|
|
554
500
|
assert.doesNotMatch(output, /trial: recorded/);
|
|
@@ -559,7 +505,6 @@ test('real-mode install writes install record and reports real mode', async () =
|
|
|
559
505
|
assert.equal(record.mode, 'real');
|
|
560
506
|
assert.equal(record.appPath, fixture.appPath);
|
|
561
507
|
assert.equal(record.cursorVersion, '3.5.38');
|
|
562
|
-
assert.equal(record.takeoverPlanId, 'cursor-3.5-mac-agent-f-workbench-p-l0');
|
|
563
508
|
assert.equal(record.extensionInstallPath, extensionInstallPath);
|
|
564
509
|
assert.equal(record.extensionLinkedPath, linkedExtensionPathForDir(cursorExtensionsDir));
|
|
565
510
|
assert.equal(record.lastOperation, 'install');
|
|
@@ -573,7 +518,6 @@ test('real-mode install rolls back install record after health failure', async (
|
|
|
573
518
|
const fixture = await createFixtureApp();
|
|
574
519
|
const installRecordFile = join(fixture.tempDir, 'install.json');
|
|
575
520
|
const extensionInstallPath = join(fixture.tempDir, 'extensions/cursor-pool-status');
|
|
576
|
-
const cursorExtensionsDir = join(fixture.tempDir, 'real-extensions');
|
|
577
521
|
|
|
578
522
|
try {
|
|
579
523
|
await assert.rejects(
|
|
@@ -584,7 +528,6 @@ test('real-mode install rolls back install record after health failure', async (
|
|
|
584
528
|
backupDir: fixture.backupDir,
|
|
585
529
|
installRecordFile,
|
|
586
530
|
extensionInstallPath,
|
|
587
|
-
cursorExtensionsDir,
|
|
588
531
|
compatEntries: [fixture.compatEntry],
|
|
589
532
|
stopServiceAfterInstall: true,
|
|
590
533
|
fetchHealth: async () => ({ ok: false, healthy: false }),
|
|
@@ -603,7 +546,6 @@ test('real-mode install requires confirmation when yes is missing', async () =>
|
|
|
603
546
|
const originalTargetContent = await readFile(fixture.targetPath, 'utf8');
|
|
604
547
|
const installRecordFile = join(fixture.tempDir, 'install.json');
|
|
605
548
|
const extensionInstallPath = join(fixture.tempDir, 'extensions/cursor-pool-status');
|
|
606
|
-
const cursorExtensionsDir = join(fixture.tempDir, 'real-extensions');
|
|
607
549
|
|
|
608
550
|
try {
|
|
609
551
|
await assert.rejects(
|
|
@@ -614,7 +556,6 @@ test('real-mode install requires confirmation when yes is missing', async () =>
|
|
|
614
556
|
backupDir: fixture.backupDir,
|
|
615
557
|
installRecordFile,
|
|
616
558
|
extensionInstallPath,
|
|
617
|
-
cursorExtensionsDir,
|
|
618
559
|
compatEntries: [fixture.compatEntry],
|
|
619
560
|
stopServiceAfterInstall: true,
|
|
620
561
|
}),
|
|
@@ -653,7 +594,6 @@ test('install links extension into an explicit Cursor extensions directory', asy
|
|
|
653
594
|
assert.equal(await getLinkedExtensionState(linkedPath), 'linked');
|
|
654
595
|
|
|
655
596
|
const trialRecord = await readTrialRecord(fixture.appPath, { trialRecordDir });
|
|
656
|
-
assert.equal(trialRecord?.takeoverPlanId, 'cursor-3.5-mac-agent-f-workbench-p-l0');
|
|
657
597
|
assert.equal(trialRecord?.extensionState, 'linked');
|
|
658
598
|
assert.equal(trialRecord?.extensionInstallPath, extensionInstallPath);
|
|
659
599
|
assert.equal(trialRecord?.extensionLinkedPath, linkedPath);
|
|
@@ -0,0 +1,71 @@
|
|
|
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';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
import { readCursorPatchSetState } from '../src/patchSet';
|
|
7
|
+
|
|
8
|
+
async function withLinuxCursorApp(rootKind: 'deb' | 'appimage') {
|
|
9
|
+
const tempDir = await mkdtemp(join(tmpdir(), `cursor-pool-patchset-${rootKind}-`));
|
|
10
|
+
const appPath = rootKind === 'deb' ? join(tempDir, 'cursor') : join(tempDir, 'squashfs-root');
|
|
11
|
+
const resourceRoot =
|
|
12
|
+
rootKind === 'deb' ? join(appPath, 'resources/app') : join(appPath, 'usr/share/cursor/resources/app');
|
|
13
|
+
await mkdir(join(resourceRoot, 'extensions/cursor-agent-exec/dist'), { recursive: true });
|
|
14
|
+
await mkdir(join(resourceRoot, 'out/vs/workbench'), { recursive: true });
|
|
15
|
+
await writeFile(
|
|
16
|
+
join(resourceRoot, 'extensions/cursor-agent-exec/dist/main.js'),
|
|
17
|
+
'function agent(){}\n',
|
|
18
|
+
'utf8',
|
|
19
|
+
);
|
|
20
|
+
await writeFile(
|
|
21
|
+
join(resourceRoot, 'out/vs/workbench/workbench.desktop.main.js'),
|
|
22
|
+
'function workbench(){}\n',
|
|
23
|
+
'utf8',
|
|
24
|
+
);
|
|
25
|
+
return { appPath, tempDir, resourceRoot };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test('readCursorPatchSetState resolves Linux deb install patch targets from /usr/share/cursor root', async () => {
|
|
29
|
+
const fixture = await withLinuxCursorApp('deb');
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const state = await readCursorPatchSetState(fixture.appPath, {
|
|
33
|
+
platform: 'linux',
|
|
34
|
+
agentExecTargetRelativePath: 'resources/app/extensions/cursor-agent-exec/dist/main.js',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
assert.equal(
|
|
38
|
+
state.patches.find((patch) => patch.name === 'agent-exec')?.targetPath,
|
|
39
|
+
join(fixture.resourceRoot, 'extensions/cursor-agent-exec/dist/main.js'),
|
|
40
|
+
);
|
|
41
|
+
assert.equal(
|
|
42
|
+
state.patches.find((patch) => patch.name === 'workbench')?.targetPath,
|
|
43
|
+
join(fixture.resourceRoot, 'out/vs/workbench/workbench.desktop.main.js'),
|
|
44
|
+
);
|
|
45
|
+
} finally {
|
|
46
|
+
await rm(fixture.tempDir, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('readCursorPatchSetState keeps Linux AppImage squashfs-root patch targets unchanged', async () => {
|
|
51
|
+
const fixture = await withLinuxCursorApp('appimage');
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const state = await readCursorPatchSetState(fixture.appPath, {
|
|
55
|
+
platform: 'linux',
|
|
56
|
+
agentExecTargetRelativePath:
|
|
57
|
+
'usr/share/cursor/resources/app/extensions/cursor-agent-exec/dist/main.js',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
assert.equal(
|
|
61
|
+
state.patches.find((patch) => patch.name === 'agent-exec')?.targetPath,
|
|
62
|
+
join(fixture.resourceRoot, 'extensions/cursor-agent-exec/dist/main.js'),
|
|
63
|
+
);
|
|
64
|
+
assert.equal(
|
|
65
|
+
state.patches.find((patch) => patch.name === 'workbench')?.targetPath,
|
|
66
|
+
join(fixture.resourceRoot, 'out/vs/workbench/workbench.desktop.main.js'),
|
|
67
|
+
);
|
|
68
|
+
} finally {
|
|
69
|
+
await rm(fixture.tempDir, { recursive: true, force: true });
|
|
70
|
+
}
|
|
71
|
+
});
|
package/test/repair.test.ts
CHANGED
|
@@ -89,7 +89,6 @@ async function createFixtureApp() {
|
|
|
89
89
|
cursorVersion: '3.5.38',
|
|
90
90
|
cursorCommit: '009bb5a3600dd98fe1c1f25798f767f686e14750',
|
|
91
91
|
supportStatus: 'supported',
|
|
92
|
-
takeoverPlanId: 'cursor-3.6-mac-agent-c-workbench-h-uv',
|
|
93
92
|
targetRelativePath,
|
|
94
93
|
expectedSha256: createHash('sha256').update(targetContent).digest('hex'),
|
|
95
94
|
structureSignature: 'fixture',
|
|
@@ -208,6 +207,137 @@ test('repair restarts stopped service and updates install record', async () => {
|
|
|
208
207
|
}
|
|
209
208
|
});
|
|
210
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
|
+
|
|
211
341
|
test('repair restores missing extension bundle', async () => {
|
|
212
342
|
const fixture = await createFixtureApp();
|
|
213
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/status.test.ts
CHANGED
|
@@ -136,7 +136,6 @@ async function createFixtureApp() {
|
|
|
136
136
|
cursorVersion: '3.5.38',
|
|
137
137
|
cursorCommit: '009bb5a3600dd98fe1c1f25798f767f686e14750',
|
|
138
138
|
supportStatus: 'supported',
|
|
139
|
-
takeoverPlanId: 'cursor-3.6-mac-agent-c-workbench-h-uv',
|
|
140
139
|
targetRelativePath,
|
|
141
140
|
expectedSha256: createHash('sha256').update(targetContent).digest('hex'),
|
|
142
141
|
structureSignature: 'fixture',
|
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' }),
|