@cursorpool-dev/cli 0.5.6 → 0.5.9
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/marker.ts +72 -3
- package/node_modules/@cursor-pool/patcher/src/workbenchAuthGateMarker.ts +128 -14
- package/node_modules/@cursor-pool/patcher/test/patchCursorAgentExec.test.ts +102 -7
- package/node_modules/@cursor-pool/patcher/test/patchCursorWorkbench.test.ts +193 -0
- package/node_modules/@cursor-pool/service/package.json +2 -2
- package/node_modules/@cursor-pool/service/src/server.ts +1 -0
- package/node_modules/@cursor-pool/service/test/server.test.ts +1 -0
- package/node_modules/@cursor-pool/shared/package.json +1 -1
- package/node_modules/@cursor-pool/shared/test/manifest.test.ts +6 -6
- 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 +9 -6
- package/src/autostart.ts +5 -1
- package/src/compat.ts +103 -29
- package/src/cursor.ts +59 -3
- package/src/extensionBundle.ts +1 -1
- package/src/extensionLink.ts +3 -3
- package/src/install.ts +118 -19
- package/src/platform.ts +3 -3
- package/src/repair.ts +2 -1
- package/src/serviceProcess.ts +2 -1
- package/src/trial.ts +2 -2
- package/test/autostart.test.ts +23 -2
- package/test/compat.test.ts +108 -9
- package/test/cursor-pool-bin.test.ts +1 -0
- package/test/cursor.test.ts +60 -1
- package/test/extensionLink.test.ts +24 -1
- package/test/install.test.ts +127 -2
- package/test/serviceProcess.test.ts +10 -1
- package/test/trial.test.ts +15 -1
package/src/cursor.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { chmod, mkdir, readFile, rm, stat } from 'node:fs/promises';
|
|
5
|
+
import { basename, join, win32 } from 'node:path';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
3
7
|
|
|
4
8
|
export const DEFAULT_MACOS_CURSOR_APP_PATH = '/Applications/Cursor.app';
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
5
10
|
|
|
6
11
|
export type CursorInfo = {
|
|
7
12
|
appPath: string;
|
|
@@ -14,6 +19,7 @@ export type FindCursorOptions = {
|
|
|
14
19
|
productRelativePath?: string;
|
|
15
20
|
platform?: NodeJS.Platform;
|
|
16
21
|
env?: NodeJS.ProcessEnv;
|
|
22
|
+
appImageExtract?: (appImagePath: string, outputRoot: string) => Promise<string>;
|
|
17
23
|
};
|
|
18
24
|
|
|
19
25
|
type CursorProductJson = {
|
|
@@ -43,7 +49,8 @@ function readString(value: unknown) {
|
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
export async function findCursor(options: FindCursorOptions = {}): Promise<CursorInfo> {
|
|
46
|
-
const
|
|
52
|
+
const rawAppPath = options.appPath ?? defaultCursorAppPath(options);
|
|
53
|
+
const appPath = await resolveCursorAppPath(rawAppPath, options);
|
|
47
54
|
const productJsonPath = await resolveProductJsonPath(appPath, options);
|
|
48
55
|
const product = JSON.parse(await readFile(productJsonPath, 'utf8')) as CursorProductJson;
|
|
49
56
|
const version = readString(product.version);
|
|
@@ -66,6 +73,55 @@ export async function findCursor(options: FindCursorOptions = {}): Promise<Curso
|
|
|
66
73
|
};
|
|
67
74
|
}
|
|
68
75
|
|
|
76
|
+
async function pathIsFile(path: string) {
|
|
77
|
+
try {
|
|
78
|
+
return (await stat(path)).isFile();
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function linuxAppImageExtractRoot(appImagePath: string) {
|
|
85
|
+
const hash = createHash('sha256').update(appImagePath).digest('hex').slice(0, 16);
|
|
86
|
+
const name = basename(appImagePath).replace(/\.AppImage$/i, '').replace(/[^A-Za-z0-9._-]/g, '-');
|
|
87
|
+
return join(homedir(), '.cursor-pool/appimages', `${name}-${hash}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function defaultExtractLinuxAppImage(appImagePath: string, outputRoot: string) {
|
|
91
|
+
await rm(outputRoot, { recursive: true, force: true });
|
|
92
|
+
await mkdir(outputRoot, { recursive: true });
|
|
93
|
+
await chmod(appImagePath, 0o755).catch(() => undefined);
|
|
94
|
+
await execFileAsync(appImagePath, ['--appimage-extract'], {
|
|
95
|
+
cwd: outputRoot,
|
|
96
|
+
maxBuffer: 1024 * 1024 * 20,
|
|
97
|
+
});
|
|
98
|
+
return join(outputRoot, 'squashfs-root');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function resolveCursorAppPath(appPath: string, options: FindCursorOptions) {
|
|
102
|
+
if (
|
|
103
|
+
(options.platform ?? process.platform) !== 'linux' ||
|
|
104
|
+
!/\.AppImage$/i.test(appPath) ||
|
|
105
|
+
!(await pathIsFile(appPath))
|
|
106
|
+
) {
|
|
107
|
+
return appPath;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const outputRoot = linuxAppImageExtractRoot(appPath);
|
|
111
|
+
const extractedPath = join(outputRoot, 'squashfs-root');
|
|
112
|
+
try {
|
|
113
|
+
await readFile(join(extractedPath, 'usr/share/cursor/resources/app/product.json'), 'utf8');
|
|
114
|
+
return extractedPath;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const extract = options.appImageExtract ?? defaultExtractLinuxAppImage;
|
|
122
|
+
return extract(appPath, outputRoot);
|
|
123
|
+
}
|
|
124
|
+
|
|
69
125
|
async function resolveProductJsonPath(appPath: string, options: FindCursorOptions) {
|
|
70
126
|
if (options.productRelativePath) {
|
|
71
127
|
return join(appPath, options.productRelativePath);
|
package/src/extensionBundle.ts
CHANGED
|
@@ -55,7 +55,7 @@ async function exists(path: string) {
|
|
|
55
55
|
function buildRuntimeManifest(sourceManifest: Record<string, unknown>) {
|
|
56
56
|
return {
|
|
57
57
|
name: 'cursorpool',
|
|
58
|
-
version: typeof sourceManifest.version === 'string' ? sourceManifest.version : '0.5.
|
|
58
|
+
version: typeof sourceManifest.version === 'string' ? sourceManifest.version : '0.5.9',
|
|
59
59
|
displayName: 'Cursor Pool 平台模式',
|
|
60
60
|
publisher: 'cursor-pool',
|
|
61
61
|
type: sourceManifest.type,
|
package/src/extensionLink.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { basename, dirname, join, resolve } from 'node:path';
|
|
|
4
4
|
import { resolveExtensionInstallPath } from './extensionBundle';
|
|
5
5
|
import type { ExtensionState } from './trial';
|
|
6
6
|
|
|
7
|
-
export const LINKED_EXTENSION_DIRNAME = 'cursor-pool.extension-0.5.
|
|
7
|
+
export const LINKED_EXTENSION_DIRNAME = 'cursor-pool.extension-0.5.9';
|
|
8
8
|
const RUNTIME_EXTENSION_ID = 'cursor-pool.cursorpool';
|
|
9
9
|
const STALE_EXTENSION_IDS = new Set([
|
|
10
10
|
RUNTIME_EXTENSION_ID,
|
|
@@ -48,7 +48,7 @@ async function readRuntimeManifest(linkedPath: string) {
|
|
|
48
48
|
};
|
|
49
49
|
const publisher = typeof manifest.publisher === 'string' ? manifest.publisher : 'cursor-pool';
|
|
50
50
|
const name = typeof manifest.name === 'string' ? manifest.name : 'cursorpool';
|
|
51
|
-
const version = typeof manifest.version === 'string' ? manifest.version : '0.5.
|
|
51
|
+
const version = typeof manifest.version === 'string' ? manifest.version : '0.5.9';
|
|
52
52
|
return { id: `${publisher}.${name}`, version };
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -100,7 +100,7 @@ function assertSafeLinkedExtensionPath(linkedPath: string) {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
export function linkedExtensionPathForDir(cursorExtensionsDir: string) {
|
|
103
|
-
return join(resolveExtensionInstallPath(cursorExtensionsDir), LINKED_EXTENSION_DIRNAME);
|
|
103
|
+
return join(resolve(resolveExtensionInstallPath(cursorExtensionsDir)), LINKED_EXTENSION_DIRNAME);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
export async function getLinkedExtensionState(linkedPath: string | undefined): Promise<LinkedExtensionState> {
|
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,
|
|
@@ -100,10 +100,16 @@ export type InstallOptions = FindCursorOptions &
|
|
|
100
100
|
restoreCursorWorkbenchAuthGate?: typeof restoreCursorWorkbenchAuthGate;
|
|
101
101
|
adHocResignApp?: typeof adHocResignApp;
|
|
102
102
|
fetchHealth?: (url: string) => Promise<{ ok: boolean; healthy: boolean }>;
|
|
103
|
+
linuxLauncherFile?: string;
|
|
103
104
|
};
|
|
104
105
|
|
|
105
106
|
const REAL_CURSOR_EXTENSIONS_DIR = '~/.cursor/extensions';
|
|
106
|
-
const PACKAGE_VERSION = '0.5.
|
|
107
|
+
const PACKAGE_VERSION = '0.5.9';
|
|
108
|
+
const AUTOSTART_SERVICE_STARTUP_TIMEOUT_MS = 30_000;
|
|
109
|
+
|
|
110
|
+
function delay(ms: number) {
|
|
111
|
+
return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
|
|
112
|
+
}
|
|
107
113
|
|
|
108
114
|
async function maybeAdHocResign({
|
|
109
115
|
appPath,
|
|
@@ -135,27 +141,73 @@ async function assertExpectedHash(targetPath: string, expectedSha256: string) {
|
|
|
135
141
|
}
|
|
136
142
|
}
|
|
137
143
|
|
|
138
|
-
async function
|
|
144
|
+
async function startServiceFromAutostart(options: InstallOptions, targetMode: 'real' | 'disposable') {
|
|
145
|
+
const autostart = await installAutostart(options, targetMode);
|
|
146
|
+
const runtimeFile = options.runtimeFile ?? DEFAULT_RUNTIME_FILE;
|
|
147
|
+
const fetchHealthFn = options.fetchHealth ?? fetchHealth;
|
|
148
|
+
const startedAt = Date.now();
|
|
149
|
+
let runtime: RuntimeInfo | null = null;
|
|
150
|
+
|
|
151
|
+
while (Date.now() - startedAt < AUTOSTART_SERVICE_STARTUP_TIMEOUT_MS) {
|
|
152
|
+
runtime = await readRuntimeInfo({ runtimeFile });
|
|
153
|
+
const health = runtime
|
|
154
|
+
? await fetchHealthFn(`http://${runtime.host}:${runtime.port}/health`)
|
|
155
|
+
: null;
|
|
156
|
+
if (runtime && health?.ok && health.healthy) {
|
|
157
|
+
return {
|
|
158
|
+
service: {
|
|
159
|
+
...runtime,
|
|
160
|
+
stop: () => stopRuntimeService(runtimeFile),
|
|
161
|
+
},
|
|
162
|
+
autostartState: autostart.state,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
await delay(50);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
throw new Error('Autostart service failed to become healthy');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function startInstallService(
|
|
172
|
+
options: InstallOptions,
|
|
173
|
+
targetMode: 'real' | 'disposable',
|
|
174
|
+
platform: NodeJS.Platform,
|
|
175
|
+
) {
|
|
139
176
|
if (options.stopServiceAfterInstall) {
|
|
140
177
|
const service = await startServer({ runtimeFile: options.runtimeFile });
|
|
141
178
|
return {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
179
|
+
service: {
|
|
180
|
+
host: service.host,
|
|
181
|
+
port: service.port,
|
|
182
|
+
runtimeId: service.runtimeId,
|
|
183
|
+
stop: service.stop,
|
|
184
|
+
},
|
|
185
|
+
autostartState: 'skipped' as const,
|
|
146
186
|
};
|
|
147
187
|
}
|
|
148
188
|
|
|
149
189
|
const startDetached = options.startDetachedService ?? startDetachedService;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
190
|
+
let runtime: RuntimeInfo;
|
|
191
|
+
try {
|
|
192
|
+
runtime = await startDetached({
|
|
193
|
+
runtimeFile: options.runtimeFile ?? DEFAULT_RUNTIME_FILE,
|
|
194
|
+
logFile: options.serviceLogFile ?? '~/.cursor-pool/logs/service.log',
|
|
195
|
+
configFile: options.clientConfigFile,
|
|
196
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
197
|
+
});
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (platform === 'linux' && targetMode === 'real') {
|
|
200
|
+
return startServiceFromAutostart(options, targetMode);
|
|
201
|
+
}
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
|
|
156
205
|
return {
|
|
157
|
-
|
|
158
|
-
|
|
206
|
+
service: {
|
|
207
|
+
...runtime,
|
|
208
|
+
stop: () => stopRuntimeService(options.runtimeFile ?? DEFAULT_RUNTIME_FILE),
|
|
209
|
+
},
|
|
210
|
+
autostartState: undefined,
|
|
159
211
|
};
|
|
160
212
|
}
|
|
161
213
|
|
|
@@ -184,6 +236,43 @@ async function installAutostart(options: InstallOptions, targetMode: 'real' | 'd
|
|
|
184
236
|
});
|
|
185
237
|
}
|
|
186
238
|
|
|
239
|
+
function defaultLinuxLauncherFile() {
|
|
240
|
+
return join(homedir(), '.local/bin/cursor-pool-cursor');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function shellSingleQuote(value: string) {
|
|
244
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function installLinuxLauncher({
|
|
248
|
+
appPath,
|
|
249
|
+
platform,
|
|
250
|
+
launcherFile,
|
|
251
|
+
}: {
|
|
252
|
+
appPath: string;
|
|
253
|
+
platform: NodeJS.Platform;
|
|
254
|
+
launcherFile?: string;
|
|
255
|
+
}) {
|
|
256
|
+
if (platform !== 'linux') {
|
|
257
|
+
return 'skipped';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const targetFile = launcherFile ?? defaultLinuxLauncherFile();
|
|
261
|
+
await mkdir(dirname(targetFile), { recursive: true });
|
|
262
|
+
await writeFile(
|
|
263
|
+
targetFile,
|
|
264
|
+
[
|
|
265
|
+
'#!/usr/bin/env sh',
|
|
266
|
+
'set -eu',
|
|
267
|
+
`exec ${shellSingleQuote(join(appPath, 'AppRun'))} --no-sandbox "$@"`,
|
|
268
|
+
'',
|
|
269
|
+
].join('\n'),
|
|
270
|
+
'utf8',
|
|
271
|
+
);
|
|
272
|
+
await chmod(targetFile, 0o755);
|
|
273
|
+
return targetFile;
|
|
274
|
+
}
|
|
275
|
+
|
|
187
276
|
function sameRuntime(a: RuntimeInfo | null | undefined, b: RuntimeInfo | null | undefined) {
|
|
188
277
|
return Boolean(
|
|
189
278
|
a &&
|
|
@@ -317,7 +406,9 @@ export async function install(options: InstallOptions = {}) {
|
|
|
317
406
|
const compat = resolveCompatEntry(cursor, environment, { entries: compatEntries });
|
|
318
407
|
const targetPath = join(cursor.appPath, compat.targetRelativePath);
|
|
319
408
|
const originalSha256 = await sha256File(targetPath);
|
|
320
|
-
|
|
409
|
+
if (compat.expectedSha256 !== '*') {
|
|
410
|
+
await assertExpectedHash(targetPath, compat.expectedSha256);
|
|
411
|
+
}
|
|
321
412
|
const patchSetBeforeInstall = await readCursorPatchSetState(cursor.appPath, {
|
|
322
413
|
agentExecTargetRelativePath: compat.targetRelativePath,
|
|
323
414
|
platform: environment.platform,
|
|
@@ -348,6 +439,7 @@ export async function install(options: InstallOptions = {}) {
|
|
|
348
439
|
let resignState: 'ad-hoc' | 'skipped' = 'skipped';
|
|
349
440
|
let patchState = 'skipped';
|
|
350
441
|
let autostartState = 'skipped';
|
|
442
|
+
let launcherState = 'skipped';
|
|
351
443
|
|
|
352
444
|
try {
|
|
353
445
|
if (target.requiresConfirmation) {
|
|
@@ -404,8 +496,9 @@ export async function install(options: InstallOptions = {}) {
|
|
|
404
496
|
}
|
|
405
497
|
|
|
406
498
|
await writeInstallClientConfig(options);
|
|
407
|
-
|
|
408
|
-
|
|
499
|
+
const started = await startInstallService(options, target.mode, environment.platform);
|
|
500
|
+
service = started.service;
|
|
501
|
+
autostartState = started.autostartState ?? (await installAutostart(options, target.mode)).state;
|
|
409
502
|
const patchSet = await patchCursorSet(cursor.appPath, {
|
|
410
503
|
backupDir: options.backupDir,
|
|
411
504
|
platform: environment.platform,
|
|
@@ -421,6 +514,11 @@ export async function install(options: InstallOptions = {}) {
|
|
|
421
514
|
requiresAdHocResign: compat.requiresAdHocResign,
|
|
422
515
|
resign: options.adHocResignApp ?? adHocResignApp,
|
|
423
516
|
});
|
|
517
|
+
launcherState = await installLinuxLauncher({
|
|
518
|
+
appPath: cursor.appPath,
|
|
519
|
+
platform: environment.platform,
|
|
520
|
+
launcherFile: options.linuxLauncherFile,
|
|
521
|
+
});
|
|
424
522
|
|
|
425
523
|
const health = await (options.fetchHealth ?? fetchHealth)(
|
|
426
524
|
`http://${service.host}:${service.port}/health`,
|
|
@@ -483,6 +581,7 @@ export async function install(options: InstallOptions = {}) {
|
|
|
483
581
|
`autostart: ${autostartState}`,
|
|
484
582
|
`patch: ${patchState}`,
|
|
485
583
|
`resign: ${resignState}`,
|
|
584
|
+
`launcher: ${launcherState}`,
|
|
486
585
|
'health: ok',
|
|
487
586
|
target.mode === 'real' ? 'install-record: recorded' : 'trial: recorded',
|
|
488
587
|
].join('\n');
|
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.9',
|
|
86
|
+
serviceVersion: '0.5.9',
|
|
87
|
+
extensionVersion: '0.5.9',
|
|
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.9';
|
|
73
73
|
|
|
74
74
|
function normalizeAppPath(path: string) {
|
|
75
75
|
return normalize(path).replace(/\/+$/, '');
|
|
@@ -256,6 +256,7 @@ export async function repair(options: RepairOptions = {}) {
|
|
|
256
256
|
installRecord.originalSha256 && patchState.currentHash === installRecord.originalSha256;
|
|
257
257
|
if (
|
|
258
258
|
!patchState.legacyMarkerPresent &&
|
|
259
|
+
compat.expectedSha256 !== '*' &&
|
|
259
260
|
patchState.currentHash !== compat.expectedSha256 &&
|
|
260
261
|
!matchesInstallOriginal
|
|
261
262
|
) {
|
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/trial.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { realpathSync } from 'node:fs';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
|
-
import { dirname, join, normalize } from 'node:path';
|
|
5
|
+
import { dirname, join, normalize, resolve } from 'node:path';
|
|
6
6
|
import { DEFAULT_MACOS_CURSOR_APP_PATH } from './cursor';
|
|
7
7
|
|
|
8
8
|
export type ExtensionState = 'bundled' | 'linked' | 'manual-step-required' | 'missing';
|
|
@@ -39,7 +39,7 @@ function resolveHomePath(path: string) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function normalizeAppPath(path: string) {
|
|
42
|
-
return normalize(path).replace(/\/+$/, '');
|
|
42
|
+
return resolve(normalize(path).replace(/\/+$/, ''));
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function isRealCursorAppPath(path: string) {
|
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
|
+
});
|
package/test/compat.test.ts
CHANGED
|
@@ -46,7 +46,7 @@ function signedEnvelope(version: number, rules: CompatibilityManifestEntry[]) {
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
test('default compatibility manifest supports Cursor 3.6
|
|
49
|
+
test('default compatibility manifest supports the Cursor 3.6 macOS arm64 version family', () => {
|
|
50
50
|
const entry = resolveCompatEntry(
|
|
51
51
|
{
|
|
52
52
|
appPath: '/Users/example/Desktop/Cursor-Pool-Agent-Canary.app',
|
|
@@ -63,20 +63,120 @@ test('default compatibility manifest supports Cursor 3.6.21 on macOS arm64', ()
|
|
|
63
63
|
assert.equal(entry.supportStatus, 'supported');
|
|
64
64
|
assert.equal(entry.requiresAdHocResign, true);
|
|
65
65
|
assert.equal(entry.targetRelativePath, CURSOR_AGENT_EXEC_RELATIVE_PATH);
|
|
66
|
+
assert.equal(entry.cursorVersion, '3.6');
|
|
67
|
+
assert.equal(entry.cursorCommit, '*');
|
|
68
|
+
assert.equal(entry.expectedSha256, '*');
|
|
66
69
|
assert.equal(
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
DEFAULT_COMPAT_ENTRIES.some(
|
|
71
|
+
(candidate) =>
|
|
72
|
+
candidate.platform === 'darwin' &&
|
|
73
|
+
candidate.arch === 'arm64' &&
|
|
74
|
+
candidate.cursorVersion === '3.6' &&
|
|
75
|
+
candidate.cursorCommit === '*',
|
|
76
|
+
),
|
|
77
|
+
true,
|
|
69
78
|
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('default compatibility manifest supports the Cursor 3.4 macOS arm64 version family', () => {
|
|
82
|
+
const entry = resolveCompatEntry(
|
|
83
|
+
{
|
|
84
|
+
appPath: '/Users/example/Desktop/Cursor-3.4.0.app',
|
|
85
|
+
version: '3.4.0',
|
|
86
|
+
commit: 'cursor-3-4-family-commit',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
platform: 'darwin',
|
|
90
|
+
arch: 'arm64',
|
|
91
|
+
nodeVersion: process.version,
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
assert.equal(entry.supportStatus, 'supported');
|
|
96
|
+
assert.equal(entry.requiresAdHocResign, true);
|
|
97
|
+
assert.equal(entry.targetRelativePath, CURSOR_AGENT_EXEC_RELATIVE_PATH);
|
|
98
|
+
assert.equal(entry.cursorVersion, '3.4');
|
|
99
|
+
assert.equal(entry.cursorCommit, '*');
|
|
100
|
+
assert.equal(entry.expectedSha256, '*');
|
|
70
101
|
assert.equal(
|
|
71
102
|
DEFAULT_COMPAT_ENTRIES.some(
|
|
72
103
|
(candidate) =>
|
|
73
|
-
candidate.
|
|
74
|
-
candidate.
|
|
104
|
+
candidate.platform === 'darwin' &&
|
|
105
|
+
candidate.arch === 'arm64' &&
|
|
106
|
+
candidate.cursorVersion === '3.4' &&
|
|
107
|
+
candidate.cursorCommit === '*',
|
|
75
108
|
),
|
|
76
109
|
true,
|
|
77
110
|
);
|
|
78
111
|
});
|
|
79
112
|
|
|
113
|
+
test('default compatibility manifest accepts later Cursor 3.6 patch versions on macOS arm64', () => {
|
|
114
|
+
const entry = resolveCompatEntry(
|
|
115
|
+
{
|
|
116
|
+
appPath: '/Users/example/Desktop/Cursor-3.6.31.app',
|
|
117
|
+
version: '3.6.31',
|
|
118
|
+
commit: '81fcf2931d7687b4ff3f3017858d0c6dee7e2a60',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
platform: 'darwin',
|
|
122
|
+
arch: 'arm64',
|
|
123
|
+
nodeVersion: process.version,
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
assert.equal(entry.supportStatus, 'supported');
|
|
128
|
+
assert.equal(entry.cursorVersion, '3.6');
|
|
129
|
+
assert.equal(entry.cursorCommit, '*');
|
|
130
|
+
assert.equal(entry.expectedSha256, '*');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('default compatibility manifest supports the Cursor 3.7 macOS arm64 version family', () => {
|
|
134
|
+
const entry = resolveCompatEntry(
|
|
135
|
+
{
|
|
136
|
+
appPath: '/Users/example/Desktop/Cursor-3.7.12.app',
|
|
137
|
+
version: '3.7.12',
|
|
138
|
+
commit: 'b887a26c4f70bd8136bfffeda812b24194ec9ce0',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
platform: 'darwin',
|
|
142
|
+
arch: 'arm64',
|
|
143
|
+
nodeVersion: process.version,
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
assert.equal(entry.supportStatus, 'supported');
|
|
148
|
+
assert.equal(entry.targetRelativePath, CURSOR_AGENT_EXEC_RELATIVE_PATH);
|
|
149
|
+
assert.equal(entry.cursorVersion, '3.7');
|
|
150
|
+
assert.equal(entry.cursorCommit, '*');
|
|
151
|
+
assert.equal(entry.expectedSha256, '*');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('default compatibility manifest supports Cursor 3.6.31 Linux x64 AppImage', () => {
|
|
155
|
+
const entry = resolveCompatEntry(
|
|
156
|
+
{
|
|
157
|
+
appPath: '/home/xubuntu/.cursor-pool/appimages/Cursor-89aa899ccc46fb58/squashfs-root',
|
|
158
|
+
version: '3.6.31',
|
|
159
|
+
commit: '81fcf2931d7687b4ff3f3017858d0c6dee7e2a60',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
platform: 'linux',
|
|
163
|
+
arch: 'x64',
|
|
164
|
+
nodeVersion: process.version,
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
assert.equal(entry.supportStatus, 'supported');
|
|
169
|
+
assert.equal(entry.requiresAdHocResign, false);
|
|
170
|
+
assert.equal(
|
|
171
|
+
entry.targetRelativePath,
|
|
172
|
+
'usr/share/cursor/resources/app/extensions/cursor-agent-exec/dist/main.js',
|
|
173
|
+
);
|
|
174
|
+
assert.equal(
|
|
175
|
+
entry.expectedSha256,
|
|
176
|
+
'05bfa29eacb8271c378765ead4bf881f806b97549dd13367183aa7a9331c1131',
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
|
|
80
180
|
test('default compatibility manifest supports Cursor 3.5.38 on macOS x64 for Rosetta-launched installers', () => {
|
|
81
181
|
const entry = resolveCompatEntry(
|
|
82
182
|
{
|
|
@@ -94,10 +194,9 @@ test('default compatibility manifest supports Cursor 3.5.38 on macOS x64 for Ros
|
|
|
94
194
|
assert.equal(entry.supportStatus, 'supported');
|
|
95
195
|
assert.equal(entry.requiresAdHocResign, true);
|
|
96
196
|
assert.equal(entry.targetRelativePath, CURSOR_AGENT_EXEC_RELATIVE_PATH);
|
|
97
|
-
assert.equal(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
);
|
|
197
|
+
assert.equal(entry.cursorVersion, '3.5');
|
|
198
|
+
assert.equal(entry.cursorCommit, '*');
|
|
199
|
+
assert.equal(entry.expectedSha256, '*');
|
|
101
200
|
});
|
|
102
201
|
|
|
103
202
|
test('verifies a remote compatibility manifest envelope with the development signature', () => {
|
|
@@ -59,6 +59,7 @@ test('CLI common options include target, record, trial, extension, and service l
|
|
|
59
59
|
cursorExtensionsDir: '/tmp/Cursor-Pool-Trial-Extensions',
|
|
60
60
|
userDataDir: '/tmp/Cursor-Pool-Trial-UserData',
|
|
61
61
|
serviceLogFile: '/tmp/service.log',
|
|
62
|
+
configFile: undefined,
|
|
62
63
|
diagnosticsFile: '/tmp/diagnostics.jsonl',
|
|
63
64
|
sessionFile: '/tmp/session.json',
|
|
64
65
|
code: 'CODE-123',
|
package/test/cursor.test.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
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';
|
|
2
5
|
import test from 'node:test';
|
|
3
|
-
import { defaultCursorAppPath } from '../src/cursor';
|
|
6
|
+
import { defaultCursorAppPath, findCursor } from '../src/cursor';
|
|
4
7
|
|
|
5
8
|
test('defaultCursorAppPath resolves the user-level Windows Cursor install directory', () => {
|
|
6
9
|
assert.equal(
|
|
@@ -18,3 +21,59 @@ test('defaultCursorAppPath gives a helpful Windows error when LOCALAPPDATA is un
|
|
|
18
21
|
/LOCALAPPDATA is required to auto-detect Cursor on Windows/,
|
|
19
22
|
);
|
|
20
23
|
});
|
|
24
|
+
|
|
25
|
+
test('findCursor extracts Linux AppImage files before reading product metadata', async () => {
|
|
26
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-linux-appimage-'));
|
|
27
|
+
const appImagePath = join(tempDir, 'Cursor.AppImage');
|
|
28
|
+
const extractedRoots: string[] = [];
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await writeFile(appImagePath, '#!/bin/sh\n', { mode: 0o755 });
|
|
32
|
+
|
|
33
|
+
const cursor = await findCursor({
|
|
34
|
+
appPath: appImagePath,
|
|
35
|
+
platform: 'linux',
|
|
36
|
+
appImageExtract: async (path, outputRoot) => {
|
|
37
|
+
assert.equal(path, appImagePath);
|
|
38
|
+
extractedRoots.push(outputRoot);
|
|
39
|
+
const extractedPath = join(outputRoot, 'squashfs-root');
|
|
40
|
+
await mkdir(join(extractedPath, 'usr/share/cursor/resources/app'), { recursive: true });
|
|
41
|
+
await writeFile(
|
|
42
|
+
join(extractedPath, 'usr/share/cursor/resources/app/product.json'),
|
|
43
|
+
JSON.stringify({ version: '3.6.31', commit: 'linux-commit' }),
|
|
44
|
+
'utf8',
|
|
45
|
+
);
|
|
46
|
+
return extractedPath;
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
assert.equal(cursor.version, '3.6.31');
|
|
51
|
+
assert.equal(cursor.commit, 'linux-commit');
|
|
52
|
+
assert.match(cursor.appPath, /squashfs-root$/);
|
|
53
|
+
assert.equal(extractedRoots.length, 1);
|
|
54
|
+
} finally {
|
|
55
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('findCursor keeps macOS app bundle product path unchanged', async () => {
|
|
60
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-macos-app-'));
|
|
61
|
+
const appPath = join(tempDir, 'Cursor.app');
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
await mkdir(join(appPath, 'Contents/Resources/app'), { recursive: true });
|
|
65
|
+
await writeFile(
|
|
66
|
+
join(appPath, 'Contents/Resources/app/product.json'),
|
|
67
|
+
JSON.stringify({ version: '3.5.38', commit: 'mac-commit' }),
|
|
68
|
+
'utf8',
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const cursor = await findCursor({ appPath, platform: 'darwin' });
|
|
72
|
+
|
|
73
|
+
assert.equal(cursor.appPath, appPath);
|
|
74
|
+
assert.equal(cursor.version, '3.5.38');
|
|
75
|
+
assert.equal(cursor.commit, 'mac-commit');
|
|
76
|
+
} finally {
|
|
77
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
});
|