impel-cli 0.18.15 → 0.18.16
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/README.md +55 -0
- package/docs/experimental-managed-cursor.md +205 -0
- package/package.json +2 -1
- package/src/apps.js +213 -43
- package/src/commands/apps.js +43 -8
- package/src/commands/converge.js +18 -3
- package/src/commands/cursorExperimental.js +192 -0
- package/src/commands/experimental.js +8 -2
- package/src/commands/launch.js +11 -2
- package/src/commands/status.js +3 -2
- package/src/commands/update.js +23 -11
- package/src/cursorLocal.js +1554 -0
- package/src/macSetup.js +20 -38
- package/src/provisioning.js +10 -4
- package/src/skills.js +24 -8
- package/src/updates.js +57 -10
- package/src/vendorCliBinaries.js +121 -0
- package/src/vendorCliVersions.js +7 -0
- package/src/windowsApps.js +64 -19
- package/src/windowsSetup.js +7 -4
|
@@ -0,0 +1,1554 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
6
|
+
|
|
7
|
+
import { codesignIdentityArgs, desiredSigningMode, resolveSigningIdentity } from "./codesign.js";
|
|
8
|
+
import { brandedEnvironmentName, RUNTIME_BRAND } from "./runtimeBrand.js";
|
|
9
|
+
import { normalizeTenantId } from "./tenants.js";
|
|
10
|
+
|
|
11
|
+
export const CURSOR_VENDOR_BUNDLE_ID = "com.todesktop.230313mzl4w4u92";
|
|
12
|
+
export const CURSOR_VENDOR_TEAM_ID = "VDXQ22DGB9";
|
|
13
|
+
export const CURSOR_RUNTIME_SCHEMA_VERSION = 5;
|
|
14
|
+
|
|
15
|
+
export const CURSOR_LOCAL_MODE_FILES = Object.freeze([
|
|
16
|
+
"extensions/cursor-commits/dist/main.js",
|
|
17
|
+
"extensions/cursor-retrieval/dist/main.js",
|
|
18
|
+
"out/main.js",
|
|
19
|
+
"out/vs/code/electron-utility/alwaysLocalSingleton/alwaysLocalSingletonMain.js",
|
|
20
|
+
"out/vs/code/electron-utility/mcpProcess/mcpProcessMain.js",
|
|
21
|
+
"out/vs/code/electron-utility/sharedProcess/sharedProcessMain.js",
|
|
22
|
+
"out/vs/workbench/api/node/extensionHostProcess.js",
|
|
23
|
+
"out/vs/workbench/api/worker/extensionHostWorkerMain.js",
|
|
24
|
+
"out/vs/workbench/workbench.desktop.main.js",
|
|
25
|
+
"out/vs/workbench/workbench.glass.main.js",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const CURSOR_PROVIDER_LOCK_FILES = Object.freeze([
|
|
29
|
+
"out/vs/workbench/workbench.desktop.main.js",
|
|
30
|
+
"out/vs/workbench/workbench.glass.main.js",
|
|
31
|
+
]);
|
|
32
|
+
const CURSOR_VERIFIED_EXTENSIONS = Object.freeze([
|
|
33
|
+
Object.freeze({ id: "anysphere.cursor-commits", relative: "extensions/cursor-commits/dist/main.js" }),
|
|
34
|
+
Object.freeze({ id: "anysphere.cursor-retrieval", relative: "extensions/cursor-retrieval/dist/main.js" }),
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
const CURSOR_LOCAL_RUNTIME_FILE = "extensions/cursor-local-agent-runtime/dist/main.js";
|
|
38
|
+
const CURSOR_REASONING_TRANSPORT_DISABLED = 'void 0===a?(delete e.reasoning,delete e.reasoning_effort):"output_config.effort"===a.param?';
|
|
39
|
+
const CURSOR_REASONING_TRANSPORT_ENABLED = 'void 0===a?"responses"===o?(e.reasoning=Object.assign(Object.assign({},qRt(e.reasoning)?e.reasoning:{}),{effort:i}),delete e.reasoning_effort):(delete e.reasoning,delete e.reasoning_effort):"output_config.effort"===a.param?';
|
|
40
|
+
const CURSOR_HELPER_BUNDLES = Object.freeze([
|
|
41
|
+
"Cursor Helper (GPU).app",
|
|
42
|
+
"Cursor Helper (Plugin).app",
|
|
43
|
+
"Cursor Helper (Renderer).app",
|
|
44
|
+
"Cursor Helper.app",
|
|
45
|
+
]);
|
|
46
|
+
const PRODUCT_FILE = "product.json";
|
|
47
|
+
const PACKAGE_FILE = "package.json";
|
|
48
|
+
const LOCAL_MODE_DISABLED = "localMode:!1";
|
|
49
|
+
const LOCAL_MODE_ENABLED = "localMode:!0";
|
|
50
|
+
const PERSONAL_DATA_IMPORT_GUARD = "if(!Ui.localMode)";
|
|
51
|
+
const MANAGED_DATA_IMPORT_GUARD = "if(Ui.localMode )";
|
|
52
|
+
const DISABLE_LIBRARY_VALIDATION_ENTITLEMENT = "com.apple.security.cs.disable-library-validation";
|
|
53
|
+
const DIRECT_PROVIDER_ENV_KEYS = Object.freeze([
|
|
54
|
+
"ANTHROPIC_API_KEY",
|
|
55
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
56
|
+
"ANTHROPIC_BASE_URL",
|
|
57
|
+
"AWS_ACCESS_KEY_ID",
|
|
58
|
+
"AWS_DEFAULT_REGION",
|
|
59
|
+
"AWS_PROFILE",
|
|
60
|
+
"AWS_REGION",
|
|
61
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
62
|
+
"AWS_SESSION_TOKEN",
|
|
63
|
+
"AZURE_OPENAI_API_KEY",
|
|
64
|
+
"AZURE_OPENAI_ENDPOINT",
|
|
65
|
+
"CURSOR_API_KEY",
|
|
66
|
+
"CURSOR_LOCAL_AGENT_API_KEY",
|
|
67
|
+
"CURSOR_LOCAL_AGENT_BASE_URL",
|
|
68
|
+
"GEMINI_API_KEY",
|
|
69
|
+
"GOOGLE_API_KEY",
|
|
70
|
+
"OPENAI_API_KEY",
|
|
71
|
+
"OPENAI_BASE_URL",
|
|
72
|
+
"OPENAI_ORG_ID",
|
|
73
|
+
]);
|
|
74
|
+
const ISOLATION_BREAKING_ARGUMENTS = Object.freeze([
|
|
75
|
+
"--extensions-dir",
|
|
76
|
+
"--override-cursor-auth-token",
|
|
77
|
+
"--user-data-dir",
|
|
78
|
+
]);
|
|
79
|
+
const RUNTIME_OVERRIDE_ENV_KEYS = Object.freeze([
|
|
80
|
+
"CLAUDE_CONFIG_DIR",
|
|
81
|
+
"CLAUDE_PLUGIN_ROOT",
|
|
82
|
+
"CODEX_HOME",
|
|
83
|
+
"CURSOR_PLUGIN_ROOT",
|
|
84
|
+
"ELECTRON_RUN_AS_NODE",
|
|
85
|
+
"NODE_OPTIONS",
|
|
86
|
+
"NODE_PATH",
|
|
87
|
+
"VSCODE_PORTABLE",
|
|
88
|
+
"XDG_CACHE_HOME",
|
|
89
|
+
"XDG_CONFIG_HOME",
|
|
90
|
+
"XDG_DATA_HOME",
|
|
91
|
+
]);
|
|
92
|
+
const CURSOR_IPC_SOCKET_PATH_LIMIT = 103;
|
|
93
|
+
const CURSOR_IPC_SOCKET_SUFFIX_RESERVE = Buffer.byteLength("/999.999-main.sock");
|
|
94
|
+
const CURSOR_PREPARE_LOCK_FILE = ".prepare.lock";
|
|
95
|
+
const CURSOR_REACTIVE_STORAGE_KEY = "src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser";
|
|
96
|
+
const CURSOR_MANAGED_MODELS_KEY = "impel.cursor.gatewayModels";
|
|
97
|
+
const CURSOR_MANAGED_GATEWAY_SENTINEL = "impel-managed-non-secret";
|
|
98
|
+
const MACOS_O_EXLOCK = 0x00000020;
|
|
99
|
+
const portableCursorPrepareLocks = new Set();
|
|
100
|
+
|
|
101
|
+
function appResources(appPath) {
|
|
102
|
+
return path.join(appPath, "Contents", "Resources", "app");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function cursorProcessBundles(appPath) {
|
|
106
|
+
const frameworks = path.join(appPath, "Contents", "Frameworks");
|
|
107
|
+
let found;
|
|
108
|
+
try {
|
|
109
|
+
found = fs.readdirSync(frameworks, { withFileTypes: true })
|
|
110
|
+
.filter((entry) => entry.isDirectory() && entry.name.endsWith(".app"))
|
|
111
|
+
.map((entry) => entry.name)
|
|
112
|
+
.sort();
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw new Error(`Cursor helper bundle contract is unavailable: ${error?.message || error}`);
|
|
115
|
+
}
|
|
116
|
+
assertStringArrayEqual(found, [...CURSOR_HELPER_BUNDLES], "Cursor helper bundle surface");
|
|
117
|
+
return [
|
|
118
|
+
...CURSOR_HELPER_BUNDLES.map((name) => path.join(frameworks, name)),
|
|
119
|
+
appPath,
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function countOccurrences(value, needle) {
|
|
124
|
+
let count = 0;
|
|
125
|
+
let offset = 0;
|
|
126
|
+
while ((offset = value.indexOf(needle, offset)) !== -1) {
|
|
127
|
+
count += 1;
|
|
128
|
+
offset += needle.length;
|
|
129
|
+
}
|
|
130
|
+
return count;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function sha256File(filePath) {
|
|
134
|
+
return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function sha256FileBase64(filePath) {
|
|
138
|
+
return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("base64").replace(/=+$/u, "");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function cursorExtensionIntegrityHash(source, id) {
|
|
142
|
+
const token = `"${id}":{dist:{"main.js":"`;
|
|
143
|
+
const first = source.indexOf(token);
|
|
144
|
+
if (first === -1 || source.indexOf(token, first + token.length) !== -1) {
|
|
145
|
+
throw new Error(`Cursor extension-integrity contract changed for ${id}`);
|
|
146
|
+
}
|
|
147
|
+
const start = first + token.length;
|
|
148
|
+
const hash = source.slice(start, start + 64);
|
|
149
|
+
if (!/^[0-9a-f]{64}$/u.test(hash) || source.slice(start + 64, start + 66) !== '"}') {
|
|
150
|
+
throw new Error(`Cursor extension-integrity hash changed for ${id}`);
|
|
151
|
+
}
|
|
152
|
+
return { hash, start };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function assertCursorExtensionIntegrityContract(resourcesRoot) {
|
|
156
|
+
const hostPath = path.join(resourcesRoot, "out", "vs", "workbench", "api", "node", "extensionHostProcess.js");
|
|
157
|
+
const host = fs.readFileSync(hostPath, "utf8");
|
|
158
|
+
return CURSOR_VERIFIED_EXTENSIONS.map(({ id, relative }) => {
|
|
159
|
+
const embedded = cursorExtensionIntegrityHash(host, id).hash;
|
|
160
|
+
const actual = sha256File(path.join(resourcesRoot, relative));
|
|
161
|
+
if (embedded !== actual) {
|
|
162
|
+
throw new Error(`Cursor vendor extension integrity does not match ${relative}; review this update before use`);
|
|
163
|
+
}
|
|
164
|
+
return { id, relative, vendorHash: embedded };
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function patchCursorExtensionIntegrity(resourcesRoot, reviewed) {
|
|
169
|
+
const hostPath = path.join(resourcesRoot, "out", "vs", "workbench", "api", "node", "extensionHostProcess.js");
|
|
170
|
+
let host = fs.readFileSync(hostPath, "utf8");
|
|
171
|
+
const originalLength = Buffer.byteLength(host);
|
|
172
|
+
for (const { id, relative, vendorHash } of reviewed) {
|
|
173
|
+
const embedded = cursorExtensionIntegrityHash(host, id);
|
|
174
|
+
if (embedded.hash !== vendorHash) {
|
|
175
|
+
throw new Error(`Cursor extension-integrity source changed while patching ${id}`);
|
|
176
|
+
}
|
|
177
|
+
const managedHash = sha256File(path.join(resourcesRoot, relative));
|
|
178
|
+
host = `${host.slice(0, embedded.start)}${managedHash}${host.slice(embedded.start + 64)}`;
|
|
179
|
+
}
|
|
180
|
+
if (Buffer.byteLength(host) !== originalLength) {
|
|
181
|
+
throw new Error("Cursor extension-integrity patch changed byte width");
|
|
182
|
+
}
|
|
183
|
+
fs.writeFileSync(hostPath, host);
|
|
184
|
+
return reviewed.length;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function readJSON(filePath, label) {
|
|
188
|
+
try {
|
|
189
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
190
|
+
} catch (error) {
|
|
191
|
+
throw new Error(`${label} is missing or invalid: ${error?.message || error}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function cursorProductChecksumEntries(resourcesRoot) {
|
|
196
|
+
const productPath = path.join(resourcesRoot, PRODUCT_FILE);
|
|
197
|
+
const product = readJSON(productPath, "Cursor product.json");
|
|
198
|
+
if (
|
|
199
|
+
!product.checksums
|
|
200
|
+
|| typeof product.checksums !== "object"
|
|
201
|
+
|| Array.isArray(product.checksums)
|
|
202
|
+
|| Object.keys(product.checksums).length === 0
|
|
203
|
+
) {
|
|
204
|
+
throw new Error("Cursor product checksum contract is missing; review this update before use");
|
|
205
|
+
}
|
|
206
|
+
const outRoot = path.join(resourcesRoot, "out");
|
|
207
|
+
const entries = Object.entries(product.checksums).map(([relative, expected]) => {
|
|
208
|
+
const normalized = path.posix.normalize(relative);
|
|
209
|
+
if (
|
|
210
|
+
normalized !== relative
|
|
211
|
+
|| path.posix.isAbsolute(relative)
|
|
212
|
+
|| relative === ".."
|
|
213
|
+
|| relative.startsWith("../")
|
|
214
|
+
|| typeof expected !== "string"
|
|
215
|
+
|| !/^[A-Za-z0-9+/]{43}$/u.test(expected)
|
|
216
|
+
) {
|
|
217
|
+
throw new Error(`Cursor product checksum entry is invalid: ${relative}`);
|
|
218
|
+
}
|
|
219
|
+
const target = path.join(outRoot, ...relative.split("/"));
|
|
220
|
+
if (!fs.statSync(target, { throwIfNoEntry: false })?.isFile()) {
|
|
221
|
+
throw new Error(`Cursor product checksum target is missing: ${relative}`);
|
|
222
|
+
}
|
|
223
|
+
return { expected, relative, target };
|
|
224
|
+
});
|
|
225
|
+
return { entries, product, productPath };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function assertCursorProductChecksums(resourcesRoot) {
|
|
229
|
+
const { entries } = cursorProductChecksumEntries(resourcesRoot);
|
|
230
|
+
for (const { expected, relative, target } of entries) {
|
|
231
|
+
if (sha256FileBase64(target) !== expected) {
|
|
232
|
+
throw new Error(`Cursor product checksum does not match ${relative}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return entries.length;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function patchCursorProductChecksums(resourcesRoot) {
|
|
239
|
+
const { entries, product, productPath } = cursorProductChecksumEntries(resourcesRoot);
|
|
240
|
+
product.checksums = Object.fromEntries(entries.map(({ relative, target }) => [
|
|
241
|
+
relative,
|
|
242
|
+
sha256FileBase64(target),
|
|
243
|
+
]));
|
|
244
|
+
fs.writeFileSync(productPath, JSON.stringify(product));
|
|
245
|
+
assertCursorProductChecksums(resourcesRoot);
|
|
246
|
+
return entries.length;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function runResult(command, args) {
|
|
250
|
+
return spawnSync(command, args, { encoding: "utf8" });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function commandFailure(label, result) {
|
|
254
|
+
const detail = result?.error?.message
|
|
255
|
+
|| String(result?.stderr || result?.stdout || "").trim()
|
|
256
|
+
|| `exit ${result?.status ?? "unknown"}`;
|
|
257
|
+
return new Error(`${label} failed: ${detail}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function defaultVerifyVendorBundle(appPath) {
|
|
261
|
+
const verified = runResult("/usr/bin/codesign", ["--verify", "--deep", "--strict", appPath]);
|
|
262
|
+
if (verified.status !== 0 || verified.error) throw commandFailure("Cursor vendor signature verification", verified);
|
|
263
|
+
const details = runResult("/usr/bin/codesign", ["-dv", "--verbose=4", appPath]);
|
|
264
|
+
if (details.status !== 0 || details.error) throw commandFailure("Cursor vendor signature inspection", details);
|
|
265
|
+
const output = `${details.stdout || ""}\n${details.stderr || ""}`;
|
|
266
|
+
const bundleIdentifier = output.match(/^Identifier=(.+)$/mu)?.[1]?.trim();
|
|
267
|
+
const teamIdentifier = output.match(/^TeamIdentifier=(.+)$/mu)?.[1]?.trim();
|
|
268
|
+
const developerID = output.match(/^Authority=Developer ID Application:/mu) !== null;
|
|
269
|
+
if (bundleIdentifier !== CURSOR_VENDOR_BUNDLE_ID || teamIdentifier !== CURSOR_VENDOR_TEAM_ID || !developerID) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
`unsupported Cursor signature (bundle ${bundleIdentifier || "unknown"}, team ${teamIdentifier || "unknown"})`,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
return { bundleIdentifier, teamIdentifier };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function localModeFiles(resourcesRoot, marker = LOCAL_MODE_DISABLED) {
|
|
278
|
+
const found = [];
|
|
279
|
+
const roots = [path.join(resourcesRoot, "out"), path.join(resourcesRoot, "extensions")];
|
|
280
|
+
const visit = (directory) => {
|
|
281
|
+
let entries;
|
|
282
|
+
try {
|
|
283
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
284
|
+
} catch (error) {
|
|
285
|
+
if (error?.code === "ENOENT") return;
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
for (const entry of entries) {
|
|
289
|
+
const absolute = path.join(directory, entry.name);
|
|
290
|
+
if (entry.isDirectory()) {
|
|
291
|
+
visit(absolute);
|
|
292
|
+
} else if (entry.isFile() && entry.name.endsWith(".js")) {
|
|
293
|
+
const contents = fs.readFileSync(absolute, "utf8");
|
|
294
|
+
if (contents.includes(marker)) {
|
|
295
|
+
found.push(path.relative(resourcesRoot, absolute).split(path.sep).join("/"));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
for (const root of roots) visit(root);
|
|
301
|
+
return found.sort();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function assertStringArrayEqual(actual, expected, label) {
|
|
305
|
+
if (actual.length !== expected.length || actual.some((entry, index) => entry !== expected[index])) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
`${label} changed (found ${actual.length ? actual.join(", ") : "none"}); review this Cursor update before use`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function assertLocalRuntimeContract(resourcesRoot) {
|
|
313
|
+
const runtimePath = path.join(resourcesRoot, CURSOR_LOCAL_RUNTIME_FILE);
|
|
314
|
+
let runtime;
|
|
315
|
+
try {
|
|
316
|
+
runtime = fs.readFileSync(runtimePath, "utf8");
|
|
317
|
+
} catch (error) {
|
|
318
|
+
throw new Error(`Cursor local-agent runtime is unavailable: ${error?.message || error}`);
|
|
319
|
+
}
|
|
320
|
+
const markers = [
|
|
321
|
+
"apiKeyHelper",
|
|
322
|
+
"api_types",
|
|
323
|
+
"anthropic_messages",
|
|
324
|
+
"fetchLocalProviderModels",
|
|
325
|
+
"max_output_tokens",
|
|
326
|
+
"openai_responses",
|
|
327
|
+
"supports_streaming",
|
|
328
|
+
"supports_tool_use",
|
|
329
|
+
];
|
|
330
|
+
const missing = markers.filter((marker) => !runtime.includes(marker));
|
|
331
|
+
if (missing.length) {
|
|
332
|
+
throw new Error(`Cursor local-agent runtime contract changed (missing ${missing.join(", ")}); review this update before use`);
|
|
333
|
+
}
|
|
334
|
+
const responseEndpointSelection = /\.includes\("responses"\)\|\|[^?]{0,120}\.includes\("openai_responses"\)\?"responses":/u;
|
|
335
|
+
const chatCompletionsEndpointSelection = /\.includes\("chat_completions"\)\|\|[^?]{0,120}\.includes\("openai_chat"\)\?"chat_completions":/u;
|
|
336
|
+
const anthropicApiTypeSelection = /\.includes\("anthropic_messages"\)&&![^?]{0,80}\?"anthropic_messages":"openai_compatible"/u;
|
|
337
|
+
if (
|
|
338
|
+
!responseEndpointSelection.test(runtime)
|
|
339
|
+
|| !chatCompletionsEndpointSelection.test(runtime)
|
|
340
|
+
|| !anthropicApiTypeSelection.test(runtime)
|
|
341
|
+
) {
|
|
342
|
+
throw new Error("Cursor local-agent Responses/model-picker contract changed; review this update before use");
|
|
343
|
+
}
|
|
344
|
+
if (countOccurrences(runtime, CURSOR_REASONING_TRANSPORT_DISABLED) !== 1) {
|
|
345
|
+
throw new Error("Cursor local-agent reasoning-effort transport contract changed; review this update before use");
|
|
346
|
+
}
|
|
347
|
+
return runtime;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function patchCursorReasoningTransport(resourcesRoot) {
|
|
351
|
+
const runtimePath = path.join(resourcesRoot, CURSOR_LOCAL_RUNTIME_FILE);
|
|
352
|
+
const runtime = fs.readFileSync(runtimePath, "utf8");
|
|
353
|
+
if (countOccurrences(runtime, CURSOR_REASONING_TRANSPORT_DISABLED) !== 1) {
|
|
354
|
+
throw new Error("Cursor local-agent reasoning-effort transport contract changed; refusing to patch");
|
|
355
|
+
}
|
|
356
|
+
fs.writeFileSync(
|
|
357
|
+
runtimePath,
|
|
358
|
+
runtime.replace(CURSOR_REASONING_TRANSPORT_DISABLED, CURSOR_REASONING_TRANSPORT_ENABLED),
|
|
359
|
+
);
|
|
360
|
+
return 1;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function assertCursorAuthenticationContract(resourcesRoot) {
|
|
364
|
+
const main = fs.readFileSync(path.join(resourcesRoot, "out", "main.js"), "utf8");
|
|
365
|
+
for (const marker of [
|
|
366
|
+
'"skip-welcome":{type:"boolean"}',
|
|
367
|
+
'"skip-onboarding":{type:"boolean"}',
|
|
368
|
+
'"override-cursor-auth-token":{type:"string"}',
|
|
369
|
+
]) {
|
|
370
|
+
if (countOccurrences(main, marker) !== 1) {
|
|
371
|
+
throw new Error("Cursor managed-authentication argument contract changed; review this update before use");
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
for (const relative of CURSOR_PROVIDER_LOCK_FILES) {
|
|
375
|
+
const source = fs.readFileSync(path.join(resourcesRoot, relative), "utf8");
|
|
376
|
+
for (const marker of [
|
|
377
|
+
"workbench.action.devAutoLoginFakeForTesting",
|
|
378
|
+
"cursor-smoke-test",
|
|
379
|
+
"fake-refresh-token-for-testing",
|
|
380
|
+
"cursorAuth/accessToken",
|
|
381
|
+
"cursorAuth/refreshToken",
|
|
382
|
+
"overrideCursorAuthToken",
|
|
383
|
+
]) {
|
|
384
|
+
if (!source.includes(marker)) {
|
|
385
|
+
throw new Error(`Cursor managed-authentication contract changed in ${relative}; review this update before use`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function assertCursorDataImportContract(resourcesRoot) {
|
|
392
|
+
const mainPath = path.join(resourcesRoot, "out", "main.js");
|
|
393
|
+
const source = fs.readFileSync(mainPath, "utf8");
|
|
394
|
+
const required = [
|
|
395
|
+
"Cursor data import is only available in local mode",
|
|
396
|
+
"localModeDataImportService",
|
|
397
|
+
"async getAvailability()",
|
|
398
|
+
"async stageImport(e)",
|
|
399
|
+
"async relaunchPreparedImport(e)",
|
|
400
|
+
];
|
|
401
|
+
if (required.some((marker) => !source.includes(marker)) || countOccurrences(source, PERSONAL_DATA_IMPORT_GUARD) !== 5) {
|
|
402
|
+
throw new Error("Cursor personal-data import contract changed; review this update before use");
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function contractFingerprint(product, packageJSON, resourcesRoot, runtime, executable) {
|
|
407
|
+
const hash = crypto.createHash("sha256");
|
|
408
|
+
hash.update(JSON.stringify({
|
|
409
|
+
applicationName: product.applicationName,
|
|
410
|
+
commit: product.commit,
|
|
411
|
+
darwinBundleIdentifier: product.darwinBundleIdentifier,
|
|
412
|
+
nameLong: product.nameLong,
|
|
413
|
+
quality: product.quality,
|
|
414
|
+
version: product.version,
|
|
415
|
+
}));
|
|
416
|
+
hash.update(JSON.stringify({ name: packageJSON.name, version: packageJSON.version }));
|
|
417
|
+
for (const relative of CURSOR_LOCAL_MODE_FILES) {
|
|
418
|
+
const contents = fs.readFileSync(path.join(resourcesRoot, relative));
|
|
419
|
+
hash.update(relative);
|
|
420
|
+
hash.update(contents);
|
|
421
|
+
}
|
|
422
|
+
hash.update(CURSOR_LOCAL_RUNTIME_FILE);
|
|
423
|
+
hash.update(runtime);
|
|
424
|
+
hash.update("Contents/MacOS/Cursor");
|
|
425
|
+
hash.update(fs.readFileSync(executable));
|
|
426
|
+
return hash.digest("hex");
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export function inspectCursorVendorApp(appPath, dependencies = {}) {
|
|
430
|
+
if (!appPath || !fs.statSync(appPath, { throwIfNoEntry: false })?.isDirectory()) {
|
|
431
|
+
throw new Error(`Cursor vendor app was not found at ${appPath || "(missing path)"}`);
|
|
432
|
+
}
|
|
433
|
+
const verifyVendorBundle = dependencies.verifyVendorBundle || defaultVerifyVendorBundle;
|
|
434
|
+
const signature = verifyVendorBundle(appPath);
|
|
435
|
+
if (
|
|
436
|
+
signature?.bundleIdentifier !== CURSOR_VENDOR_BUNDLE_ID
|
|
437
|
+
|| signature?.teamIdentifier !== CURSOR_VENDOR_TEAM_ID
|
|
438
|
+
) {
|
|
439
|
+
throw new Error("Cursor vendor verifier did not establish the expected bundle and Developer ID team");
|
|
440
|
+
}
|
|
441
|
+
const resourcesRoot = appResources(appPath);
|
|
442
|
+
const product = readJSON(path.join(resourcesRoot, PRODUCT_FILE), "Cursor product.json");
|
|
443
|
+
const packageJSON = readJSON(path.join(resourcesRoot, PACKAGE_FILE), "Cursor package.json");
|
|
444
|
+
if (
|
|
445
|
+
product.nameLong !== "Cursor"
|
|
446
|
+
|| product.applicationName !== "cursor"
|
|
447
|
+
|| product.quality !== "stable"
|
|
448
|
+
|| product.darwinBundleIdentifier !== CURSOR_VENDOR_BUNDLE_ID
|
|
449
|
+
|| typeof product.version !== "string"
|
|
450
|
+
|| !/^\d+\.\d+\.\d+$/u.test(product.version)
|
|
451
|
+
|| typeof product.commit !== "string"
|
|
452
|
+
|| !/^[0-9a-f]{40}$/u.test(product.commit)
|
|
453
|
+
) {
|
|
454
|
+
throw new Error("unsupported Cursor product contract; only a signed stable Cursor desktop build can be managed");
|
|
455
|
+
}
|
|
456
|
+
if (packageJSON.name !== "Cursor" || packageJSON.version !== product.version) {
|
|
457
|
+
throw new Error("unsupported Cursor Electron package contract; review this update before use");
|
|
458
|
+
}
|
|
459
|
+
const executable = path.join(appPath, "Contents", "MacOS", "Cursor");
|
|
460
|
+
if (!fs.statSync(executable, { throwIfNoEntry: false })?.isFile()) {
|
|
461
|
+
throw new Error("Cursor vendor executable is missing");
|
|
462
|
+
}
|
|
463
|
+
cursorProcessBundles(appPath);
|
|
464
|
+
const found = localModeFiles(resourcesRoot);
|
|
465
|
+
assertStringArrayEqual(found, [...CURSOR_LOCAL_MODE_FILES].sort(), "Cursor local-mode source surface");
|
|
466
|
+
const alreadyEnabled = localModeFiles(resourcesRoot, LOCAL_MODE_ENABLED);
|
|
467
|
+
if (alreadyEnabled.length) {
|
|
468
|
+
throw new Error(`Cursor vendor app already contains enabled local-mode surfaces (${alreadyEnabled.join(", ")}); review this update before use`);
|
|
469
|
+
}
|
|
470
|
+
for (const relative of CURSOR_LOCAL_MODE_FILES) {
|
|
471
|
+
const source = fs.readFileSync(path.join(resourcesRoot, relative), "utf8");
|
|
472
|
+
if (countOccurrences(source, LOCAL_MODE_DISABLED) !== 1) {
|
|
473
|
+
throw new Error(`Cursor local-mode signature changed in ${relative}; review this update before use`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
for (const relative of CURSOR_PROVIDER_LOCK_FILES) {
|
|
477
|
+
assertProviderCandidateContract(fs.readFileSync(path.join(resourcesRoot, relative), "utf8"), relative);
|
|
478
|
+
}
|
|
479
|
+
assertCursorExtensionIntegrityContract(resourcesRoot);
|
|
480
|
+
assertCursorDataImportContract(resourcesRoot);
|
|
481
|
+
assertCursorAuthenticationContract(resourcesRoot);
|
|
482
|
+
assertCursorProductChecksums(resourcesRoot);
|
|
483
|
+
const runtime = assertLocalRuntimeContract(resourcesRoot);
|
|
484
|
+
return {
|
|
485
|
+
appPath,
|
|
486
|
+
bundleIdentifier: signature.bundleIdentifier,
|
|
487
|
+
teamIdentifier: signature.teamIdentifier,
|
|
488
|
+
version: product.version,
|
|
489
|
+
commit: product.commit,
|
|
490
|
+
sourceFingerprint: contractFingerprint(product, packageJSON, resourcesRoot, runtime, executable),
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function matchingBracket(value, openingIndex, open, close) {
|
|
495
|
+
let depth = 0;
|
|
496
|
+
let quote = null;
|
|
497
|
+
let escaped = false;
|
|
498
|
+
for (let index = openingIndex; index < value.length; index += 1) {
|
|
499
|
+
const character = value[index];
|
|
500
|
+
if (quote) {
|
|
501
|
+
if (escaped) escaped = false;
|
|
502
|
+
else if (character === "\\") escaped = true;
|
|
503
|
+
else if (character === quote) quote = null;
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
507
|
+
quote = character;
|
|
508
|
+
} else if (character === open) {
|
|
509
|
+
depth += 1;
|
|
510
|
+
} else if (character === close) {
|
|
511
|
+
depth -= 1;
|
|
512
|
+
if (depth === 0) return index;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return -1;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function uniqueArrayRange(source, token, label) {
|
|
519
|
+
const first = source.indexOf(token);
|
|
520
|
+
if (first === -1 || source.indexOf(token, first + token.length) !== -1) {
|
|
521
|
+
throw new Error(`Cursor ${label} candidate contract changed`);
|
|
522
|
+
}
|
|
523
|
+
const opening = first + token.length - 1;
|
|
524
|
+
const closing = matchingBracket(source, opening, "[", "]");
|
|
525
|
+
if (closing === -1) throw new Error(`Cursor ${label} candidate array is malformed`);
|
|
526
|
+
return { opening, closing };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function splitTopLevelArray(source, range, label) {
|
|
530
|
+
const body = source.slice(range.opening + 1, range.closing);
|
|
531
|
+
const entries = [];
|
|
532
|
+
let start = 0;
|
|
533
|
+
let braceDepth = 0;
|
|
534
|
+
let bracketDepth = 0;
|
|
535
|
+
let parenDepth = 0;
|
|
536
|
+
let quote = null;
|
|
537
|
+
let escaped = false;
|
|
538
|
+
for (let index = 0; index < body.length; index += 1) {
|
|
539
|
+
const character = body[index];
|
|
540
|
+
if (quote) {
|
|
541
|
+
if (escaped) escaped = false;
|
|
542
|
+
else if (character === "\\") escaped = true;
|
|
543
|
+
else if (character === quote) quote = null;
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
if (character === "\"" || character === "'" || character === "`") quote = character;
|
|
547
|
+
else if (character === "{") braceDepth += 1;
|
|
548
|
+
else if (character === "}") braceDepth -= 1;
|
|
549
|
+
else if (character === "[") bracketDepth += 1;
|
|
550
|
+
else if (character === "]") bracketDepth -= 1;
|
|
551
|
+
else if (character === "(") parenDepth += 1;
|
|
552
|
+
else if (character === ")") parenDepth -= 1;
|
|
553
|
+
else if (character === "," && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
|
|
554
|
+
entries.push(body.slice(start, index));
|
|
555
|
+
start = index + 1;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
entries.push(body.slice(start));
|
|
559
|
+
if (entries.length !== 4 || entries.some((entry) => !entry.startsWith("{value:") || !entry.endsWith("}"))) {
|
|
560
|
+
throw new Error(`Cursor ${label} candidate list changed`);
|
|
561
|
+
}
|
|
562
|
+
return entries;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function candidateKind(entry, label) {
|
|
566
|
+
if (entry.includes(`CURSOR_LOCAL_AGENT_${label === "API key" ? "API_KEY" : "BASE_URL"}`)) return "cursor";
|
|
567
|
+
if (entry.includes(label === "API key" ? "ANTHROPIC_AUTH_TOKEN" : "ANTHROPIC_BASE_URL")) return "compatibility";
|
|
568
|
+
if (entry.includes(label === "API key" ? ".modelProvidedApiKey" : ".modelProvidedBaseUrl")) return "model";
|
|
569
|
+
if (entry.includes(label === "API key" ? ".storedOpenAIKey" : ".openAIBaseUrl")) return "stored";
|
|
570
|
+
throw new Error(`Cursor ${label} candidate source changed`);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function providerCandidateArray(source, token, label) {
|
|
574
|
+
const range = uniqueArrayRange(source, token, label);
|
|
575
|
+
const entries = splitTopLevelArray(source, range, label);
|
|
576
|
+
const kinds = entries.map((entry) => candidateKind(entry, label));
|
|
577
|
+
assertStringArrayEqual(kinds, ["model", "stored", "cursor", "compatibility"], `Cursor ${label} priority`);
|
|
578
|
+
return { range, entries, kinds };
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function assertProviderCandidateContract(source, relative) {
|
|
582
|
+
try {
|
|
583
|
+
providerCandidateArray(source, "apiKeyCandidates:[", "API key");
|
|
584
|
+
providerCandidateArray(source, "baseUrlCandidates:[", "base URL");
|
|
585
|
+
} catch (error) {
|
|
586
|
+
throw new Error(`${error.message} in ${relative}; review this Cursor update before use`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function prioritizeManagedProvider(source, relative) {
|
|
591
|
+
const arrays = [
|
|
592
|
+
providerCandidateArray(source, "apiKeyCandidates:[", "API key"),
|
|
593
|
+
providerCandidateArray(source, "baseUrlCandidates:[", "base URL"),
|
|
594
|
+
].sort((left, right) => right.range.opening - left.range.opening);
|
|
595
|
+
let patched = source;
|
|
596
|
+
for (const { range, entries, kinds } of arrays) {
|
|
597
|
+
const byKind = new Map(kinds.map((kind, index) => [
|
|
598
|
+
kind,
|
|
599
|
+
["model", "stored"].includes(kind) ? disableProviderCandidate(entries[index], relative) : entries[index],
|
|
600
|
+
]));
|
|
601
|
+
const replacement = ["cursor", "compatibility", "model", "stored"]
|
|
602
|
+
.map((kind) => byKind.get(kind))
|
|
603
|
+
.join(",");
|
|
604
|
+
const originalLength = range.closing - range.opening - 1;
|
|
605
|
+
if (replacement.length !== originalLength) {
|
|
606
|
+
throw new Error(`Cursor managed-provider patch changed byte width in ${relative}`);
|
|
607
|
+
}
|
|
608
|
+
patched = `${patched.slice(0, range.opening + 1)}${replacement}${patched.slice(range.closing)}`;
|
|
609
|
+
}
|
|
610
|
+
return patched;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function disableProviderCandidate(entry, relative) {
|
|
614
|
+
const valueStart = entry.indexOf("{value:") + "{value:".length;
|
|
615
|
+
const sourceStart = entry.lastIndexOf(",source:");
|
|
616
|
+
if (valueStart < "{value:".length || sourceStart <= valueStart) {
|
|
617
|
+
throw new Error(`Cursor managed-provider fallback contract changed in ${relative}`);
|
|
618
|
+
}
|
|
619
|
+
const width = sourceStart - valueStart;
|
|
620
|
+
if (width < "void 0".length) throw new Error(`Cursor managed-provider fallback is too narrow in ${relative}`);
|
|
621
|
+
return `${entry.slice(0, valueStart)}${"void 0".padEnd(width, " ")}${entry.slice(sourceStart)}`;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
export function managedCursorApplicationName(tenantId) {
|
|
625
|
+
return `${RUNTIME_BRAND.apps.displayPrefix} Cursor [${normalizeTenantId(tenantId)}]`;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function patchCursorApplicationName(resourcesRoot, applicationName) {
|
|
629
|
+
const packagePath = path.join(resourcesRoot, PACKAGE_FILE);
|
|
630
|
+
const source = fs.readFileSync(packagePath, "utf8");
|
|
631
|
+
const parsed = readJSON(packagePath, "Cursor package.json");
|
|
632
|
+
if (parsed.name !== "Cursor") throw new Error("Cursor Electron application-name contract changed; refusing to patch");
|
|
633
|
+
const needle = '"name": "Cursor"';
|
|
634
|
+
if (countOccurrences(source, needle) !== 1) {
|
|
635
|
+
throw new Error("Cursor Electron application-name source signature changed; refusing to patch");
|
|
636
|
+
}
|
|
637
|
+
fs.writeFileSync(packagePath, source.replace(needle, `"name": ${JSON.stringify(applicationName)}`));
|
|
638
|
+
return 1;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function disableCursorPersonalDataImport(resourcesRoot) {
|
|
642
|
+
const mainPath = path.join(resourcesRoot, "out", "main.js");
|
|
643
|
+
const source = fs.readFileSync(mainPath, "utf8");
|
|
644
|
+
assertCursorDataImportContract(resourcesRoot);
|
|
645
|
+
const patched = source.replaceAll(PERSONAL_DATA_IMPORT_GUARD, MANAGED_DATA_IMPORT_GUARD);
|
|
646
|
+
if (Buffer.byteLength(patched) !== Buffer.byteLength(source)) {
|
|
647
|
+
throw new Error("Cursor personal-data import patch changed byte width");
|
|
648
|
+
}
|
|
649
|
+
fs.writeFileSync(mainPath, patched);
|
|
650
|
+
return 5;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
export function patchCursorBundle(bundlePath, { applicationName } = {}) {
|
|
654
|
+
if (!applicationName) throw new Error("a tenant-specific Cursor application name is required");
|
|
655
|
+
const resourcesRoot = appResources(bundlePath);
|
|
656
|
+
const reviewedExtensionIntegrity = assertCursorExtensionIntegrityContract(resourcesRoot);
|
|
657
|
+
const isolatedApplicationName = patchCursorApplicationName(resourcesRoot, applicationName);
|
|
658
|
+
const disabledPersonalDataImport = disableCursorPersonalDataImport(resourcesRoot);
|
|
659
|
+
let localModePatches = 0;
|
|
660
|
+
let providerOnlyPatches = 0;
|
|
661
|
+
for (const relative of CURSOR_LOCAL_MODE_FILES) {
|
|
662
|
+
const target = path.join(resourcesRoot, relative);
|
|
663
|
+
const source = fs.readFileSync(target, "utf8");
|
|
664
|
+
if (countOccurrences(source, LOCAL_MODE_DISABLED) !== 1) {
|
|
665
|
+
throw new Error(`Cursor local-mode signature changed in ${relative}; refusing to patch`);
|
|
666
|
+
}
|
|
667
|
+
let patched = source.replace(LOCAL_MODE_DISABLED, LOCAL_MODE_ENABLED);
|
|
668
|
+
if (CURSOR_PROVIDER_LOCK_FILES.includes(relative)) {
|
|
669
|
+
patched = prioritizeManagedProvider(patched, relative);
|
|
670
|
+
providerOnlyPatches += 1;
|
|
671
|
+
}
|
|
672
|
+
if (Buffer.byteLength(patched) !== Buffer.byteLength(source)) {
|
|
673
|
+
throw new Error(`Cursor fixed-width patch changed ${relative} size`);
|
|
674
|
+
}
|
|
675
|
+
fs.writeFileSync(target, patched);
|
|
676
|
+
localModePatches += 1;
|
|
677
|
+
}
|
|
678
|
+
const extensionIntegrity = patchCursorExtensionIntegrity(resourcesRoot, reviewedExtensionIntegrity);
|
|
679
|
+
const reasoningTransport = patchCursorReasoningTransport(resourcesRoot);
|
|
680
|
+
const productChecksums = patchCursorProductChecksums(resourcesRoot);
|
|
681
|
+
return {
|
|
682
|
+
disabledPersonalDataImport,
|
|
683
|
+
extensionIntegrity,
|
|
684
|
+
isolatedApplicationName,
|
|
685
|
+
localMode: localModePatches,
|
|
686
|
+
managedProviderOnly: providerOnlyPatches,
|
|
687
|
+
productChecksums,
|
|
688
|
+
reasoningTransport,
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
export function cursorManagedPaths(homeDir = os.homedir(), tenantId, options = {}) {
|
|
693
|
+
const normalizedTenant = normalizeTenantId(tenantId);
|
|
694
|
+
const appRoot = options.appRoot
|
|
695
|
+
|| process.env[brandedEnvironmentName("APP_HOME")]
|
|
696
|
+
|| path.join(homeDir, ".config", RUNTIME_BRAND.cli.configNamespace, "apps");
|
|
697
|
+
const root = path.join(appRoot, "tenants", normalizedTenant, "cursor");
|
|
698
|
+
const generation = path.join(root, "runtime", "current");
|
|
699
|
+
return {
|
|
700
|
+
root,
|
|
701
|
+
home: path.join(root, "home"),
|
|
702
|
+
runtimeRoot: path.join(root, "runtime"),
|
|
703
|
+
generation,
|
|
704
|
+
bundle: path.join(generation, "Cursor.app"),
|
|
705
|
+
manifest: path.join(generation, "manifest.json"),
|
|
706
|
+
userData: path.join(root, "user-data"),
|
|
707
|
+
extensions: path.join(root, "extensions"),
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
export function resolveCursorVendorApp(homeDir = os.homedir(), environment = process.env) {
|
|
712
|
+
const explicit = String(environment.IMPEL_CURSOR_VENDOR_APP || "").trim();
|
|
713
|
+
const candidates = explicit
|
|
714
|
+
? [path.resolve(explicit)]
|
|
715
|
+
: ["/Applications/Cursor.app", path.join(homeDir, "Applications", "Cursor.app")];
|
|
716
|
+
const found = candidates.find((candidate) => fs.statSync(candidate, { throwIfNoEntry: false })?.isDirectory());
|
|
717
|
+
if (!found) throw new Error("Cursor.app is not installed; install the stable Cursor desktop app first");
|
|
718
|
+
return found;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function defaultCloneBundle(source, destination) {
|
|
722
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
723
|
+
const cloned = spawnSync("/bin/cp", ["-cR", source, destination], { encoding: "utf8" });
|
|
724
|
+
if (cloned.status === 0 && !cloned.error) return;
|
|
725
|
+
fs.rmSync(destination, { recursive: true, force: true });
|
|
726
|
+
fs.cpSync(source, destination, { recursive: true, dereference: false, preserveTimestamps: true });
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function defaultSignManagedBundle(bundle) {
|
|
730
|
+
const identity = resolveSigningIdentity();
|
|
731
|
+
spawnSync("/usr/bin/xattr", ["-dr", "com.apple.quarantine", bundle], { stdio: "ignore" });
|
|
732
|
+
const signed = runResult("/usr/bin/codesign", [
|
|
733
|
+
"--force",
|
|
734
|
+
"--deep",
|
|
735
|
+
"--options",
|
|
736
|
+
"runtime",
|
|
737
|
+
"--preserve-metadata=entitlements,flags,runtime",
|
|
738
|
+
...codesignIdentityArgs(identity),
|
|
739
|
+
bundle,
|
|
740
|
+
]);
|
|
741
|
+
if (signed.status !== 0 || signed.error) throw commandFailure("managed Cursor signing", signed);
|
|
742
|
+
const entitlementFiles = [];
|
|
743
|
+
try {
|
|
744
|
+
// A self-signed (or ad-hoc) local identity has no Apple Team ID. Electron's
|
|
745
|
+
// hardened main and helper processes would therefore refuse to map their
|
|
746
|
+
// separately identified Electron framework even though every individual
|
|
747
|
+
// signature passes `codesign --deep --strict`. Preserve each vendor
|
|
748
|
+
// entitlement set while disabling library validation on every process.
|
|
749
|
+
for (const target of cursorProcessBundles(bundle)) {
|
|
750
|
+
const entitlements = readCursorEntitlements(target);
|
|
751
|
+
const entitlementFile = path.join(
|
|
752
|
+
os.tmpdir(),
|
|
753
|
+
`impel-cursor-entitlements-${process.pid}-${crypto.randomBytes(4).toString("hex")}.plist`,
|
|
754
|
+
);
|
|
755
|
+
entitlementFiles.push(entitlementFile);
|
|
756
|
+
fs.writeFileSync(entitlementFile, cursorEntitlementsWithDisabledLibraryValidation(entitlements), { mode: 0o600 });
|
|
757
|
+
const restored = runResult("/usr/bin/codesign", [
|
|
758
|
+
"--force",
|
|
759
|
+
"--options",
|
|
760
|
+
"runtime",
|
|
761
|
+
"--entitlements",
|
|
762
|
+
entitlementFile,
|
|
763
|
+
...codesignIdentityArgs(identity),
|
|
764
|
+
target,
|
|
765
|
+
]);
|
|
766
|
+
if (restored.status !== 0 || restored.error) {
|
|
767
|
+
throw commandFailure("managed Cursor capability signing", restored);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
} finally {
|
|
771
|
+
for (const entitlementFile of entitlementFiles) fs.rmSync(entitlementFile, { force: true });
|
|
772
|
+
}
|
|
773
|
+
return identity.mode === "external" ? identity.descriptor : identity.mode;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function readCursorEntitlements(target) {
|
|
777
|
+
const inspected = runResult("/usr/bin/codesign", ["-d", "--entitlements", ":-", target]);
|
|
778
|
+
if (inspected.status !== 0 || inspected.error) {
|
|
779
|
+
throw commandFailure("managed Cursor entitlement inspection", inspected);
|
|
780
|
+
}
|
|
781
|
+
const source = String(inspected.stdout || "").trim();
|
|
782
|
+
if (!source.startsWith("<?xml") || !source.endsWith("</plist>")) {
|
|
783
|
+
throw new Error("managed Cursor entitlement inspection returned an unsupported plist");
|
|
784
|
+
}
|
|
785
|
+
return source;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
export function cursorEntitlementsWithDisabledLibraryValidation(source) {
|
|
789
|
+
const key = `<key>${DISABLE_LIBRARY_VALIDATION_ENTITLEMENT}</key>`;
|
|
790
|
+
const occurrences = countOccurrences(source, key);
|
|
791
|
+
if (occurrences > 1) throw new Error("Cursor library-validation entitlement is ambiguous");
|
|
792
|
+
if (occurrences === 1) {
|
|
793
|
+
const value = source.slice(source.indexOf(key) + key.length);
|
|
794
|
+
if (!/^\s*<true\s*\/>/u.test(value)) {
|
|
795
|
+
throw new Error("Cursor library-validation entitlement is not enabled");
|
|
796
|
+
}
|
|
797
|
+
return source;
|
|
798
|
+
}
|
|
799
|
+
if (countOccurrences(source, "</dict>") !== 1) {
|
|
800
|
+
throw new Error("Cursor entitlement plist contract changed");
|
|
801
|
+
}
|
|
802
|
+
return source.replace("</dict>", `${key}<true/></dict>`);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function defaultVerifyManagedBundleSignature(bundle) {
|
|
806
|
+
const verified = runResult("/usr/bin/codesign", ["--verify", "--deep", "--strict", bundle]);
|
|
807
|
+
if (verified.status !== 0 || verified.error) throw commandFailure("managed Cursor signature verification", verified);
|
|
808
|
+
for (const target of cursorProcessBundles(bundle)) {
|
|
809
|
+
cursorEntitlementsWithDisabledLibraryValidation(readCursorEntitlements(target));
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function defaultVerifyManagedBundle(bundle) {
|
|
814
|
+
defaultVerifyManagedBundleSignature(bundle);
|
|
815
|
+
assertCursorProductChecksums(appResources(bundle));
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function readManifest(manifestPath) {
|
|
819
|
+
try {
|
|
820
|
+
return JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
821
|
+
} catch {
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function signingModeCompatible(recorded, wanted) {
|
|
827
|
+
if (wanted === "local") return recorded === "local";
|
|
828
|
+
return recorded === wanted;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function manifestMatches(manifest, source, paths, wantedSigningMode) {
|
|
832
|
+
return manifestRuntimeMatches(manifest, paths, wantedSigningMode)
|
|
833
|
+
&& manifest?.vendor?.bundleIdentifier === source.bundleIdentifier
|
|
834
|
+
&& manifest?.vendor?.teamIdentifier === source.teamIdentifier
|
|
835
|
+
&& manifest?.vendor?.version === source.version
|
|
836
|
+
&& manifest?.vendor?.commit === source.commit
|
|
837
|
+
&& manifest?.vendor?.sourceFingerprint === source.sourceFingerprint;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function manifestRuntimeMatches(manifest, paths, wantedSigningMode) {
|
|
841
|
+
return manifest?.schemaVersion === CURSOR_RUNTIME_SCHEMA_VERSION
|
|
842
|
+
&& manifest?.tenantId === path.basename(path.dirname(paths.root))
|
|
843
|
+
&& manifest?.profile?.userData === paths.userData
|
|
844
|
+
&& manifest?.profile?.extensions === paths.extensions
|
|
845
|
+
&& manifest?.profile?.home === paths.home
|
|
846
|
+
&& manifest?.applicationName === managedCursorApplicationName(manifest.tenantId)
|
|
847
|
+
&& manifest?.authentication?.kind === "cursor-smoke-test"
|
|
848
|
+
&& manifest?.authentication?.storage === "tenant-only-global-storage"
|
|
849
|
+
&& manifest?.authentication?.processOverride === true
|
|
850
|
+
&& manifest?.patches?.disabledPersonalDataImport === 5
|
|
851
|
+
&& manifest?.patches?.extensionIntegrity === CURSOR_VERIFIED_EXTENSIONS.length
|
|
852
|
+
&& manifest?.patches?.isolatedApplicationName === 1
|
|
853
|
+
&& manifest?.patches?.localMode === CURSOR_LOCAL_MODE_FILES.length
|
|
854
|
+
&& manifest?.patches?.managedProviderOnly === CURSOR_PROVIDER_LOCK_FILES.length
|
|
855
|
+
&& Number.isInteger(manifest?.patches?.productChecksums)
|
|
856
|
+
&& manifest.patches.productChecksums > 0
|
|
857
|
+
&& manifest?.patches?.reasoningTransport === 1
|
|
858
|
+
&& signingModeCompatible(manifest?.signingMode, wantedSigningMode);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
function legacyManifestRuntimeMatches(manifest, paths, wantedSigningMode) {
|
|
862
|
+
return manifest?.schemaVersion === 4
|
|
863
|
+
&& manifest?.tenantId === path.basename(path.dirname(paths.root))
|
|
864
|
+
&& manifest?.profile?.userData === paths.userData
|
|
865
|
+
&& manifest?.profile?.extensions === paths.extensions
|
|
866
|
+
&& manifest?.profile?.home === paths.home
|
|
867
|
+
&& manifest?.applicationName === managedCursorApplicationName(manifest.tenantId)
|
|
868
|
+
&& manifest?.authentication?.kind === "cursor-smoke-test"
|
|
869
|
+
&& manifest?.authentication?.storage === "tenant-only-global-storage"
|
|
870
|
+
&& manifest?.authentication?.processOverride === true
|
|
871
|
+
&& manifest?.patches?.disabledPersonalDataImport === 5
|
|
872
|
+
&& manifest?.patches?.extensionIntegrity === CURSOR_VERIFIED_EXTENSIONS.length
|
|
873
|
+
&& manifest?.patches?.isolatedApplicationName === 1
|
|
874
|
+
&& manifest?.patches?.localMode === CURSOR_LOCAL_MODE_FILES.length
|
|
875
|
+
&& manifest?.patches?.managedProviderOnly === CURSOR_PROVIDER_LOCK_FILES.length
|
|
876
|
+
&& manifest?.patches?.productChecksums === undefined
|
|
877
|
+
&& manifest?.patches?.reasoningTransport === undefined
|
|
878
|
+
&& signingModeCompatible(manifest?.signingMode, wantedSigningMode);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function cursorSourcesMatch(first, second) {
|
|
882
|
+
return first?.bundleIdentifier === second?.bundleIdentifier
|
|
883
|
+
&& first?.teamIdentifier === second?.teamIdentifier
|
|
884
|
+
&& first?.version === second?.version
|
|
885
|
+
&& first?.commit === second?.commit
|
|
886
|
+
&& first?.sourceFingerprint === second?.sourceFingerprint;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function acquireCursorPrepareLock(runtimeRoot) {
|
|
890
|
+
const lockPath = path.join(runtimeRoot, CURSOR_PREPARE_LOCK_FILE);
|
|
891
|
+
if (process.platform !== "darwin") {
|
|
892
|
+
// Managed Cursor is macOS-only. This process-local fallback exists solely
|
|
893
|
+
// so the dependency-free unit suite can exercise serialization on the
|
|
894
|
+
// Linux and Windows CI runners without pretending to provide cross-process
|
|
895
|
+
// locking on unsupported platforms.
|
|
896
|
+
if (portableCursorPrepareLocks.has(lockPath)) {
|
|
897
|
+
throw new Error("another managed Cursor prepare is already running");
|
|
898
|
+
}
|
|
899
|
+
const descriptor = fs.openSync(lockPath, fs.constants.O_CREAT | fs.constants.O_RDWR, 0o600);
|
|
900
|
+
portableCursorPrepareLocks.add(lockPath);
|
|
901
|
+
return () => {
|
|
902
|
+
fs.closeSync(descriptor);
|
|
903
|
+
portableCursorPrepareLocks.delete(lockPath);
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
let descriptor;
|
|
908
|
+
try {
|
|
909
|
+
descriptor = fs.openSync(
|
|
910
|
+
lockPath,
|
|
911
|
+
fs.constants.O_CREAT | fs.constants.O_RDWR | fs.constants.O_NONBLOCK | MACOS_O_EXLOCK,
|
|
912
|
+
0o600,
|
|
913
|
+
);
|
|
914
|
+
} catch (error) {
|
|
915
|
+
if (["EAGAIN", "EWOULDBLOCK"].includes(error?.code)) {
|
|
916
|
+
throw new Error("another managed Cursor prepare is already running");
|
|
917
|
+
}
|
|
918
|
+
throw error;
|
|
919
|
+
}
|
|
920
|
+
return () => {
|
|
921
|
+
try {
|
|
922
|
+
if (descriptor !== undefined) fs.closeSync(descriptor);
|
|
923
|
+
} finally {
|
|
924
|
+
descriptor = undefined;
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function replaceGeneration(current, staging) {
|
|
930
|
+
const previous = `${current}.previous-${process.pid}`;
|
|
931
|
+
fs.rmSync(previous, { recursive: true, force: true });
|
|
932
|
+
const hadCurrent = fs.existsSync(current);
|
|
933
|
+
if (hadCurrent) fs.renameSync(current, previous);
|
|
934
|
+
try {
|
|
935
|
+
fs.renameSync(staging, current);
|
|
936
|
+
} catch (error) {
|
|
937
|
+
if (hadCurrent && !fs.existsSync(current) && fs.existsSync(previous)) fs.renameSync(previous, current);
|
|
938
|
+
throw error;
|
|
939
|
+
}
|
|
940
|
+
fs.rmSync(previous, { recursive: true, force: true });
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
function recoverPreviousGeneration(paths) {
|
|
944
|
+
if (fs.existsSync(paths.generation)) return;
|
|
945
|
+
let candidates = [];
|
|
946
|
+
try {
|
|
947
|
+
candidates = fs.readdirSync(paths.runtimeRoot, { withFileTypes: true })
|
|
948
|
+
.filter((entry) => entry.isDirectory() && entry.name.startsWith("current.previous-"))
|
|
949
|
+
.flatMap((entry) => {
|
|
950
|
+
try {
|
|
951
|
+
return [{
|
|
952
|
+
name: entry.name,
|
|
953
|
+
modifiedAt: fs.statSync(path.join(paths.runtimeRoot, entry.name)).mtimeMs,
|
|
954
|
+
}];
|
|
955
|
+
} catch {
|
|
956
|
+
return [];
|
|
957
|
+
}
|
|
958
|
+
})
|
|
959
|
+
.sort((first, second) => first.modifiedAt - second.modifiedAt || first.name.localeCompare(second.name));
|
|
960
|
+
} catch {
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
const latest = candidates.at(-1)?.name;
|
|
964
|
+
if (latest) fs.renameSync(path.join(paths.runtimeRoot, latest), paths.generation);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function sweepCursorGenerationArtifacts(paths) {
|
|
968
|
+
let entries = [];
|
|
969
|
+
try {
|
|
970
|
+
entries = fs.readdirSync(paths.runtimeRoot);
|
|
971
|
+
} catch {
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
const recovered = fs.existsSync(paths.generation);
|
|
975
|
+
for (const entry of entries) {
|
|
976
|
+
if ((recovered && entry.startsWith("current.previous-")) || entry.startsWith(".current.tmp-")) {
|
|
977
|
+
fs.rmSync(path.join(paths.runtimeRoot, entry), { recursive: true, force: true });
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function prepareManagedCursorGeneration({ tenantId, vendorPath }, dependencies, paths, inspect, wantedSigningMode) {
|
|
983
|
+
const source = inspect(vendorPath, dependencies);
|
|
984
|
+
recoverPreviousGeneration(paths);
|
|
985
|
+
sweepCursorGenerationArtifacts(paths);
|
|
986
|
+
|
|
987
|
+
const current = readManifest(paths.manifest);
|
|
988
|
+
if (manifestMatches(current, source, paths, wantedSigningMode) && fs.existsSync(paths.bundle)) {
|
|
989
|
+
try {
|
|
990
|
+
(dependencies.verifyManagedBundle || defaultVerifyManagedBundle)(paths.bundle);
|
|
991
|
+
return { updated: false, manifest: current, paths, source };
|
|
992
|
+
} catch {
|
|
993
|
+
// A corrupted or incompletely signed managed copy is rebuilt from the
|
|
994
|
+
// still-verified vendor source below. Personal Cursor state is unrelated.
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
const staging = path.join(paths.runtimeRoot, `.current.tmp-${process.pid}-${crypto.randomBytes(4).toString("hex")}`);
|
|
999
|
+
const stagingBundle = path.join(staging, "Cursor.app");
|
|
1000
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
1001
|
+
fs.mkdirSync(staging, { recursive: true, mode: 0o700 });
|
|
1002
|
+
try {
|
|
1003
|
+
(dependencies.cloneBundle || defaultCloneBundle)(vendorPath, stagingBundle);
|
|
1004
|
+
const clonedSource = inspect(stagingBundle, dependencies);
|
|
1005
|
+
const refreshedSource = inspect(vendorPath, dependencies);
|
|
1006
|
+
if (!cursorSourcesMatch(source, clonedSource) || !cursorSourcesMatch(source, refreshedSource)) {
|
|
1007
|
+
throw new Error("Cursor.app changed while its managed copy was being prepared; retry after the vendor update finishes");
|
|
1008
|
+
}
|
|
1009
|
+
const applicationName = managedCursorApplicationName(tenantId);
|
|
1010
|
+
const patches = patchCursorBundle(stagingBundle, { applicationName });
|
|
1011
|
+
const signingMode = (dependencies.signManagedBundle || defaultSignManagedBundle)(stagingBundle) || "adhoc";
|
|
1012
|
+
(dependencies.verifyManagedBundle || defaultVerifyManagedBundle)(stagingBundle);
|
|
1013
|
+
const manifest = {
|
|
1014
|
+
schemaVersion: CURSOR_RUNTIME_SCHEMA_VERSION,
|
|
1015
|
+
tenantId: normalizeTenantId(tenantId),
|
|
1016
|
+
applicationName,
|
|
1017
|
+
authentication: {
|
|
1018
|
+
kind: "cursor-smoke-test",
|
|
1019
|
+
storage: "tenant-only-global-storage",
|
|
1020
|
+
processOverride: true,
|
|
1021
|
+
},
|
|
1022
|
+
vendor: {
|
|
1023
|
+
path: vendorPath,
|
|
1024
|
+
bundleIdentifier: source.bundleIdentifier,
|
|
1025
|
+
teamIdentifier: source.teamIdentifier,
|
|
1026
|
+
version: source.version,
|
|
1027
|
+
commit: source.commit,
|
|
1028
|
+
sourceFingerprint: source.sourceFingerprint,
|
|
1029
|
+
},
|
|
1030
|
+
profile: {
|
|
1031
|
+
home: paths.home,
|
|
1032
|
+
userData: paths.userData,
|
|
1033
|
+
extensions: paths.extensions,
|
|
1034
|
+
},
|
|
1035
|
+
patches,
|
|
1036
|
+
signingMode,
|
|
1037
|
+
updatePolicy: "mirror-compatible-installed-stable-build",
|
|
1038
|
+
};
|
|
1039
|
+
if (!manifestMatches(manifest, source, paths, wantedSigningMode)) {
|
|
1040
|
+
throw new Error("generated managed Cursor manifest did not satisfy the reuse contract");
|
|
1041
|
+
}
|
|
1042
|
+
fs.writeFileSync(path.join(staging, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o600 });
|
|
1043
|
+
replaceGeneration(paths.generation, staging);
|
|
1044
|
+
return { updated: true, manifest, paths, source };
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
1047
|
+
throw error;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
export function prepareManagedCursor(options, dependencies = {}) {
|
|
1052
|
+
const {
|
|
1053
|
+
tenantId,
|
|
1054
|
+
homeDir = os.homedir(),
|
|
1055
|
+
vendorPath = resolveCursorVendorApp(homeDir),
|
|
1056
|
+
appRoot,
|
|
1057
|
+
} = options;
|
|
1058
|
+
if ((dependencies.platform || process.platform) !== "darwin") {
|
|
1059
|
+
throw new Error("the managed Cursor experiment currently supports macOS only");
|
|
1060
|
+
}
|
|
1061
|
+
const inspect = dependencies.inspectCursorVendorApp || inspectCursorVendorApp;
|
|
1062
|
+
const paths = cursorManagedPaths(homeDir, tenantId, { appRoot });
|
|
1063
|
+
const wantedSigningMode = dependencies.desiredSigningMode?.() || desiredSigningMode();
|
|
1064
|
+
fs.mkdirSync(paths.runtimeRoot, { recursive: true, mode: 0o700 });
|
|
1065
|
+
fs.mkdirSync(paths.home, { recursive: true, mode: 0o700 });
|
|
1066
|
+
fs.mkdirSync(paths.userData, { recursive: true, mode: 0o700 });
|
|
1067
|
+
fs.mkdirSync(paths.extensions, { recursive: true, mode: 0o700 });
|
|
1068
|
+
const releasePrepareLock = acquireCursorPrepareLock(paths.runtimeRoot);
|
|
1069
|
+
try {
|
|
1070
|
+
return prepareManagedCursorGeneration(
|
|
1071
|
+
{ tenantId, vendorPath },
|
|
1072
|
+
dependencies,
|
|
1073
|
+
paths,
|
|
1074
|
+
inspect,
|
|
1075
|
+
wantedSigningMode,
|
|
1076
|
+
);
|
|
1077
|
+
} finally {
|
|
1078
|
+
releasePrepareLock();
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
function upgradePreparedManagedCursorGeneration(manifest, dependencies, paths, wantedSigningMode) {
|
|
1083
|
+
const verifyLegacy = dependencies.verifyLegacyManagedBundle
|
|
1084
|
+
|| dependencies.verifyManagedBundle
|
|
1085
|
+
|| defaultVerifyManagedBundleSignature;
|
|
1086
|
+
verifyLegacy(paths.bundle);
|
|
1087
|
+
const staging = path.join(paths.runtimeRoot, `.current.tmp-${process.pid}-${crypto.randomBytes(4).toString("hex")}`);
|
|
1088
|
+
const stagingBundle = path.join(staging, "Cursor.app");
|
|
1089
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
1090
|
+
fs.mkdirSync(staging, { recursive: true, mode: 0o700 });
|
|
1091
|
+
try {
|
|
1092
|
+
(dependencies.cloneBundle || defaultCloneBundle)(paths.bundle, stagingBundle);
|
|
1093
|
+
const patches = {
|
|
1094
|
+
...manifest.patches,
|
|
1095
|
+
reasoningTransport: patchCursorReasoningTransport(appResources(stagingBundle)),
|
|
1096
|
+
};
|
|
1097
|
+
patches.productChecksums = patchCursorProductChecksums(appResources(stagingBundle));
|
|
1098
|
+
const signingMode = (dependencies.signManagedBundle || defaultSignManagedBundle)(stagingBundle) || "adhoc";
|
|
1099
|
+
(dependencies.verifyManagedBundle || defaultVerifyManagedBundle)(stagingBundle);
|
|
1100
|
+
const upgraded = {
|
|
1101
|
+
...manifest,
|
|
1102
|
+
schemaVersion: CURSOR_RUNTIME_SCHEMA_VERSION,
|
|
1103
|
+
patches,
|
|
1104
|
+
signingMode,
|
|
1105
|
+
};
|
|
1106
|
+
if (!manifestRuntimeMatches(upgraded, paths, wantedSigningMode)) {
|
|
1107
|
+
throw new Error("upgraded managed Cursor manifest did not satisfy the reuse contract");
|
|
1108
|
+
}
|
|
1109
|
+
fs.writeFileSync(path.join(staging, "manifest.json"), `${JSON.stringify(upgraded, null, 2)}\n`, { mode: 0o600 });
|
|
1110
|
+
replaceGeneration(paths.generation, staging);
|
|
1111
|
+
return {
|
|
1112
|
+
updated: true,
|
|
1113
|
+
manifest: upgraded,
|
|
1114
|
+
paths,
|
|
1115
|
+
source: upgraded.vendor,
|
|
1116
|
+
reusedWithoutVendorUpdate: true,
|
|
1117
|
+
upgradedPreparedRuntime: true,
|
|
1118
|
+
};
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
1121
|
+
throw error;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
export function reusePreparedManagedCursor(options, dependencies = {}) {
|
|
1126
|
+
const {
|
|
1127
|
+
tenantId,
|
|
1128
|
+
homeDir = os.homedir(),
|
|
1129
|
+
appRoot,
|
|
1130
|
+
} = options;
|
|
1131
|
+
if ((dependencies.platform || process.platform) !== "darwin") {
|
|
1132
|
+
throw new Error("the managed Cursor experiment currently supports macOS only");
|
|
1133
|
+
}
|
|
1134
|
+
const paths = cursorManagedPaths(homeDir, tenantId, { appRoot });
|
|
1135
|
+
const wantedSigningMode = dependencies.desiredSigningMode?.() || desiredSigningMode();
|
|
1136
|
+
if (!fs.statSync(paths.runtimeRoot, { throwIfNoEntry: false })?.isDirectory()) {
|
|
1137
|
+
throw new Error("no verified prepared managed Cursor runtime is available");
|
|
1138
|
+
}
|
|
1139
|
+
const releasePrepareLock = acquireCursorPrepareLock(paths.runtimeRoot);
|
|
1140
|
+
try {
|
|
1141
|
+
recoverPreviousGeneration(paths);
|
|
1142
|
+
sweepCursorGenerationArtifacts(paths);
|
|
1143
|
+
const manifest = readManifest(paths.manifest);
|
|
1144
|
+
if (!fs.existsSync(paths.bundle)) {
|
|
1145
|
+
throw new Error("no verified prepared managed Cursor runtime is available");
|
|
1146
|
+
}
|
|
1147
|
+
if (manifestRuntimeMatches(manifest, paths, wantedSigningMode)) {
|
|
1148
|
+
(dependencies.verifyManagedBundle || defaultVerifyManagedBundle)(paths.bundle);
|
|
1149
|
+
return {
|
|
1150
|
+
updated: false,
|
|
1151
|
+
manifest,
|
|
1152
|
+
paths,
|
|
1153
|
+
source: manifest.vendor,
|
|
1154
|
+
reusedWithoutVendorUpdate: true,
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
if (legacyManifestRuntimeMatches(manifest, paths, wantedSigningMode)) {
|
|
1158
|
+
return upgradePreparedManagedCursorGeneration(manifest, dependencies, paths, wantedSigningMode);
|
|
1159
|
+
}
|
|
1160
|
+
throw new Error("no verified prepared managed Cursor runtime is available");
|
|
1161
|
+
} finally {
|
|
1162
|
+
releasePrepareLock();
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
function argumentName(argument) {
|
|
1167
|
+
return String(argument).split("=", 1)[0];
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
export function assertCursorLaunchArguments(argv) {
|
|
1171
|
+
const blocked = argv.find((argument) => ISOLATION_BREAKING_ARGUMENTS.includes(argumentName(argument)));
|
|
1172
|
+
if (blocked) throw new Error(`Cursor argument ${blocked} would bypass the Impel tenant profile`);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
export function cursorSmokeAuthToken(now = Date.now()) {
|
|
1176
|
+
const issuedAt = Math.floor(Number(now) / 1_000);
|
|
1177
|
+
if (!Number.isSafeInteger(issuedAt) || issuedAt <= 0) {
|
|
1178
|
+
throw new Error("a valid time is required for Cursor managed authentication");
|
|
1179
|
+
}
|
|
1180
|
+
const encode = (value) => Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
1181
|
+
return `${encode({ alg: "none", typ: "JWT" })}.${encode({
|
|
1182
|
+
iss: "cursor-smoke-test",
|
|
1183
|
+
sub: "fake-user",
|
|
1184
|
+
aud: ["cursor"],
|
|
1185
|
+
iat: issuedAt,
|
|
1186
|
+
exp: issuedAt + (180 * 24 * 60 * 60),
|
|
1187
|
+
azp: "cursor-smoke-test",
|
|
1188
|
+
scope: "smoke-test",
|
|
1189
|
+
})}.`;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
function sqliteString(value) {
|
|
1193
|
+
return `'${String(value).replaceAll("'", "''")}'`;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
function parseSQLiteJSONHex(value, fallback, label) {
|
|
1197
|
+
try {
|
|
1198
|
+
const decoded = Buffer.from(String(value || ""), "hex").toString("utf8");
|
|
1199
|
+
return decoded ? JSON.parse(decoded) : fallback;
|
|
1200
|
+
} catch (error) {
|
|
1201
|
+
throw new Error(`managed Cursor ${label} is invalid: ${error?.message || error}`);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
function uniqueStrings(value) {
|
|
1206
|
+
return [...new Set(Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [])];
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
const CURSOR_REASONING_LABELS = Object.freeze({
|
|
1210
|
+
none: "None",
|
|
1211
|
+
minimal: "Minimal",
|
|
1212
|
+
low: "Low",
|
|
1213
|
+
medium: "Medium",
|
|
1214
|
+
high: "High",
|
|
1215
|
+
xhigh: "Extra High",
|
|
1216
|
+
max: "Max",
|
|
1217
|
+
ultra: "Ultra",
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
function cursorReasoningControls(model, capabilities, displayName) {
|
|
1221
|
+
const rawLevels = capabilities.reasoning_effort;
|
|
1222
|
+
if (rawLevels === undefined) return { parameterDefinitions: [], variants: [] };
|
|
1223
|
+
if (
|
|
1224
|
+
!Array.isArray(rawLevels)
|
|
1225
|
+
|| rawLevels.length === 0
|
|
1226
|
+
|| rawLevels.some((level) => (
|
|
1227
|
+
typeof level !== "string"
|
|
1228
|
+
|| !/^[a-z][a-z0-9_-]{0,31}$/u.test(level)
|
|
1229
|
+
))
|
|
1230
|
+
) {
|
|
1231
|
+
throw new Error(`Cursor model ${model.id} contains invalid reasoning effort levels`);
|
|
1232
|
+
}
|
|
1233
|
+
const levels = [...new Set(rawLevels)];
|
|
1234
|
+
const advertisedDefault = capabilities.default_reasoning_effort;
|
|
1235
|
+
if (advertisedDefault !== undefined && !levels.includes(advertisedDefault)) {
|
|
1236
|
+
throw new Error(`Cursor model ${model.id} contains an invalid default reasoning effort`);
|
|
1237
|
+
}
|
|
1238
|
+
const fallbackDefault = model.provider === "claude"
|
|
1239
|
+
? (levels.includes("high") ? "high" : levels[0])
|
|
1240
|
+
: (levels.includes("medium") ? "medium" : levels[0]);
|
|
1241
|
+
const defaultEffort = advertisedDefault || fallbackDefault;
|
|
1242
|
+
const label = (effort) => CURSOR_REASONING_LABELS[effort]
|
|
1243
|
+
|| `${effort.slice(0, 1).toUpperCase()}${effort.slice(1)}`;
|
|
1244
|
+
return {
|
|
1245
|
+
parameterDefinitions: [{
|
|
1246
|
+
id: "reasoning",
|
|
1247
|
+
name: "Reasoning",
|
|
1248
|
+
markdownTooltip: "Controls how much reasoning effort the model uses.",
|
|
1249
|
+
parameterType: {
|
|
1250
|
+
enumParameter: {
|
|
1251
|
+
values: levels.map((effort) => ({ value: effort, displayName: label(effort) })),
|
|
1252
|
+
},
|
|
1253
|
+
},
|
|
1254
|
+
isCycleableByHotkey: true,
|
|
1255
|
+
}],
|
|
1256
|
+
variants: levels.map((effort) => ({
|
|
1257
|
+
parameterValues: [{ id: "reasoning", value: effort }],
|
|
1258
|
+
displayName: `${displayName} ${label(effort)}`,
|
|
1259
|
+
displayNameOutsidePicker: `${displayName} ${label(effort)}`,
|
|
1260
|
+
isMaxMode: false,
|
|
1261
|
+
isDefaultNonMaxConfig: effort === defaultEffort,
|
|
1262
|
+
isDefaultMaxConfig: effort === defaultEffort,
|
|
1263
|
+
})),
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
function cursorCatalogProjection(models) {
|
|
1268
|
+
const byId = new Map();
|
|
1269
|
+
for (const entry of Array.isArray(models) ? models : []) {
|
|
1270
|
+
const model = typeof entry === "string" ? { id: entry } : entry;
|
|
1271
|
+
const modelId = model?.id;
|
|
1272
|
+
if (
|
|
1273
|
+
typeof modelId !== "string"
|
|
1274
|
+
|| modelId === "default"
|
|
1275
|
+
|| modelId.length > 128
|
|
1276
|
+
|| !/^[A-Za-z0-9][A-Za-z0-9._:/-]*$/u.test(modelId)
|
|
1277
|
+
) {
|
|
1278
|
+
throw new Error("Cursor model catalog contains an invalid model identifier");
|
|
1279
|
+
}
|
|
1280
|
+
if (!byId.has(modelId)) byId.set(modelId, model);
|
|
1281
|
+
}
|
|
1282
|
+
if (byId.size === 0) throw new Error("Cursor model catalog contains no model identifiers");
|
|
1283
|
+
return [...byId].map(([modelId, model]) => {
|
|
1284
|
+
const capabilities = model?.capabilities && typeof model.capabilities === "object"
|
|
1285
|
+
? model.capabilities
|
|
1286
|
+
: {};
|
|
1287
|
+
const displayName = typeof model.display_name === "string" && model.display_name.trim()
|
|
1288
|
+
? model.display_name.trim()
|
|
1289
|
+
: modelId;
|
|
1290
|
+
const reasoningControls = cursorReasoningControls(model, capabilities, displayName);
|
|
1291
|
+
return {
|
|
1292
|
+
defaultOn: true,
|
|
1293
|
+
name: modelId,
|
|
1294
|
+
clientDisplayName: displayName,
|
|
1295
|
+
serverModelName: modelId,
|
|
1296
|
+
inputboxShortModelName: displayName,
|
|
1297
|
+
supportsAgent: true,
|
|
1298
|
+
supportsNonMaxMode: true,
|
|
1299
|
+
supportsThinking: capabilities.supports_reasoning === true,
|
|
1300
|
+
supportsVision: capabilities.supports_vision === true,
|
|
1301
|
+
namedModelSectionIndex: 99,
|
|
1302
|
+
isRecommendedForBackgroundComposer: false,
|
|
1303
|
+
isUserAdded: true,
|
|
1304
|
+
idAliases: [],
|
|
1305
|
+
cloudAgentEffortModes: [],
|
|
1306
|
+
modelPickerBadges: [],
|
|
1307
|
+
parameterDefinitions: reasoningControls.parameterDefinitions,
|
|
1308
|
+
variants: reasoningControls.variants,
|
|
1309
|
+
legacySlugs: [],
|
|
1310
|
+
};
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
export function mergeCursorManagedModels(persistentStorage, previousModelIds, models, gatewayUrl) {
|
|
1315
|
+
if (!persistentStorage || typeof persistentStorage !== "object" || Array.isArray(persistentStorage)) {
|
|
1316
|
+
throw new Error("managed Cursor application storage is invalid");
|
|
1317
|
+
}
|
|
1318
|
+
const modelDescriptors = cursorCatalogProjection(models);
|
|
1319
|
+
const current = modelDescriptors.map((model) => model.name);
|
|
1320
|
+
const previous = new Set(uniqueStrings(previousModelIds));
|
|
1321
|
+
const managed = new Set([...previous, ...current]);
|
|
1322
|
+
const existingAISettings = persistentStorage.aiSettings;
|
|
1323
|
+
const aiSettings = existingAISettings && typeof existingAISettings === "object" && !Array.isArray(existingAISettings)
|
|
1324
|
+
? { ...existingAISettings }
|
|
1325
|
+
: {};
|
|
1326
|
+
const replaceManaged = (value) => uniqueStrings([
|
|
1327
|
+
...uniqueStrings(value).filter((entry) => !previous.has(entry)),
|
|
1328
|
+
...current,
|
|
1329
|
+
]);
|
|
1330
|
+
aiSettings.userAddedModels = replaceManaged(aiSettings.userAddedModels);
|
|
1331
|
+
aiSettings.modelOverrideEnabled = replaceManaged(aiSettings.modelOverrideEnabled);
|
|
1332
|
+
aiSettings.modelOverrideDisabled = uniqueStrings(aiSettings.modelOverrideDisabled)
|
|
1333
|
+
.filter((entry) => !managed.has(entry));
|
|
1334
|
+
const retainedModels = (Array.isArray(persistentStorage.availableDefaultModels2)
|
|
1335
|
+
? persistentStorage.availableDefaultModels2
|
|
1336
|
+
: [])
|
|
1337
|
+
.filter((model) => (
|
|
1338
|
+
model
|
|
1339
|
+
&& typeof model === "object"
|
|
1340
|
+
&& typeof model.name === "string"
|
|
1341
|
+
&& !managed.has(model.name)
|
|
1342
|
+
));
|
|
1343
|
+
if (!retainedModels.some((model) => model.name === "default")) {
|
|
1344
|
+
retainedModels.unshift({
|
|
1345
|
+
defaultOn: true,
|
|
1346
|
+
name: "default",
|
|
1347
|
+
clientDisplayName: "Auto",
|
|
1348
|
+
inputboxShortModelName: "Auto",
|
|
1349
|
+
supportsAgent: true,
|
|
1350
|
+
isRecommendedForBackgroundComposer: true,
|
|
1351
|
+
idAliases: [],
|
|
1352
|
+
cloudAgentEffortModes: [],
|
|
1353
|
+
modelPickerBadges: [],
|
|
1354
|
+
parameterDefinitions: [],
|
|
1355
|
+
variants: [],
|
|
1356
|
+
legacySlugs: [],
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
let managedGatewayUrl;
|
|
1360
|
+
if (gatewayUrl !== undefined) {
|
|
1361
|
+
try {
|
|
1362
|
+
const parsed = new URL(gatewayUrl);
|
|
1363
|
+
if (!["http:", "https:"].includes(parsed.protocol) || parsed.username || parsed.password) throw new Error();
|
|
1364
|
+
managedGatewayUrl = parsed.toString().replace(/\/$/u, "");
|
|
1365
|
+
} catch {
|
|
1366
|
+
throw new Error("managed Cursor gateway URL is invalid");
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
return {
|
|
1370
|
+
persistentStorage: {
|
|
1371
|
+
...persistentStorage,
|
|
1372
|
+
aiSettings,
|
|
1373
|
+
localProviderModelIds: current,
|
|
1374
|
+
localProviderAgentModelIds: current,
|
|
1375
|
+
availableDefaultModels2: [...retainedModels, ...modelDescriptors],
|
|
1376
|
+
...(managedGatewayUrl ? {
|
|
1377
|
+
openAIBaseUrl: managedGatewayUrl,
|
|
1378
|
+
useOpenAIKey: true,
|
|
1379
|
+
} : {}),
|
|
1380
|
+
},
|
|
1381
|
+
managedModelIds: current,
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
export function ensureCursorManagedModels(userData, models, gatewayUrl, dependencies = {}) {
|
|
1386
|
+
if (typeof userData !== "string" || userData.length === 0 || userData.includes("\0")) {
|
|
1387
|
+
throw new Error("a valid managed Cursor user-data directory is required");
|
|
1388
|
+
}
|
|
1389
|
+
const globalStorage = path.join(userData, "User", "globalStorage");
|
|
1390
|
+
const database = path.join(globalStorage, "state.vscdb");
|
|
1391
|
+
fs.mkdirSync(globalStorage, { recursive: true, mode: 0o700 });
|
|
1392
|
+
const readSQL = [
|
|
1393
|
+
"PRAGMA busy_timeout=5000;",
|
|
1394
|
+
"CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB);",
|
|
1395
|
+
`SELECT hex(CAST(COALESCE((SELECT value FROM ItemTable WHERE key=${sqliteString(CURSOR_REACTIVE_STORAGE_KEY)}),'{}') AS BLOB));`,
|
|
1396
|
+
`SELECT hex(CAST(COALESCE((SELECT value FROM ItemTable WHERE key=${sqliteString(CURSOR_MANAGED_MODELS_KEY)}),'[]') AS BLOB));`,
|
|
1397
|
+
].join("");
|
|
1398
|
+
const readResult = dependencies.querySQLite
|
|
1399
|
+
? dependencies.querySQLite(database, readSQL)
|
|
1400
|
+
: spawnSync("/usr/bin/sqlite3", ["-batch", "-noheader", database, readSQL], { encoding: "utf8" });
|
|
1401
|
+
if (readResult?.status !== 0 || readResult?.error) {
|
|
1402
|
+
const detail = readResult?.error?.message || String(readResult?.stderr || "").trim() || "unknown sqlite error";
|
|
1403
|
+
throw new Error(`could not read managed Cursor models: ${detail}`);
|
|
1404
|
+
}
|
|
1405
|
+
const outputLines = String(readResult.stdout || "").trimEnd().split(/\r?\n/u);
|
|
1406
|
+
const [storageHex = "", previousHex = ""] = outputLines.slice(-2);
|
|
1407
|
+
const persistentStorage = parseSQLiteJSONHex(storageHex, {}, "application storage");
|
|
1408
|
+
const previousModelIds = parseSQLiteJSONHex(previousHex, [], "model marker");
|
|
1409
|
+
const merged = mergeCursorManagedModels(persistentStorage, previousModelIds, models, gatewayUrl);
|
|
1410
|
+
const writeSQL = [
|
|
1411
|
+
"PRAGMA busy_timeout=5000;",
|
|
1412
|
+
"CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB);",
|
|
1413
|
+
"BEGIN IMMEDIATE;",
|
|
1414
|
+
`INSERT OR REPLACE INTO ItemTable (key,value) VALUES (${sqliteString(CURSOR_REACTIVE_STORAGE_KEY)},${sqliteString(JSON.stringify(merged.persistentStorage))});`,
|
|
1415
|
+
`INSERT OR REPLACE INTO ItemTable (key,value) VALUES (${sqliteString(CURSOR_MANAGED_MODELS_KEY)},${sqliteString(JSON.stringify(merged.managedModelIds))});`,
|
|
1416
|
+
`INSERT OR REPLACE INTO ItemTable (key,value) VALUES ('cursorAuth/openAIKey',${sqliteString(CURSOR_MANAGED_GATEWAY_SENTINEL)});`,
|
|
1417
|
+
"COMMIT;",
|
|
1418
|
+
].join("");
|
|
1419
|
+
const writeResult = dependencies.runSQLite
|
|
1420
|
+
? dependencies.runSQLite(database, writeSQL)
|
|
1421
|
+
: spawnSync("/usr/bin/sqlite3", [database, writeSQL], { encoding: "utf8" });
|
|
1422
|
+
if (writeResult?.status !== 0 || writeResult?.error) {
|
|
1423
|
+
const detail = writeResult?.error?.message || String(writeResult?.stderr || "").trim() || "unknown sqlite error";
|
|
1424
|
+
throw new Error(`could not initialize managed Cursor models: ${detail}`);
|
|
1425
|
+
}
|
|
1426
|
+
fs.chmodSync(database, 0o600);
|
|
1427
|
+
return { database, modelIds: merged.managedModelIds };
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
export function ensureCursorManagedAuthentication(userData, dependencies = {}) {
|
|
1431
|
+
if (typeof userData !== "string" || userData.length === 0 || userData.includes("\0")) {
|
|
1432
|
+
throw new Error("a valid managed Cursor user-data directory is required");
|
|
1433
|
+
}
|
|
1434
|
+
const token = cursorSmokeAuthToken((dependencies.now || Date.now)());
|
|
1435
|
+
const globalStorage = path.join(userData, "User", "globalStorage");
|
|
1436
|
+
const database = path.join(globalStorage, "state.vscdb");
|
|
1437
|
+
fs.mkdirSync(globalStorage, { recursive: true, mode: 0o700 });
|
|
1438
|
+
const sql = [
|
|
1439
|
+
"PRAGMA busy_timeout=5000;",
|
|
1440
|
+
"CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB);",
|
|
1441
|
+
"BEGIN IMMEDIATE;",
|
|
1442
|
+
`INSERT OR REPLACE INTO ItemTable (key,value) VALUES ('cursorAuth/accessToken',${sqliteString(token)});`,
|
|
1443
|
+
"INSERT OR REPLACE INTO ItemTable (key,value) VALUES ('cursorAuth/refreshToken','fake-refresh-token-for-testing');",
|
|
1444
|
+
"DELETE FROM ItemTable WHERE key IN ('cursorAuth/teamId','autorun.cachedAdminSettings');",
|
|
1445
|
+
"COMMIT;",
|
|
1446
|
+
].join("");
|
|
1447
|
+
const result = dependencies.runSQLite
|
|
1448
|
+
? dependencies.runSQLite(database, sql)
|
|
1449
|
+
: spawnSync("/usr/bin/sqlite3", [database, sql], { encoding: "utf8" });
|
|
1450
|
+
if (result?.status !== 0 || result?.error) {
|
|
1451
|
+
const detail = result?.error?.message || String(result?.stderr || "").trim() || "unknown sqlite error";
|
|
1452
|
+
throw new Error(`could not initialize managed Cursor authentication: ${detail}`);
|
|
1453
|
+
}
|
|
1454
|
+
fs.chmodSync(database, 0o600);
|
|
1455
|
+
return { database, token };
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
export function cursorLaunchSpec({
|
|
1459
|
+
paths,
|
|
1460
|
+
gatewayUrl,
|
|
1461
|
+
credential,
|
|
1462
|
+
tenantId,
|
|
1463
|
+
cursorAuthToken,
|
|
1464
|
+
argv = [],
|
|
1465
|
+
environment = process.env,
|
|
1466
|
+
}) {
|
|
1467
|
+
assertCursorLaunchArguments(argv);
|
|
1468
|
+
if (typeof cursorAuthToken !== "string" || !cursorAuthToken.endsWith(".") || cursorAuthToken.split(".").length !== 3) {
|
|
1469
|
+
throw new Error("managed Cursor authentication was not initialized");
|
|
1470
|
+
}
|
|
1471
|
+
if (Buffer.byteLength(paths.userData) + CURSOR_IPC_SOCKET_SUFFIX_RESERVE > CURSOR_IPC_SOCKET_PATH_LIMIT) {
|
|
1472
|
+
throw new Error(`managed Cursor user-data path is too long for its macOS IPC socket: ${paths.userData}`);
|
|
1473
|
+
}
|
|
1474
|
+
const env = { ...environment };
|
|
1475
|
+
for (const key of [...DIRECT_PROVIDER_ENV_KEYS, ...RUNTIME_OVERRIDE_ENV_KEYS]) delete env[key];
|
|
1476
|
+
env.HOME = paths.home;
|
|
1477
|
+
env.XDG_CACHE_HOME = path.join(paths.home, ".cache");
|
|
1478
|
+
env.XDG_CONFIG_HOME = path.join(paths.home, ".config");
|
|
1479
|
+
env.XDG_DATA_HOME = path.join(paths.home, ".local", "share");
|
|
1480
|
+
env.CURSOR_LOCAL_AGENT_BASE_URL = `${String(gatewayUrl).replace(/\/+$/u, "")}/experimental/openai/v1`;
|
|
1481
|
+
env.CURSOR_LOCAL_AGENT_API_KEY = credential;
|
|
1482
|
+
env.IMPEL_TENANT_ID = normalizeTenantId(tenantId);
|
|
1483
|
+
return {
|
|
1484
|
+
command: path.join(paths.bundle, "Contents", "MacOS", "Cursor"),
|
|
1485
|
+
args: [
|
|
1486
|
+
`--user-data-dir=${paths.userData}`,
|
|
1487
|
+
`--extensions-dir=${paths.extensions}`,
|
|
1488
|
+
"--disable-updates",
|
|
1489
|
+
"--use-inmemory-secretstorage",
|
|
1490
|
+
"--skip-welcome",
|
|
1491
|
+
"--skip-onboarding",
|
|
1492
|
+
`--override-cursor-auth-token=${cursorAuthToken}`,
|
|
1493
|
+
"--new-window",
|
|
1494
|
+
...argv,
|
|
1495
|
+
],
|
|
1496
|
+
env,
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
export function launchManagedCursor(spec, dependencies = {}) {
|
|
1501
|
+
const spawnImpl = dependencies.spawn || spawn;
|
|
1502
|
+
return new Promise((resolve, reject) => {
|
|
1503
|
+
const child = spawnImpl(spec.command, spec.args, {
|
|
1504
|
+
cwd: process.cwd(),
|
|
1505
|
+
detached: true,
|
|
1506
|
+
env: spec.env,
|
|
1507
|
+
stdio: "ignore",
|
|
1508
|
+
});
|
|
1509
|
+
child.once("error", reject);
|
|
1510
|
+
child.once("spawn", () => {
|
|
1511
|
+
child.unref();
|
|
1512
|
+
resolve();
|
|
1513
|
+
});
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
export function managedCursorStatus(options, dependencies = {}) {
|
|
1518
|
+
const {
|
|
1519
|
+
tenantId,
|
|
1520
|
+
homeDir = os.homedir(),
|
|
1521
|
+
vendorPath = null,
|
|
1522
|
+
appRoot,
|
|
1523
|
+
} = options;
|
|
1524
|
+
const paths = cursorManagedPaths(homeDir, tenantId, { appRoot });
|
|
1525
|
+
let source;
|
|
1526
|
+
let compatibilityError = null;
|
|
1527
|
+
try {
|
|
1528
|
+
const resolvedVendorPath = vendorPath || resolveCursorVendorApp(homeDir);
|
|
1529
|
+
source = (dependencies.inspectCursorVendorApp || inspectCursorVendorApp)(resolvedVendorPath, dependencies);
|
|
1530
|
+
} catch (error) {
|
|
1531
|
+
compatibilityError = error?.message || String(error);
|
|
1532
|
+
}
|
|
1533
|
+
const manifest = readManifest(paths.manifest);
|
|
1534
|
+
const wantedSigningMode = dependencies.desiredSigningMode?.() || desiredSigningMode();
|
|
1535
|
+
let runtimeError = null;
|
|
1536
|
+
let ready = Boolean(source && manifestMatches(manifest, source, paths, wantedSigningMode) && fs.existsSync(paths.bundle));
|
|
1537
|
+
if (ready) {
|
|
1538
|
+
try {
|
|
1539
|
+
(dependencies.verifyManagedBundle || defaultVerifyManagedBundle)(paths.bundle);
|
|
1540
|
+
} catch (error) {
|
|
1541
|
+
ready = false;
|
|
1542
|
+
runtimeError = error?.message || String(error);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
return {
|
|
1546
|
+
ready,
|
|
1547
|
+
compatibleVendor: Boolean(source),
|
|
1548
|
+
compatibilityError,
|
|
1549
|
+
runtimeError,
|
|
1550
|
+
vendor: source || null,
|
|
1551
|
+
manifest,
|
|
1552
|
+
paths,
|
|
1553
|
+
};
|
|
1554
|
+
}
|