@cursorpool-dev/cli 0.5.6 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/node_modules/@cursor-pool/extension/dist/extension.js +116 -46
- package/node_modules/@cursor-pool/extension/package.json +3 -3
- package/node_modules/@cursor-pool/extension/src/api.ts +17 -2
- package/node_modules/@cursor-pool/extension/src/panel.ts +26 -3
- package/node_modules/@cursor-pool/extension/test/panel.test.ts +34 -1
- package/node_modules/@cursor-pool/patcher/package.json +2 -2
- package/node_modules/@cursor-pool/patcher/src/marker.ts +5 -1
- package/node_modules/@cursor-pool/patcher/src/workbenchAuthGateMarker.ts +58 -7
- package/node_modules/@cursor-pool/patcher/test/patchCursorAgentExec.test.ts +20 -0
- package/node_modules/@cursor-pool/patcher/test/patchCursorWorkbench.test.ts +193 -2
- package/node_modules/@cursor-pool/service/package.json +2 -2
- package/node_modules/@cursor-pool/service/src/platformSession.ts +30 -7
- package/node_modules/@cursor-pool/service/src/server.ts +1 -0
- package/node_modules/@cursor-pool/service/test/platformSession.test.ts +5 -4
- package/node_modules/@cursor-pool/service/test/server.test.ts +130 -0
- package/node_modules/@cursor-pool/shared/package.json +1 -1
- package/node_modules/@cursor-pool/shared/src/manifest.ts +35 -0
- package/node_modules/@cursor-pool/shared/test/manifest.test.ts +43 -9
- package/node_modules/@cursor-pool/takeover-plans/package.json +12 -0
- package/node_modules/@cursor-pool/takeover-plans/src/index.ts +22 -0
- package/node_modules/@cursor-pool/takeover-plans/src/plans.ts +37 -0
- package/node_modules/@cursor-pool/takeover-plans/src/types.ts +9 -0
- package/node_modules/@cursor-pool/takeover-plans/test/registry.test.ts +23 -0
- package/node_modules/@esbuild/linux-x64/README.md +3 -0
- package/node_modules/@esbuild/linux-x64/bin/esbuild +0 -0
- package/node_modules/@esbuild/linux-x64/package.json +20 -0
- package/node_modules/esbuild/LICENSE.md +21 -0
- package/node_modules/esbuild/README.md +3 -0
- package/node_modules/esbuild/bin/esbuild +223 -0
- package/node_modules/esbuild/install.js +300 -0
- package/node_modules/esbuild/lib/main.d.ts +716 -0
- package/node_modules/esbuild/lib/main.js +2532 -0
- package/node_modules/esbuild/package.json +74 -0
- package/node_modules/tsx/LICENSE +21 -0
- package/node_modules/tsx/README.md +32 -0
- package/node_modules/tsx/dist/cjs/api/index.cjs +1 -0
- package/node_modules/tsx/dist/cjs/api/index.d.cts +35 -0
- package/node_modules/tsx/dist/cjs/api/index.d.mts +35 -0
- package/node_modules/tsx/dist/cjs/api/index.mjs +1 -0
- package/node_modules/tsx/dist/cjs/index.cjs +1 -0
- package/node_modules/tsx/dist/cjs/index.mjs +1 -0
- package/node_modules/tsx/dist/cli.cjs +54 -0
- package/node_modules/tsx/dist/cli.mjs +55 -0
- package/node_modules/tsx/dist/client-D3mGB526.cjs +1 -0
- package/node_modules/tsx/dist/client-D_mPDF5S.mjs +1 -0
- package/node_modules/tsx/dist/esm/api/index.cjs +1 -0
- package/node_modules/tsx/dist/esm/api/index.d.cts +35 -0
- package/node_modules/tsx/dist/esm/api/index.d.mts +35 -0
- package/node_modules/tsx/dist/esm/api/index.mjs +1 -0
- package/node_modules/tsx/dist/esm/index.cjs +1 -0
- package/node_modules/tsx/dist/esm/index.mjs +1 -0
- package/node_modules/tsx/dist/get-pipe-path-D4YM6rQt.cjs +1 -0
- package/node_modules/tsx/dist/get-pipe-path-_tAJyU_v.mjs +1 -0
- package/node_modules/tsx/dist/index-BWFBUo6r.cjs +1 -0
- package/node_modules/tsx/dist/index-D9F1FXzN.cjs +14 -0
- package/node_modules/tsx/dist/index-XurvG3JN.mjs +14 -0
- package/node_modules/tsx/dist/index-gbaejti9.mjs +1 -0
- package/node_modules/tsx/dist/lexer-DQCqS3nf.mjs +3 -0
- package/node_modules/tsx/dist/lexer-DgIbo0BU.cjs +3 -0
- package/node_modules/tsx/dist/loader.cjs +1 -0
- package/node_modules/tsx/dist/loader.mjs +1 -0
- package/node_modules/tsx/dist/node-features-B9BBLzwu.mjs +1 -0
- package/node_modules/tsx/dist/node-features-CQLdkVE6.cjs +1 -0
- package/node_modules/tsx/dist/package-CGdS2_oX.cjs +1 -0
- package/node_modules/tsx/dist/package-DyJMwVU5.mjs +1 -0
- package/node_modules/tsx/dist/patch-repl.cjs +1 -0
- package/node_modules/tsx/dist/patch-repl.mjs +1 -0
- package/node_modules/tsx/dist/preflight.cjs +1 -0
- package/node_modules/tsx/dist/preflight.mjs +1 -0
- package/node_modules/tsx/dist/register-BOkp8V6j.cjs +10 -0
- package/node_modules/tsx/dist/register-BnTWPeIB.mjs +10 -0
- package/node_modules/tsx/dist/register-CHVGxKtC.cjs +2 -0
- package/node_modules/tsx/dist/register-D_B8UL5H.mjs +2 -0
- package/node_modules/tsx/dist/repl.cjs +3 -0
- package/node_modules/tsx/dist/repl.mjs +3 -0
- package/node_modules/tsx/dist/require-CjvaJWEr.cjs +1 -0
- package/node_modules/tsx/dist/require-DzmC1hVr.mjs +1 -0
- package/node_modules/tsx/dist/suppress-warnings.cjs +1 -0
- package/node_modules/tsx/dist/suppress-warnings.mjs +1 -0
- package/node_modules/tsx/dist/temporary-directory-B83uKxJF.cjs +1 -0
- package/node_modules/tsx/dist/temporary-directory-BDDVQOvU.mjs +1 -0
- package/node_modules/tsx/dist/types-Cxp8y2TL.d.ts +5 -0
- package/node_modules/tsx/package.json +67 -0
- package/package.json +11 -6
- package/src/autostart.ts +5 -1
- package/src/compat.ts +193 -47
- package/src/cursor.ts +59 -3
- package/src/extensionBundle.ts +1 -1
- package/src/extensionLink.ts +28 -7
- package/src/install.ts +176 -24
- package/src/installRecord.ts +2 -0
- package/src/patchSet.ts +12 -6
- package/src/platform.ts +3 -3
- package/src/repair.ts +10 -1
- package/src/restore.ts +12 -4
- package/src/serviceProcess.ts +2 -1
- package/src/status.ts +6 -0
- package/src/trial.ts +1 -0
- package/test/autostart.test.ts +23 -2
- package/test/compat.test.ts +238 -3
- package/test/cursor-pool-bin.test.ts +1 -0
- package/test/cursor.test.ts +60 -1
- package/test/e2e-install.test.ts +53 -0
- package/test/extensionLink.test.ts +48 -2
- package/test/install.test.ts +191 -6
- package/test/repair.test.ts +1 -0
- package/test/serviceProcess.test.ts +10 -1
- package/test/status.test.ts +1 -0
package/src/install.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { cp, mkdir, mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
2
|
-
import { tmpdir } from 'node:os';
|
|
1
|
+
import { chmod, cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir, tmpdir } from 'node:os';
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
import {
|
|
5
5
|
patchCursorAgentExec,
|
|
@@ -7,16 +7,18 @@ import {
|
|
|
7
7
|
restoreCursorAgentExec,
|
|
8
8
|
restoreCursorAlwaysLocal,
|
|
9
9
|
restoreCursorWorkbenchAuthGate,
|
|
10
|
+
containsCursorWorkbenchAuthGateMarker,
|
|
10
11
|
} from '@cursor-pool/patcher';
|
|
11
12
|
import { sha256File } from '@cursor-pool/patcher/hash';
|
|
12
13
|
import { startServer } from '@cursor-pool/service';
|
|
14
|
+
import { assertBundledTakeoverPlan } from '@cursor-pool/takeover-plans';
|
|
13
15
|
import {
|
|
14
16
|
readRuntimeInfo,
|
|
15
17
|
resolveRuntimeFile,
|
|
16
18
|
writeRuntimeInfo,
|
|
17
19
|
type RuntimeInfo,
|
|
18
20
|
} from '@cursor-pool/service';
|
|
19
|
-
import type { CompatibilityManifestEntry } from '@cursor-pool/shared/manifest';
|
|
21
|
+
import type { CompatibilityManifestEntry, CompatibilityPatchTarget } from '@cursor-pool/shared/manifest';
|
|
20
22
|
import { DEFAULT_RUNTIME_FILE } from '@cursor-pool/shared/runtime';
|
|
21
23
|
import { writeClientConfig } from '@cursor-pool/shared/clientConfig';
|
|
22
24
|
import { containsCursorPoolMarker } from '@cursor-pool/patcher/marker';
|
|
@@ -32,6 +34,7 @@ import {
|
|
|
32
34
|
import {
|
|
33
35
|
getLinkedExtensionState,
|
|
34
36
|
linkExtensionBundle,
|
|
37
|
+
linkedExtensionPathForSource,
|
|
35
38
|
linkedExtensionPathForDir,
|
|
36
39
|
refreshCursorExtensionsIndex,
|
|
37
40
|
removeLinkedExtensionBundle,
|
|
@@ -100,10 +103,16 @@ export type InstallOptions = FindCursorOptions &
|
|
|
100
103
|
restoreCursorWorkbenchAuthGate?: typeof restoreCursorWorkbenchAuthGate;
|
|
101
104
|
adHocResignApp?: typeof adHocResignApp;
|
|
102
105
|
fetchHealth?: (url: string) => Promise<{ ok: boolean; healthy: boolean }>;
|
|
106
|
+
linuxLauncherFile?: string;
|
|
103
107
|
};
|
|
104
108
|
|
|
105
109
|
const REAL_CURSOR_EXTENSIONS_DIR = '~/.cursor/extensions';
|
|
106
|
-
const PACKAGE_VERSION = '0.5.
|
|
110
|
+
const PACKAGE_VERSION = '0.5.8';
|
|
111
|
+
const AUTOSTART_SERVICE_STARTUP_TIMEOUT_MS = 30_000;
|
|
112
|
+
|
|
113
|
+
function delay(ms: number) {
|
|
114
|
+
return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
|
|
115
|
+
}
|
|
107
116
|
|
|
108
117
|
async function maybeAdHocResign({
|
|
109
118
|
appPath,
|
|
@@ -124,38 +133,124 @@ async function maybeAdHocResign({
|
|
|
124
133
|
return 'ad-hoc' as const;
|
|
125
134
|
}
|
|
126
135
|
|
|
127
|
-
|
|
136
|
+
function markerPredicateForPatchTarget(target: CompatibilityPatchTarget) {
|
|
137
|
+
if (target.name === 'workbench' || target.verifyMarker === 'cursor-pool-workbench') {
|
|
138
|
+
return containsCursorWorkbenchAuthGateMarker;
|
|
139
|
+
}
|
|
140
|
+
return containsCursorPoolMarker;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function assertExpectedHash(
|
|
144
|
+
targetPath: string,
|
|
145
|
+
expectedSha256: string,
|
|
146
|
+
containsExpectedMarker = containsCursorPoolMarker,
|
|
147
|
+
) {
|
|
128
148
|
const currentHash = await sha256File(targetPath);
|
|
129
149
|
if (currentHash !== expectedSha256) {
|
|
130
150
|
const content = await readFile(targetPath, 'utf8');
|
|
131
|
-
if (
|
|
151
|
+
if (containsExpectedMarker(content)) {
|
|
132
152
|
return;
|
|
133
153
|
}
|
|
134
154
|
throw new Error(`Patch target hash mismatch: expected ${expectedSha256}, got ${currentHash}`);
|
|
135
155
|
}
|
|
136
156
|
}
|
|
137
157
|
|
|
138
|
-
|
|
158
|
+
function compatPatchTargets(compat: CompatibilityManifestEntry): CompatibilityPatchTarget[] {
|
|
159
|
+
if (compat.patchTargets?.length) {
|
|
160
|
+
return compat.patchTargets;
|
|
161
|
+
}
|
|
162
|
+
return [
|
|
163
|
+
{
|
|
164
|
+
name: 'agent-exec',
|
|
165
|
+
targetRelativePath: compat.targetRelativePath,
|
|
166
|
+
expectedSha256: compat.expectedSha256,
|
|
167
|
+
patchStrategy: compat.patchStrategy,
|
|
168
|
+
verifyMarker: compat.verifyMarker,
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function workbenchTargetRelativePathFromCompat(compat: CompatibilityManifestEntry) {
|
|
174
|
+
return compat.patchTargets?.find((target) => target.name === 'workbench')?.targetRelativePath;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function assertExpectedPatchTargetHashes(appPath: string, compat: CompatibilityManifestEntry) {
|
|
178
|
+
for (const target of compatPatchTargets(compat)) {
|
|
179
|
+
await assertExpectedHash(
|
|
180
|
+
join(appPath, target.targetRelativePath),
|
|
181
|
+
target.expectedSha256,
|
|
182
|
+
markerPredicateForPatchTarget(target),
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function startServiceFromAutostart(options: InstallOptions, targetMode: 'real' | 'disposable') {
|
|
188
|
+
const autostart = await installAutostart(options, targetMode);
|
|
189
|
+
const runtimeFile = options.runtimeFile ?? DEFAULT_RUNTIME_FILE;
|
|
190
|
+
const fetchHealthFn = options.fetchHealth ?? fetchHealth;
|
|
191
|
+
const startedAt = Date.now();
|
|
192
|
+
let runtime: RuntimeInfo | null = null;
|
|
193
|
+
|
|
194
|
+
while (Date.now() - startedAt < AUTOSTART_SERVICE_STARTUP_TIMEOUT_MS) {
|
|
195
|
+
runtime = await readRuntimeInfo({ runtimeFile });
|
|
196
|
+
const health = runtime
|
|
197
|
+
? await fetchHealthFn(`http://${runtime.host}:${runtime.port}/health`)
|
|
198
|
+
: null;
|
|
199
|
+
if (runtime && health?.ok && health.healthy) {
|
|
200
|
+
return {
|
|
201
|
+
service: {
|
|
202
|
+
...runtime,
|
|
203
|
+
stop: () => stopRuntimeService(runtimeFile),
|
|
204
|
+
},
|
|
205
|
+
autostartState: autostart.state,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
await delay(50);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
throw new Error('Autostart service failed to become healthy');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function startInstallService(
|
|
215
|
+
options: InstallOptions,
|
|
216
|
+
targetMode: 'real' | 'disposable',
|
|
217
|
+
platform: NodeJS.Platform,
|
|
218
|
+
) {
|
|
139
219
|
if (options.stopServiceAfterInstall) {
|
|
140
220
|
const service = await startServer({ runtimeFile: options.runtimeFile });
|
|
141
221
|
return {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
222
|
+
service: {
|
|
223
|
+
host: service.host,
|
|
224
|
+
port: service.port,
|
|
225
|
+
runtimeId: service.runtimeId,
|
|
226
|
+
stop: service.stop,
|
|
227
|
+
},
|
|
228
|
+
autostartState: 'skipped' as const,
|
|
146
229
|
};
|
|
147
230
|
}
|
|
148
231
|
|
|
149
232
|
const startDetached = options.startDetachedService ?? startDetachedService;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
233
|
+
let runtime: RuntimeInfo;
|
|
234
|
+
try {
|
|
235
|
+
runtime = await startDetached({
|
|
236
|
+
runtimeFile: options.runtimeFile ?? DEFAULT_RUNTIME_FILE,
|
|
237
|
+
logFile: options.serviceLogFile ?? '~/.cursor-pool/logs/service.log',
|
|
238
|
+
configFile: options.clientConfigFile,
|
|
239
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
240
|
+
});
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (platform === 'linux' && targetMode === 'real') {
|
|
243
|
+
return startServiceFromAutostart(options, targetMode);
|
|
244
|
+
}
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
|
|
156
248
|
return {
|
|
157
|
-
|
|
158
|
-
|
|
249
|
+
service: {
|
|
250
|
+
...runtime,
|
|
251
|
+
stop: () => stopRuntimeService(options.runtimeFile ?? DEFAULT_RUNTIME_FILE),
|
|
252
|
+
},
|
|
253
|
+
autostartState: undefined,
|
|
159
254
|
};
|
|
160
255
|
}
|
|
161
256
|
|
|
@@ -184,6 +279,43 @@ async function installAutostart(options: InstallOptions, targetMode: 'real' | 'd
|
|
|
184
279
|
});
|
|
185
280
|
}
|
|
186
281
|
|
|
282
|
+
function defaultLinuxLauncherFile() {
|
|
283
|
+
return join(homedir(), '.local/bin/cursor-pool-cursor');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function shellSingleQuote(value: string) {
|
|
287
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function installLinuxLauncher({
|
|
291
|
+
appPath,
|
|
292
|
+
platform,
|
|
293
|
+
launcherFile,
|
|
294
|
+
}: {
|
|
295
|
+
appPath: string;
|
|
296
|
+
platform: NodeJS.Platform;
|
|
297
|
+
launcherFile?: string;
|
|
298
|
+
}) {
|
|
299
|
+
if (platform !== 'linux') {
|
|
300
|
+
return 'skipped';
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const targetFile = launcherFile ?? defaultLinuxLauncherFile();
|
|
304
|
+
await mkdir(dirname(targetFile), { recursive: true });
|
|
305
|
+
await writeFile(
|
|
306
|
+
targetFile,
|
|
307
|
+
[
|
|
308
|
+
'#!/usr/bin/env sh',
|
|
309
|
+
'set -eu',
|
|
310
|
+
`exec ${shellSingleQuote(join(appPath, 'AppRun'))} --no-sandbox "$@"`,
|
|
311
|
+
'',
|
|
312
|
+
].join('\n'),
|
|
313
|
+
'utf8',
|
|
314
|
+
);
|
|
315
|
+
await chmod(targetFile, 0o755);
|
|
316
|
+
return targetFile;
|
|
317
|
+
}
|
|
318
|
+
|
|
187
319
|
function sameRuntime(a: RuntimeInfo | null | undefined, b: RuntimeInfo | null | undefined) {
|
|
188
320
|
return Boolean(
|
|
189
321
|
a &&
|
|
@@ -286,11 +418,12 @@ async function linkInstallExtensionBundle({
|
|
|
286
418
|
sourceBundlePath: string;
|
|
287
419
|
cursorExtensionsDir: string;
|
|
288
420
|
}): ReturnType<typeof linkExtensionBundle> {
|
|
289
|
-
const linkedPath = linkedExtensionPathForDir(cursorExtensionsDir);
|
|
290
421
|
if ((await getLinkedExtensionState(sourceBundlePath)) === 'missing') {
|
|
422
|
+
const linkedPath = linkedExtensionPathForDir(cursorExtensionsDir);
|
|
291
423
|
return { state: 'missing', linkedPath };
|
|
292
424
|
}
|
|
293
425
|
|
|
426
|
+
const linkedPath = await linkedExtensionPathForSource(cursorExtensionsDir, sourceBundlePath);
|
|
294
427
|
await removeInstallLinkedExtensionBundle(linkedPath, cursorExtensionsDir);
|
|
295
428
|
await mkdir(dirname(linkedPath), { recursive: true });
|
|
296
429
|
await cp(sourceBundlePath, linkedPath, { recursive: true });
|
|
@@ -314,13 +447,19 @@ export async function install(options: InstallOptions = {}) {
|
|
|
314
447
|
compatManifestUrl: options.compatManifestUrl,
|
|
315
448
|
fetchManifest: options.fetchCompatManifest,
|
|
316
449
|
});
|
|
317
|
-
const
|
|
450
|
+
const adapterVersion = PACKAGE_VERSION === '0.5.8'
|
|
451
|
+
? undefined
|
|
452
|
+
: PACKAGE_VERSION.split('-', 1)[0].split('+', 1)[0];
|
|
453
|
+
const compat = resolveCompatEntry(cursor, environment, { entries: compatEntries, adapterVersion });
|
|
454
|
+
assertBundledTakeoverPlan(compat.takeoverPlanId);
|
|
455
|
+
const workbenchTargetRelativePath = workbenchTargetRelativePathFromCompat(compat);
|
|
318
456
|
const targetPath = join(cursor.appPath, compat.targetRelativePath);
|
|
319
457
|
const originalSha256 = await sha256File(targetPath);
|
|
320
|
-
await
|
|
458
|
+
await assertExpectedPatchTargetHashes(cursor.appPath, compat);
|
|
321
459
|
const patchSetBeforeInstall = await readCursorPatchSetState(cursor.appPath, {
|
|
322
460
|
agentExecTargetRelativePath: compat.targetRelativePath,
|
|
323
461
|
platform: environment.platform,
|
|
462
|
+
workbenchTargetRelativePath,
|
|
324
463
|
});
|
|
325
464
|
const wasPatchedBeforeInstall = patchSetBeforeInstall.allApplied;
|
|
326
465
|
const existingTrialRecord = await readTrialRecord(cursor.appPath, {
|
|
@@ -348,6 +487,7 @@ export async function install(options: InstallOptions = {}) {
|
|
|
348
487
|
let resignState: 'ad-hoc' | 'skipped' = 'skipped';
|
|
349
488
|
let patchState = 'skipped';
|
|
350
489
|
let autostartState = 'skipped';
|
|
490
|
+
let launcherState = 'skipped';
|
|
351
491
|
|
|
352
492
|
try {
|
|
353
493
|
if (target.requiresConfirmation) {
|
|
@@ -404,12 +544,14 @@ export async function install(options: InstallOptions = {}) {
|
|
|
404
544
|
}
|
|
405
545
|
|
|
406
546
|
await writeInstallClientConfig(options);
|
|
407
|
-
|
|
408
|
-
|
|
547
|
+
const started = await startInstallService(options, target.mode, environment.platform);
|
|
548
|
+
service = started.service;
|
|
549
|
+
autostartState = started.autostartState ?? (await installAutostart(options, target.mode)).state;
|
|
409
550
|
const patchSet = await patchCursorSet(cursor.appPath, {
|
|
410
551
|
backupDir: options.backupDir,
|
|
411
552
|
platform: environment.platform,
|
|
412
553
|
agentExecTargetRelativePath: compat.targetRelativePath,
|
|
554
|
+
workbenchTargetRelativePath,
|
|
413
555
|
patchCursorAgentExec: options.patchCursorAgentExec,
|
|
414
556
|
patchCursorWorkbenchAuthGate: options.patchCursorWorkbenchAuthGate,
|
|
415
557
|
});
|
|
@@ -421,6 +563,11 @@ export async function install(options: InstallOptions = {}) {
|
|
|
421
563
|
requiresAdHocResign: compat.requiresAdHocResign,
|
|
422
564
|
resign: options.adHocResignApp ?? adHocResignApp,
|
|
423
565
|
});
|
|
566
|
+
launcherState = await installLinuxLauncher({
|
|
567
|
+
appPath: cursor.appPath,
|
|
568
|
+
platform: environment.platform,
|
|
569
|
+
launcherFile: options.linuxLauncherFile,
|
|
570
|
+
});
|
|
424
571
|
|
|
425
572
|
const health = await (options.fetchHealth ?? fetchHealth)(
|
|
426
573
|
`http://${service.host}:${service.port}/health`,
|
|
@@ -434,6 +581,7 @@ export async function install(options: InstallOptions = {}) {
|
|
|
434
581
|
appPath: cursor.appPath,
|
|
435
582
|
cursorVersion: cursor.version,
|
|
436
583
|
cursorCommit: cursor.commit,
|
|
584
|
+
takeoverPlanId: compat.takeoverPlanId,
|
|
437
585
|
targetRelativePath: compat.targetRelativePath,
|
|
438
586
|
originalSha256,
|
|
439
587
|
compatSupportStatus: compat.supportStatus,
|
|
@@ -478,11 +626,13 @@ export async function install(options: InstallOptions = {}) {
|
|
|
478
626
|
`mode: ${target.mode}`,
|
|
479
627
|
`app: ${cursor.appPath}`,
|
|
480
628
|
`compat: ${compat.supportStatus}`,
|
|
629
|
+
`takeover-plan: ${compat.takeoverPlanId}`,
|
|
481
630
|
`extension: ${extension.state}`,
|
|
482
631
|
`service: running ${service.host}:${service.port}`,
|
|
483
632
|
`autostart: ${autostartState}`,
|
|
484
633
|
`patch: ${patchState}`,
|
|
485
634
|
`resign: ${resignState}`,
|
|
635
|
+
`launcher: ${launcherState}`,
|
|
486
636
|
'health: ok',
|
|
487
637
|
target.mode === 'real' ? 'install-record: recorded' : 'trial: recorded',
|
|
488
638
|
].join('\n');
|
|
@@ -496,6 +646,7 @@ export async function install(options: InstallOptions = {}) {
|
|
|
496
646
|
(await readCursorPatchSetState(cursor.appPath, {
|
|
497
647
|
agentExecTargetRelativePath: compat.targetRelativePath,
|
|
498
648
|
platform: environment.platform,
|
|
649
|
+
workbenchTargetRelativePath,
|
|
499
650
|
})).appliedCount > 0);
|
|
500
651
|
} catch (rollbackError) {
|
|
501
652
|
rollbackErrors.push(rollbackError);
|
|
@@ -505,6 +656,7 @@ export async function install(options: InstallOptions = {}) {
|
|
|
505
656
|
await restoreCursorSet(cursor.appPath, {
|
|
506
657
|
backupDir: options.backupDir,
|
|
507
658
|
platform: environment.platform,
|
|
659
|
+
workbenchTargetRelativePath,
|
|
508
660
|
restoreCursorAgentExec: options.restoreCursorAgentExec,
|
|
509
661
|
restoreCursorAlwaysLocal: options.restoreCursorAlwaysLocal,
|
|
510
662
|
restoreCursorWorkbenchAuthGate: options.restoreCursorWorkbenchAuthGate,
|
package/src/installRecord.ts
CHANGED
|
@@ -13,6 +13,7 @@ export type InstallRecord = {
|
|
|
13
13
|
appPath: string;
|
|
14
14
|
cursorVersion: string;
|
|
15
15
|
cursorCommit: string;
|
|
16
|
+
takeoverPlanId: string;
|
|
16
17
|
targetRelativePath: string;
|
|
17
18
|
originalSha256: string;
|
|
18
19
|
compatSupportStatus: CompatibilityManifestEntry['supportStatus'];
|
|
@@ -91,6 +92,7 @@ export function isInstallRecordStale(record: InstallRecord | null, actual: Insta
|
|
|
91
92
|
'appPath',
|
|
92
93
|
'cursorVersion',
|
|
93
94
|
'cursorCommit',
|
|
95
|
+
'takeoverPlanId',
|
|
94
96
|
'targetRelativePath',
|
|
95
97
|
'originalSha256',
|
|
96
98
|
'compatSupportStatus',
|
package/src/patchSet.ts
CHANGED
|
@@ -144,6 +144,10 @@ export async function patchCursorSet(appPath: string, options: PatchCursorSetOpt
|
|
|
144
144
|
|
|
145
145
|
export async function restoreCursorSet(appPath: string, options: RestoreCursorSetOptions = {}) {
|
|
146
146
|
const restoreErrors: unknown[] = [];
|
|
147
|
+
const before = await readCursorPatchSetState(appPath, {
|
|
148
|
+
platform: options.platform,
|
|
149
|
+
workbenchTargetRelativePath: options.workbenchTargetRelativePath,
|
|
150
|
+
});
|
|
147
151
|
const restore = async (operation: () => Promise<unknown>) => {
|
|
148
152
|
try {
|
|
149
153
|
await operation();
|
|
@@ -152,12 +156,14 @@ export async function restoreCursorSet(appPath: string, options: RestoreCursorSe
|
|
|
152
156
|
}
|
|
153
157
|
};
|
|
154
158
|
|
|
155
|
-
|
|
156
|
-
(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
if (before.patches.some((patch) => patch.name === 'workbench' && patch.markerPresent)) {
|
|
160
|
+
await restore(() =>
|
|
161
|
+
(options.restoreCursorWorkbenchAuthGate ?? restoreCursorWorkbenchAuthGate)(appPath, {
|
|
162
|
+
backupDir: options.backupDir,
|
|
163
|
+
targetRelativePath: workbenchRelativePath(options),
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
161
167
|
const alwaysLocalPath = resolveCursorAlwaysLocalPath(appPath, alwaysLocalRelativePath(options));
|
|
162
168
|
const shouldRestoreAlwaysLocal =
|
|
163
169
|
Boolean(options.restoreCursorAlwaysLocal) ||
|
package/src/platform.ts
CHANGED
|
@@ -82,9 +82,9 @@ function buildDeviceInfo() {
|
|
|
82
82
|
name: hostname(),
|
|
83
83
|
os: osPlatform(),
|
|
84
84
|
arch: arch(),
|
|
85
|
-
cliVersion: '0.5.
|
|
86
|
-
serviceVersion: '0.5.
|
|
87
|
-
extensionVersion: '0.5.
|
|
85
|
+
cliVersion: '0.5.8',
|
|
86
|
+
serviceVersion: '0.5.8',
|
|
87
|
+
extensionVersion: '0.5.8',
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
|
package/src/repair.ts
CHANGED
|
@@ -69,7 +69,7 @@ export type RepairOptions = FindCursorOptions &
|
|
|
69
69
|
adHocResignApp?: typeof adHocResignApp;
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
const PACKAGE_VERSION = '0.5.
|
|
72
|
+
const PACKAGE_VERSION = '0.5.8';
|
|
73
73
|
|
|
74
74
|
function normalizeAppPath(path: string) {
|
|
75
75
|
return normalize(path).replace(/\/+$/, '');
|
|
@@ -96,6 +96,10 @@ function assertRealRepairInstallRecord(
|
|
|
96
96
|
return record;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
function workbenchTargetRelativePathFromCompat(compat: CompatibilityManifestEntry) {
|
|
100
|
+
return compat.patchTargets?.find((target) => target.name === 'workbench')?.targetRelativePath;
|
|
101
|
+
}
|
|
102
|
+
|
|
99
103
|
function resolveRepairCompatEntry({
|
|
100
104
|
cursorVersion,
|
|
101
105
|
cursorCommit,
|
|
@@ -223,6 +227,7 @@ export async function repair(options: RepairOptions = {}) {
|
|
|
223
227
|
if (compat.supportStatus === 'blocked' || compat.supportStatus === 'unknown') {
|
|
224
228
|
throw new Error(`compat: ${compat.supportStatus}`);
|
|
225
229
|
}
|
|
230
|
+
const workbenchTargetRelativePath = workbenchTargetRelativePathFromCompat(compat);
|
|
226
231
|
const targetPath = join(cursor.appPath, compat.targetRelativePath);
|
|
227
232
|
const runtimeFile = options.runtimeFile ?? installRecord.runtimeFile ?? DEFAULT_RUNTIME_FILE;
|
|
228
233
|
const backupDir = options.backupDir ?? installRecord.backupDir;
|
|
@@ -323,6 +328,8 @@ export async function repair(options: RepairOptions = {}) {
|
|
|
323
328
|
|
|
324
329
|
const patchSetState = await readCursorPatchSetState(cursor.appPath, {
|
|
325
330
|
agentExecTargetRelativePath: compat.targetRelativePath,
|
|
331
|
+
platform: environment.platform,
|
|
332
|
+
workbenchTargetRelativePath,
|
|
326
333
|
});
|
|
327
334
|
|
|
328
335
|
if (patchSetState.allApplied) {
|
|
@@ -331,7 +338,9 @@ export async function repair(options: RepairOptions = {}) {
|
|
|
331
338
|
} else {
|
|
332
339
|
const repairedPatchSet = await patchCursorSet(cursor.appPath, {
|
|
333
340
|
backupDir,
|
|
341
|
+
platform: environment.platform,
|
|
334
342
|
agentExecTargetRelativePath: compat.targetRelativePath,
|
|
343
|
+
workbenchTargetRelativePath,
|
|
335
344
|
patchCursorAgentExec: options.patchCursorAgentExec,
|
|
336
345
|
patchCursorWorkbenchAuthGate: options.patchCursorWorkbenchAuthGate,
|
|
337
346
|
});
|
package/src/restore.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { normalize } from 'node:path';
|
|
2
|
-
import {
|
|
2
|
+
import { resolveCursorAgentExecPath } from '@cursor-pool/patcher';
|
|
3
3
|
import { DEFAULT_RUNTIME_FILE } from '@cursor-pool/shared/runtime';
|
|
4
4
|
import {
|
|
5
5
|
confirmRealOperation,
|
|
@@ -14,6 +14,11 @@ import {
|
|
|
14
14
|
type InstallRecordOptions,
|
|
15
15
|
} from './installRecord';
|
|
16
16
|
import { stopRuntimeService } from './serviceProcess';
|
|
17
|
+
import {
|
|
18
|
+
formatCursorPatchSetState,
|
|
19
|
+
readCursorPatchSetState,
|
|
20
|
+
restoreCursorSet,
|
|
21
|
+
} from './patchSet';
|
|
17
22
|
import { resolveCursorTarget, type CursorTargetOptions } from './target';
|
|
18
23
|
import { assertDisposableCursorAppPath, readTrialRecord, type TrialRecordOptions } from './trial';
|
|
19
24
|
|
|
@@ -132,9 +137,12 @@ export async function restore(options: RestoreOptions = {}) {
|
|
|
132
137
|
});
|
|
133
138
|
}
|
|
134
139
|
|
|
135
|
-
|
|
140
|
+
await restoreCursorSet(cursor.appPath, {
|
|
136
141
|
backupDir,
|
|
137
|
-
targetRelativePath,
|
|
142
|
+
agentExecTargetRelativePath: targetRelativePath,
|
|
143
|
+
});
|
|
144
|
+
const patchSetState = await readCursorPatchSetState(cursor.appPath, {
|
|
145
|
+
agentExecTargetRelativePath: targetRelativePath,
|
|
138
146
|
});
|
|
139
147
|
if (target.mode === 'real') {
|
|
140
148
|
await stopRuntimeServiceIfRecorded(runtimeFile);
|
|
@@ -145,7 +153,7 @@ export async function restore(options: RestoreOptions = {}) {
|
|
|
145
153
|
`mode: ${target.mode}`,
|
|
146
154
|
`app: ${cursor.appPath}`,
|
|
147
155
|
'restore: ok',
|
|
148
|
-
`patch: ${
|
|
156
|
+
`patch: ${formatCursorPatchSetState(patchSetState)}`,
|
|
149
157
|
target.mode === 'real'
|
|
150
158
|
? `install-record: ${realInstallRecord ? 'recorded' : 'missing'}`
|
|
151
159
|
: `trial: ${trialRecord ? 'recorded' : 'missing'}`,
|
package/src/serviceProcess.ts
CHANGED
|
@@ -18,6 +18,7 @@ export type StartDetachedServiceOptions = {
|
|
|
18
18
|
const require = createRequire(import.meta.url);
|
|
19
19
|
const DEFAULT_SERVICE_ENTRY = require.resolve('@cursor-pool/service/entry');
|
|
20
20
|
const TSX_ESM_LOADER = require.resolve('tsx/esm');
|
|
21
|
+
export const DEFAULT_DETACHED_SERVICE_STARTUP_TIMEOUT_MS = 30_000;
|
|
21
22
|
function resolveTildePath(path: string) {
|
|
22
23
|
if (path.startsWith('~/')) {
|
|
23
24
|
return join(homedir(), path.slice(2));
|
|
@@ -132,7 +133,7 @@ export async function startDetachedService(
|
|
|
132
133
|
let healthy = false;
|
|
133
134
|
try {
|
|
134
135
|
const startedAt = Date.now();
|
|
135
|
-
const timeoutMs = options.startupTimeoutMs ??
|
|
136
|
+
const timeoutMs = options.startupTimeoutMs ?? DEFAULT_DETACHED_SERVICE_STARTUP_TIMEOUT_MS;
|
|
136
137
|
while (Date.now() - startedAt < timeoutMs) {
|
|
137
138
|
if (startupError) {
|
|
138
139
|
throw startupError;
|
package/src/status.ts
CHANGED
|
@@ -49,6 +49,10 @@ type LatestTakeoverResponse = {
|
|
|
49
49
|
};
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
function workbenchTargetRelativePathFromCompat(compat: CompatibilityManifestEntry) {
|
|
53
|
+
return compat.patchTargets?.find((target) => target.name === 'workbench')?.targetRelativePath;
|
|
54
|
+
}
|
|
55
|
+
|
|
52
56
|
const MAX_CANARY_FIELD_LENGTH = 256;
|
|
53
57
|
|
|
54
58
|
function isSafeCanaryString(value: unknown): value is string {
|
|
@@ -158,6 +162,7 @@ export async function status(options: StatusOptions = {}) {
|
|
|
158
162
|
fetchManifest: options.fetchCompatManifest,
|
|
159
163
|
});
|
|
160
164
|
const compat = resolveCompatEntry(cursor, environment, { entries: compatEntries });
|
|
165
|
+
const workbenchTargetRelativePath = workbenchTargetRelativePathFromCompat(compat);
|
|
161
166
|
const installRecord =
|
|
162
167
|
target.mode === 'real'
|
|
163
168
|
? await readInstallRecord({ installRecordFile: options.installRecordFile })
|
|
@@ -189,6 +194,7 @@ export async function status(options: StatusOptions = {}) {
|
|
|
189
194
|
const patchSetState = await readCursorPatchSetState(cursor.appPath, {
|
|
190
195
|
agentExecTargetRelativePath: compat.targetRelativePath,
|
|
191
196
|
platform: environment.platform,
|
|
197
|
+
workbenchTargetRelativePath,
|
|
192
198
|
});
|
|
193
199
|
const serviceRunning = await isRuntimeHealthy(runtime);
|
|
194
200
|
const takeoverStatus = await getTakeoverStatus(runtime, serviceRunning);
|
package/src/trial.ts
CHANGED
|
@@ -12,6 +12,7 @@ export type TrialRecord = {
|
|
|
12
12
|
appPath: string;
|
|
13
13
|
cursorVersion: string;
|
|
14
14
|
cursorCommit: string;
|
|
15
|
+
takeoverPlanId: string;
|
|
15
16
|
targetRelativePath: string;
|
|
16
17
|
originalSha256: string;
|
|
17
18
|
compatSupportStatus: 'supported' | 'canary' | 'warning' | 'blocked' | 'unknown';
|
package/test/autostart.test.ts
CHANGED
|
@@ -125,7 +125,7 @@ test('installUserAutostart writes a Linux user systemd service', async () => {
|
|
|
125
125
|
linuxServiceFile: serviceFile,
|
|
126
126
|
nodeCommand: '/usr/bin/node',
|
|
127
127
|
cliEntry: '/home/demo/.cursor-pool/client/node_modules/@cursorpool-dev/cli/bin/cursor-pool.mjs',
|
|
128
|
-
tsxLoader: '/home/demo/.cursor-pool/client/node_modules/tsx/dist/esm/index.mjs',
|
|
128
|
+
tsxLoader: '/home/demo/.cursor-pool/client/node_modules/@cursorpool-dev/cli/node_modules/tsx/dist/esm/index.mjs',
|
|
129
129
|
serviceEntry: '/home/demo/.cursor-pool/client/node_modules/@cursorpool-dev/cli/node_modules/@cursor-pool/service/src/entry.ts',
|
|
130
130
|
runtimeFile: '/home/demo/.cursor-pool/runtime.json',
|
|
131
131
|
serviceLogFile: '/home/demo/.cursor-pool/logs/service.log',
|
|
@@ -138,10 +138,11 @@ test('installUserAutostart writes a Linux user systemd service', async () => {
|
|
|
138
138
|
|
|
139
139
|
assert.equal(result.state, 'installed');
|
|
140
140
|
const service = await readFile(serviceFile, 'utf8');
|
|
141
|
-
assert.match(service, /ExecStart=\/usr\/bin\/node --import
|
|
141
|
+
assert.match(service, /ExecStart=\/usr\/bin\/node --import .*@cursorpool-dev\/cli\/node_modules\/tsx\/dist\/esm\/index\.mjs .*service\/src\/entry\.ts --runtime-file/);
|
|
142
142
|
assert.match(service, /--config-file/);
|
|
143
143
|
assert.match(service, /Restart=always/);
|
|
144
144
|
assert.deepEqual(calls, [
|
|
145
|
+
['--user', 'unmask', 'cursor-pool.service'],
|
|
145
146
|
['--user', 'daemon-reload'],
|
|
146
147
|
['--user', 'enable', '--now', 'cursor-pool.service'],
|
|
147
148
|
]);
|
|
@@ -149,3 +150,23 @@ test('installUserAutostart writes a Linux user systemd service', async () => {
|
|
|
149
150
|
await rm(tempDir, { recursive: true, force: true });
|
|
150
151
|
}
|
|
151
152
|
});
|
|
153
|
+
|
|
154
|
+
test('installUserAutostart resolves the default tsx loader from the packaged CLI dependency tree', async () => {
|
|
155
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-autostart-default-tsx-'));
|
|
156
|
+
const serviceFile = join(tempDir, 'systemd/cursor-pool.service');
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
await installUserAutostart({
|
|
160
|
+
platform: 'linux',
|
|
161
|
+
linuxServiceFile: serviceFile,
|
|
162
|
+
nodeCommand: '/usr/bin/node',
|
|
163
|
+
execFile: async () => ({ stdout: '', stderr: '' }),
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const service = await readFile(serviceFile, 'utf8');
|
|
167
|
+
assert.match(service, /--import .*tsx\/dist\/esm\/index\.mjs/);
|
|
168
|
+
assert.doesNotMatch(service, /\.cursor-pool\/client\/node_modules\/tsx\/dist\/esm\/index\.mjs/);
|
|
169
|
+
} finally {
|
|
170
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
171
|
+
}
|
|
172
|
+
});
|