pi-cursor-bridge 0.1.0

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.
@@ -0,0 +1,1290 @@
1
+ import { createRequire } from 'module'; const require = createRequire(import.meta.url);
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // cursor-runtime.mjs
13
+ import { execFileSync, spawn } from "node:child_process";
14
+ import { mkdirSync as mkdirSync2, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
15
+ import { homedir as homedir2 } from "node:os";
16
+ import { basename, dirname, join as join2, resolve } from "node:path";
17
+ function normalizeCursorRuntimeMode(value, fallback = "normal") {
18
+ const normalized = String(value || "").trim().toLowerCase();
19
+ return CURSOR_RUNTIME_MODES.includes(normalized) ? normalized : fallback;
20
+ }
21
+ function resolveCursorRuntimeFile(value = process.env.CURSOR_BRIDGE_RUNTIME_FILE) {
22
+ const configured = String(value || "").trim();
23
+ if (configured) return resolve(configured);
24
+ const configRoot = process.platform === "win32" && process.env.APPDATA ? process.env.APPDATA : process.env.XDG_CONFIG_HOME || join2(homedir2(), ".config");
25
+ return join2(configRoot, "cursor-bridge", "runtime.json");
26
+ }
27
+ function parseNetstatListeningPid(output, port) {
28
+ const expectedPort = Number(port);
29
+ if (!Number.isInteger(expectedPort) || expectedPort <= 0) return null;
30
+ for (const line of String(output || "").split(/\r?\n/)) {
31
+ const columns = line.trim().split(/\s+/);
32
+ if (columns.length < 5 || String(columns[0]).toUpperCase() !== "TCP") continue;
33
+ const local = columns[1] || "";
34
+ const state = String(columns[3] || "").toUpperCase();
35
+ const pid = Number(columns[4]);
36
+ const portMatch = local.match(/:(\d+)$/);
37
+ if (state === "LISTENING" && portMatch && Number(portMatch[1]) === expectedPort && Number.isInteger(pid) && pid > 0) {
38
+ return pid;
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+ function findCursorPidByPort(port, options = {}) {
44
+ if ((options.platform || process.platform) !== "win32") return null;
45
+ const run = options.execFileSyncImpl || execFileSync;
46
+ try {
47
+ const output = run("netstat.exe", ["-ano", "-p", "tcp"], {
48
+ encoding: "utf8",
49
+ windowsHide: true,
50
+ stdio: ["ignore", "pipe", "ignore"]
51
+ });
52
+ return parseNetstatListeningPid(output, port);
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+ function powershellWindowScript(options) {
58
+ const targetPid = Number(options.pid);
59
+ const loop = options.loop === true;
60
+ const lifetime = options.lifetime === true;
61
+ const show = options.action === "show" ? "$true" : "$false";
62
+ const iterations = Number(options.iterations || 1);
63
+ const intervalMs = Number(options.intervalMs || 100);
64
+ const showFlagPath = String(options.showFlagPath || "").replace(/'/g, "''");
65
+ const hideIfAllowed = `if (-not (Test-Path -LiteralPath '${showFlagPath}')) { [void][CursorBridgeWindowControl]::Apply(${targetPid}, $false) }`;
66
+ const mutexName = `Local\\CursorBridgeMinimalGuard-${targetPid}`;
67
+ const lifetimeLoop = [
68
+ "$createdNew = $false",
69
+ `$guardMutex = [System.Threading.Mutex]::new($true, '${mutexName}', [ref]$createdNew)`,
70
+ "if (-not $createdNew) { $guardMutex.Dispose(); exit 0 }",
71
+ "try {",
72
+ " while ($true) {",
73
+ ` $target = Get-Process -Id ${targetPid} -ErrorAction SilentlyContinue`,
74
+ " if ($null -eq $target -or $target.ProcessName -ine 'Cursor') { break }",
75
+ ` ${hideIfAllowed}`,
76
+ ` Start-Sleep -Milliseconds ${intervalMs}`,
77
+ " }",
78
+ "} finally {",
79
+ " try { $guardMutex.ReleaseMutex() } catch {}",
80
+ " $guardMutex.Dispose()",
81
+ ` Remove-Item -LiteralPath '${showFlagPath}' -Force -ErrorAction SilentlyContinue`,
82
+ "}"
83
+ ].join("\n");
84
+ const apply = lifetime ? lifetimeLoop : loop ? `for ($i = 0; $i -lt ${iterations}; $i++) { ${hideIfAllowed}; Start-Sleep -Milliseconds ${intervalMs} }` : `$changed = [CursorBridgeWindowControl]::Apply(${targetPid}, ${show}); [Console]::Out.Write($changed)`;
85
+ return `$ErrorActionPreference = 'Stop'
86
+ Add-Type -TypeDefinition @'
87
+ ${WINDOW_CONTROL_TYPE}
88
+ '@
89
+ ${apply}`;
90
+ }
91
+ function encodePowerShell(script) {
92
+ return Buffer.from(String(script), "utf16le").toString("base64");
93
+ }
94
+ function setCursorWindowPresentation(options = {}) {
95
+ const platform = options.platform || process.platform;
96
+ const action = String(options.action || "").trim().toLowerCase();
97
+ if (!["hide", "show"].includes(action)) throw new Error(`unsupported Cursor window action: ${options.action}`);
98
+ if (platform !== "win32") {
99
+ return { supported: false, applied: false, action, reason: `window control is not implemented for ${platform}` };
100
+ }
101
+ const port = Number(options.port || 9223);
102
+ const pid = Number(options.pid || findCursorPidByPort(port, options));
103
+ if (!Number.isInteger(pid) || pid <= 0) {
104
+ return { supported: true, applied: false, action, port, reason: `no listening Cursor PID found on CDP ${port}` };
105
+ }
106
+ const showFlagPath = resolve(options.showFlagPath || join2(dirname(resolveCursorRuntimeFile()), `show-${pid}.flag`));
107
+ try {
108
+ if (action === "show") {
109
+ mkdirSync2(dirname(showFlagPath), { recursive: true });
110
+ writeFileSync(showFlagPath, `${pid}
111
+ `, { encoding: "utf8", mode: 384 });
112
+ } else {
113
+ rmSync(showFlagPath, { force: true });
114
+ }
115
+ } catch (error) {
116
+ return {
117
+ supported: true,
118
+ applied: false,
119
+ action,
120
+ port,
121
+ pid,
122
+ reason: `failed to update minimal-window override: ${error instanceof Error ? error.message : String(error)}`
123
+ };
124
+ }
125
+ const run = options.execFileSyncImpl || execFileSync;
126
+ try {
127
+ const script = powershellWindowScript({ pid, action });
128
+ const output = run("powershell.exe", [
129
+ "-NoLogo",
130
+ "-NoProfile",
131
+ "-NonInteractive",
132
+ "-ExecutionPolicy",
133
+ "Bypass",
134
+ "-EncodedCommand",
135
+ encodePowerShell(script)
136
+ ], {
137
+ encoding: "utf8",
138
+ windowsHide: true,
139
+ stdio: ["ignore", "pipe", "pipe"],
140
+ timeout: Number(options.timeoutMs || 15e3)
141
+ });
142
+ const changedWindows = Number(String(output || "").trim() || 0);
143
+ return { supported: true, applied: true, action, port, pid, changedWindows, showFlagPath };
144
+ } catch (error) {
145
+ if (action === "show") rmSync(showFlagPath, { force: true });
146
+ return {
147
+ supported: true,
148
+ applied: false,
149
+ action,
150
+ port,
151
+ pid,
152
+ reason: error instanceof Error ? error.message : String(error)
153
+ };
154
+ }
155
+ }
156
+ function startMinimalWindowGuard(pid, options = {}) {
157
+ if ((options.platform || process.platform) !== "win32") return { started: false, reason: "unsupported-platform" };
158
+ const targetPid = Number(pid);
159
+ if (!Number.isInteger(targetPid) || targetPid <= 0) return { started: false, reason: "invalid-pid" };
160
+ const intervalMs = Math.max(50, Math.min(1e3, Number(options.intervalMs || 100)));
161
+ const lifetime = options.durationMs == null;
162
+ const durationMs = lifetime ? null : Math.max(intervalMs, Math.min(12e4, Number(options.durationMs)));
163
+ const iterations = lifetime ? null : Math.ceil(durationMs / intervalMs);
164
+ const showFlagPath = resolve(options.showFlagPath || join2(dirname(resolveCursorRuntimeFile()), `show-${targetPid}.flag`));
165
+ const spawnImpl = options.spawnImpl || spawn;
166
+ try {
167
+ const script = powershellWindowScript({
168
+ pid: targetPid,
169
+ loop: !lifetime,
170
+ lifetime,
171
+ iterations,
172
+ intervalMs,
173
+ showFlagPath
174
+ });
175
+ const child = spawnImpl("powershell.exe", [
176
+ "-NoLogo",
177
+ "-NoProfile",
178
+ "-NonInteractive",
179
+ "-ExecutionPolicy",
180
+ "Bypass",
181
+ "-EncodedCommand",
182
+ encodePowerShell(script)
183
+ ], { detached: !lifetime, stdio: "ignore", windowsHide: true });
184
+ if (!lifetime && child && typeof child.unref === "function") child.unref();
185
+ return {
186
+ started: true,
187
+ pid: child && child.pid || null,
188
+ targetPid,
189
+ lifetime,
190
+ retainedBySupervisor: lifetime,
191
+ durationMs,
192
+ intervalMs,
193
+ showFlagPath
194
+ };
195
+ } catch (error) {
196
+ return { started: false, targetPid, reason: error instanceof Error ? error.message : String(error) };
197
+ }
198
+ }
199
+ var CURSOR_RUNTIME_MODES, WINDOW_CONTROL_TYPE;
200
+ var init_cursor_runtime = __esm({
201
+ "cursor-runtime.mjs"() {
202
+ CURSOR_RUNTIME_MODES = Object.freeze(["normal", "minimal"]);
203
+ WINDOW_CONTROL_TYPE = String.raw`
204
+ using System;
205
+ using System.Runtime.InteropServices;
206
+ using System.Text;
207
+
208
+ public static class CursorBridgeWindowControl {
209
+ [StructLayout(LayoutKind.Sequential)]
210
+ private struct RECT {
211
+ public int Left;
212
+ public int Top;
213
+ public int Right;
214
+ public int Bottom;
215
+ }
216
+
217
+ private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
218
+ [DllImport("user32.dll")] private static extern bool EnumWindows(EnumWindowsProc callback, IntPtr lParam);
219
+ [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
220
+ [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr hWnd);
221
+ [DllImport("user32.dll")] private static extern bool IsIconic(IntPtr hWnd);
222
+ [DllImport("user32.dll")] private static extern bool IsZoomed(IntPtr hWnd);
223
+ [DllImport("user32.dll", EntryPoint = "IsWindowArranged")] private static extern bool IsWindowArranged(IntPtr hWnd);
224
+ [DllImport("user32.dll")] private static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
225
+ [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetWindowTextLengthW(IntPtr hWnd);
226
+ [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetClassNameW(IntPtr hWnd, StringBuilder className, int maxCount);
227
+ [DllImport("user32.dll")] private static extern bool ShowWindowAsync(IntPtr hWnd, int command);
228
+ [DllImport("user32.dll")] private static extern bool SetWindowPos(IntPtr hWnd, IntPtr insertAfter, int x, int y, int width, int height, uint flags);
229
+ [DllImport("user32.dll")] private static extern bool RedrawWindow(IntPtr hWnd, IntPtr updateRect, IntPtr updateRegion, uint flags);
230
+
231
+ private const uint SWP_NOSIZE = 0x0001;
232
+ private const uint SWP_NOMOVE = 0x0002;
233
+ private const uint SWP_NOZORDER = 0x0004;
234
+ private const uint SWP_NOACTIVATE = 0x0010;
235
+ private const uint SWP_SHOWWINDOW = 0x0040;
236
+ private const uint SWP_NOOWNERZORDER = 0x0200;
237
+ private const uint SWP_ASYNCWINDOWPOS = 0x4000;
238
+ private const uint SHOW_NO_ACTIVATE_FLAGS = SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER | SWP_ASYNCWINDOWPOS;
239
+ private const uint COMPOSITOR_PULSE_FLAGS = SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER | SWP_ASYNCWINDOWPOS;
240
+
241
+ private static bool IsArrangedSafely(IntPtr hWnd) {
242
+ try { return IsWindowArranged(hWnd); }
243
+ // Older Windows versions do not export IsWindowArranged. Treat unknown as
244
+ // arranged so the geometry pulse fails closed and cannot disturb placement.
245
+ catch (EntryPointNotFoundException) { return true; }
246
+ }
247
+
248
+ public static int Apply(int expectedProcessId, bool show) {
249
+ int changed = 0;
250
+ EnumWindows((hWnd, lParam) => {
251
+ uint processId;
252
+ GetWindowThreadProcessId(hWnd, out processId);
253
+ if (processId != (uint)expectedProcessId || GetWindowTextLengthW(hWnd) <= 0) return true;
254
+ StringBuilder className = new StringBuilder(256);
255
+ GetClassNameW(hWnd, className, className.Capacity);
256
+ if (!String.Equals(className.ToString(), "Chrome_WidgetWin_1", StringComparison.Ordinal)) return true;
257
+ bool visible = IsWindowVisible(hWnd);
258
+ if (show) {
259
+ // SWP_SHOWWINDOW + SWP_NOACTIVATE preserves minimized/maximized/arranged
260
+ // placement without taking keyboard focus from the requesting app.
261
+ bool restored = SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, SHOW_NO_ACTIVATE_FLAGS);
262
+ bool pulsed = false;
263
+ RECT rect;
264
+ // RedrawWindow alone does not invalidate Chromium's DirectComposition
265
+ // surface after a long SW_HIDE. A real one-pixel resize does, while the
266
+ // NOACTIVATE/NOZORDER flags preserve the caller's foreground window.
267
+ // Do not resize minimized, maximized, or snapped windows: their placement
268
+ // is user-owned and a native redraw remains the safe fallback.
269
+ if (!IsIconic(hWnd) && !IsZoomed(hWnd) && !IsArrangedSafely(hWnd)
270
+ && GetWindowRect(hWnd, out rect)) {
271
+ int width = rect.Right - rect.Left;
272
+ int height = rect.Bottom - rect.Top;
273
+ if (width > 1 && height > 1) {
274
+ bool expanded = SetWindowPos(hWnd, IntPtr.Zero, 0, 0, width + 1, height, COMPOSITOR_PULSE_FLAGS);
275
+ if (expanded) System.Threading.Thread.Sleep(80);
276
+ bool restoredSize = SetWindowPos(hWnd, IntPtr.Zero, 0, 0, width, height, COMPOSITOR_PULSE_FLAGS);
277
+ pulsed = expanded || restoredSize;
278
+ }
279
+ }
280
+ bool redrawn = RedrawWindow(hWnd, IntPtr.Zero, IntPtr.Zero, 0x00000585);
281
+ if (restored || pulsed || redrawn) changed++;
282
+ }
283
+ if (!show && visible) { if (ShowWindowAsync(hWnd, 0)) changed++; }
284
+ return true;
285
+ }, IntPtr.Zero);
286
+ return changed;
287
+ }
288
+ }
289
+ `;
290
+ }
291
+ });
292
+
293
+ // cursor-ensure-core.mjs
294
+ var cursor_ensure_core_exports = {};
295
+ __export(cursor_ensure_core_exports, {
296
+ CDP_HOST: () => CDP_HOST,
297
+ CDP_ORIGIN: () => CDP_ORIGIN,
298
+ CDP_PORT: () => CDP_PORT,
299
+ cdpIsCursor: () => cdpIsCursor,
300
+ cdpUp: () => cdpUp,
301
+ cursorRunning: () => cursorRunning,
302
+ ensureCursorRunningLocal: () => ensureCursorRunningLocal,
303
+ findCursorExe: () => findCursorExe,
304
+ findCursorExeDetails: () => findCursorExeDetails,
305
+ isAgentsWindowTitle: () => isAgentsWindowTitle,
306
+ looksLikePluginRuntimePath: () => looksLikePluginRuntimePath,
307
+ normalizeCodexThreadCwd: () => normalizeCodexThreadCwd,
308
+ normalizeCursorExeCandidate: () => normalizeCursorExeCandidate,
309
+ resolveCodexThreadProjectPath: () => resolveCodexThreadProjectPath,
310
+ resolveCursorLaunchCdpPort: () => resolveCursorLaunchCdpPort,
311
+ resolveProjectPath: () => resolveProjectPath,
312
+ selectAgentsWindowTarget: () => selectAgentsWindowTarget,
313
+ selectNewCdpTarget: () => selectNewCdpTarget,
314
+ targetCanServeProject: () => targetCanServeProject,
315
+ targetTitleMatchesProject: () => targetTitleMatchesProject,
316
+ waitForCdp: () => waitForCdp
317
+ });
318
+ import { spawn as spawn2, execFileSync as execFileSync2 } from "child_process";
319
+ import { existsSync } from "fs";
320
+ import { createRequire as createNodeRequire } from "node:module";
321
+ import { homedir as homedir3 } from "node:os";
322
+ import { basename as basename2, extname, join as join3, resolve as resolve2, win32 as winPath, posix as posixPath } from "node:path";
323
+ import http from "http";
324
+ function resolveCursorLaunchCdpPort(port = process.env.CURSOR_BRIDGE_CDP_PORT) {
325
+ const parsed = Number(port == null || String(port).trim() === "" ? 9223 : port);
326
+ if (!Number.isInteger(parsed) || parsed < 1024 || parsed > 65535) return 9223;
327
+ return parsed;
328
+ }
329
+ function looksLikePluginRuntimePath(candidate) {
330
+ const p = String(candidate || "").replace(/\//g, "\\").toLowerCase();
331
+ return p.includes("\\.codex\\.tmp\\marketplaces\\") || p.includes("\\.codex\\plugins\\cache\\") || p.includes("\\.claude\\plugins\\cache\\") || p.includes("\\appdata\\local\\npm-cache\\_npx\\");
332
+ }
333
+ function normalizeCodexThreadCwd(value) {
334
+ const raw = String(value || "").trim();
335
+ if (/^\\\\\?\\UNC\\/i.test(raw)) return `\\\\${raw.slice(8)}`;
336
+ if (/^\\\\\?\\[a-zA-Z]:\\/.test(raw)) return raw.slice(4);
337
+ return raw;
338
+ }
339
+ function resolveCodexThreadProjectPath(options = {}) {
340
+ const threadId = String(options.threadId ?? process.env.CODEX_THREAD_ID ?? "").trim();
341
+ if (!threadId) return null;
342
+ if (CODEX_THREAD_PROJECTS.has(threadId) && options.useCache !== false) {
343
+ return CODEX_THREAD_PROJECTS.get(threadId);
344
+ }
345
+ let database = null;
346
+ try {
347
+ const lookupThreadCwd = options.lookupThreadCwd || ((id) => {
348
+ const { DatabaseSync } = (options.requireImpl || loadModule)("node:sqlite");
349
+ const databasePath = options.databasePath || join3(homedir3(), ".codex", "state_5.sqlite");
350
+ database = new DatabaseSync(databasePath, { readOnly: true });
351
+ return database.prepare("SELECT cwd FROM threads WHERE id = ?").get(id)?.cwd || null;
352
+ });
353
+ const candidate = normalizeCodexThreadCwd(lookupThreadCwd(threadId));
354
+ const existsImpl = options.existsImpl || existsSync;
355
+ const resolved = candidate && !looksLikePluginRuntimePath(candidate) && existsImpl(candidate) ? resolve2(candidate) : null;
356
+ if (options.useCache !== false) CODEX_THREAD_PROJECTS.set(threadId, resolved);
357
+ return resolved;
358
+ } catch {
359
+ if (options.useCache !== false) CODEX_THREAD_PROJECTS.set(threadId, null);
360
+ return null;
361
+ } finally {
362
+ try {
363
+ database?.close();
364
+ } catch {
365
+ }
366
+ }
367
+ }
368
+ function resolveProjectPath(value = process.env.CURSOR_PROJECT_PATH, options = {}) {
369
+ const explicit = String(value || "").trim();
370
+ if (explicit) return resolve2(explicit);
371
+ const persisted = String(options.persistedProjectPath || "").trim();
372
+ if (persisted) return resolve2(normalizeCodexThreadCwd(persisted));
373
+ const threadProjectPath = options.threadProjectPath === void 0 ? resolveCodexThreadProjectPath(options) : options.threadProjectPath;
374
+ if (threadProjectPath) return resolve2(normalizeCodexThreadCwd(threadProjectPath));
375
+ const cwd = options.cwd ?? process.cwd();
376
+ if (!cwd || looksLikePluginRuntimePath(cwd)) return null;
377
+ return resolve2(cwd);
378
+ }
379
+ function cursorFromRegistry(options = {}) {
380
+ const execFileSyncImpl = options.execFileSyncImpl || execFileSync2;
381
+ const legacyExecSyncImpl = options.execSyncImpl;
382
+ const existsImpl = options.existsImpl || existsSync;
383
+ const queries = [
384
+ ["HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Cursor.exe", "/ve"],
385
+ ["HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Cursor.exe", "/ve"],
386
+ ["HKCU\\Software\\Classes\\cursor\\shell\\open\\command", "/ve"],
387
+ ["HKLM\\Software\\Classes\\cursor\\shell\\open\\command", "/ve"],
388
+ ["HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Cursor (User)", "/v", "DisplayIcon"],
389
+ ["HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Cursor", "/v", "DisplayIcon"]
390
+ ];
391
+ for (const [key, ...valueArgs] of queries) {
392
+ try {
393
+ const runOptions = {
394
+ encoding: "utf8",
395
+ windowsHide: true,
396
+ stdio: ["ignore", "pipe", "ignore"]
397
+ };
398
+ const out = legacyExecSyncImpl && !options.execFileSyncImpl ? legacyExecSyncImpl(`reg query "${key}" ${valueArgs.join(" ")}`, runOptions) : execFileSyncImpl("reg.exe", ["query", key, ...valueArgs], runOptions);
399
+ const m = out.match(/([A-Za-z]:\\[^"\r\n]*?Cursor\.exe)/i);
400
+ if (m && existsImpl(m[1])) return m[1];
401
+ } catch {
402
+ }
403
+ }
404
+ return null;
405
+ }
406
+ function normalizeCursorExeCandidate(value, options = {}) {
407
+ const platform = options.platform || process.platform;
408
+ const existsImpl = options.existsImpl || existsSync;
409
+ const raw = String(value || "").trim().replace(/^(["'])(.*)\1$/, "$2").trim();
410
+ if (!raw) return null;
411
+ let candidates;
412
+ if (platform === "win32") {
413
+ const normalized = raw.replace(/\//g, "\\");
414
+ candidates = /\.exe$/i.test(normalized) ? [normalized] : [winPath.join(normalized, "Cursor.exe")];
415
+ } else if (platform === "darwin") {
416
+ const normalized = raw.replace(/\\/g, "/").replace(/\/$/, "");
417
+ candidates = /\.app$/i.test(normalized) ? [posixPath.join(normalized, "Contents", "MacOS", "Cursor")] : /\/Contents\/MacOS$/i.test(normalized) ? [posixPath.join(normalized, "Cursor")] : [normalized];
418
+ } else {
419
+ candidates = [raw];
420
+ }
421
+ for (const candidate of candidates) {
422
+ try {
423
+ if (existsImpl(candidate)) return candidate;
424
+ } catch {
425
+ }
426
+ }
427
+ return null;
428
+ }
429
+ function findCursorExeDetails(options = {}) {
430
+ const platform = options.platform || process.platform;
431
+ const env = options.env || process.env;
432
+ const existsImpl = options.existsImpl || existsSync;
433
+ const override = normalizeCursorExeCandidate(env.CURSOR_EXE, { platform, existsImpl });
434
+ if (override) return { path: override, source: "CURSOR_EXE", platform };
435
+ if (platform === "win32") {
436
+ const fromReg = cursorFromRegistry({
437
+ execFileSyncImpl: options.execFileSyncImpl,
438
+ execSyncImpl: options.execSyncImpl,
439
+ existsImpl
440
+ });
441
+ if (fromReg) return { path: fromReg, source: "windows_registry", platform };
442
+ const localAppData = env.LOCALAPPDATA || join3(homedir3(), "AppData", "Local");
443
+ const programFiles = env.ProgramFiles || env.PROGRAMFILES || "C:\\Program Files";
444
+ const programFilesX86 = env["ProgramFiles(x86)"] || env.PROGRAMFILES_X86 || "";
445
+ const candidates = [
446
+ localAppData && winPath.join(localAppData, "Programs", "Cursor", "Cursor.exe"),
447
+ programFiles && winPath.join(programFiles, "Cursor", "Cursor.exe"),
448
+ programFilesX86 && winPath.join(programFilesX86, "Cursor", "Cursor.exe")
449
+ ].filter(Boolean);
450
+ for (const candidate of candidates) {
451
+ try {
452
+ if (existsImpl(candidate)) return { path: candidate, source: "windows_standard_location", platform };
453
+ } catch {
454
+ }
455
+ }
456
+ return null;
457
+ }
458
+ if (platform === "darwin") {
459
+ const userHome = env.HOME || homedir3();
460
+ const candidates = [
461
+ "/Applications/Cursor.app/Contents/MacOS/Cursor",
462
+ userHome && posixPath.join(userHome, "Applications", "Cursor.app", "Contents", "MacOS", "Cursor")
463
+ ].filter(Boolean);
464
+ for (const candidate of candidates) {
465
+ try {
466
+ if (existsImpl(candidate)) return { path: candidate, source: "macos_standard_location", platform };
467
+ } catch {
468
+ }
469
+ }
470
+ return null;
471
+ }
472
+ return null;
473
+ }
474
+ function findCursorExe(options = {}) {
475
+ return findCursorExeDetails(options)?.path || null;
476
+ }
477
+ function cdpUp(timeoutMs = 1500) {
478
+ return new Promise((resolve3) => {
479
+ const req = http.get({ host: CDP_HOST, port: CDP_PORT, path: "/json/version" }, (res) => {
480
+ res.resume();
481
+ resolve3(res.statusCode === 200);
482
+ });
483
+ req.on("error", () => resolve3(false));
484
+ req.setTimeout(timeoutMs, () => {
485
+ try {
486
+ req.destroy();
487
+ } catch {
488
+ }
489
+ resolve3(false);
490
+ });
491
+ });
492
+ }
493
+ function cdpIsCursor(timeoutMs = 1500) {
494
+ return new Promise((resolve3) => {
495
+ const req = http.get({ host: CDP_HOST, port: CDP_PORT, path: "/json/list" }, (res) => {
496
+ let d = "";
497
+ res.on("data", (c) => d += c);
498
+ res.on("end", () => {
499
+ try {
500
+ if (/[\/\\](windsurf)[\/\\]/i.test(d)) return resolve3(false);
501
+ resolve3(/[\/\\]cursor[\/\\](resources|app)|cursor\.exe|vscode-app[^"]*[\/\\]cursor[\/\\]/i.test(d));
502
+ } catch {
503
+ resolve3(false);
504
+ }
505
+ });
506
+ });
507
+ req.on("error", () => resolve3(false));
508
+ req.setTimeout(timeoutMs, () => {
509
+ try {
510
+ req.destroy();
511
+ } catch {
512
+ }
513
+ resolve3(false);
514
+ });
515
+ });
516
+ }
517
+ function cursorRunning(options = {}) {
518
+ const platform = options.platform || process.platform;
519
+ const run = options.execFileSyncImpl || execFileSync2;
520
+ try {
521
+ if (platform === "win32") {
522
+ return /Cursor\.exe/i.test(run("tasklist.exe", ["/fi", "imagename eq Cursor.exe", "/nh"], {
523
+ encoding: "utf8",
524
+ windowsHide: true,
525
+ stdio: ["ignore", "pipe", "ignore"]
526
+ }));
527
+ }
528
+ if (platform === "darwin") {
529
+ run("pgrep", ["-f", "Cursor.app/Contents/MacOS/Cursor"], { stdio: "ignore" });
530
+ return true;
531
+ }
532
+ return false;
533
+ } catch {
534
+ return false;
535
+ }
536
+ }
537
+ async function waitForCdp(maxMs = 3e4, stepMs = 1e3) {
538
+ const start = Date.now();
539
+ while (Date.now() - start < maxMs) {
540
+ if (await cdpUp()) return true;
541
+ await new Promise((r) => setTimeout(r, stepMs));
542
+ }
543
+ return false;
544
+ }
545
+ async function ensureCursorRunningLocal(options = {}) {
546
+ const waitMs = Number(options.waitMs || 3e4);
547
+ const runtimeMode = options.runtimeMode || "normal";
548
+ const effectiveRuntimeMode = normalizeCursorRuntimeMode(runtimeMode);
549
+ const cdpUpImpl = options.cdpUpImpl || cdpUp;
550
+ const cdpIsCursorImpl = options.cdpIsCursorImpl || cdpIsCursor;
551
+ const cursorRunningImpl = options.cursorRunningImpl || cursorRunning;
552
+ const findCursorExeDetailsImpl = options.findCursorExeDetailsImpl || findCursorExeDetails;
553
+ const projectPath = Object.hasOwn(options, "projectPath") ? options.projectPath ? resolve2(String(options.projectPath)) : null : resolveProjectPath();
554
+ const listCdpPageTargetsImpl = options.listCdpPageTargetsImpl || listCdpPageTargets;
555
+ const spawnImpl = options.spawnImpl || spawn2;
556
+ if (await cdpUpImpl()) {
557
+ const isCursor = await cdpIsCursorImpl();
558
+ if (isCursor) {
559
+ const cursorPid2 = findCursorPidByPort(CDP_PORT);
560
+ const windowGuard2 = effectiveRuntimeMode === "minimal" && cursorPid2 ? startMinimalWindowGuard(cursorPid2) : null;
561
+ const presentation2 = effectiveRuntimeMode === "minimal" ? setCursorWindowPresentation({ action: "hide", port: CDP_PORT, pid: cursorPid2 }) : null;
562
+ const currentTargets = await listCdpPageTargetsImpl();
563
+ const projectKey = normalizeProjectKey(projectPath);
564
+ let targetId2 = projectKey ? PROJECT_TARGETS.get(projectKey) || null : currentTargets[0] && currentTargets[0].id || null;
565
+ let workspaceAction = projectPath ? "reused-project-target" : "reused-last-workspace";
566
+ const cachedTarget = targetId2 ? currentTargets.find((target2) => target2.id === targetId2) : null;
567
+ if (targetId2 && (!cachedTarget || projectPath && !targetCanServeProject(cachedTarget.title, projectPath))) {
568
+ PROJECT_TARGETS.delete(projectKey);
569
+ targetId2 = null;
570
+ } else if (targetId2 && cachedTarget && isAgentsWindowTitle(cachedTarget.title)) {
571
+ workspaceAction = "reused-agents-window";
572
+ }
573
+ if (projectPath && !targetId2) {
574
+ const existingTarget = currentTargets.find((target2) => targetTitleMatchesProject(target2.title, projectPath));
575
+ if (existingTarget) {
576
+ targetId2 = existingTarget.id;
577
+ PROJECT_TARGETS.set(projectKey, targetId2);
578
+ workspaceAction = "recovered-project-target";
579
+ }
580
+ }
581
+ if (projectPath && !targetId2) {
582
+ const agentsTarget = selectAgentsWindowTarget(currentTargets);
583
+ if (agentsTarget) {
584
+ targetId2 = agentsTarget.id;
585
+ PROJECT_TARGETS.set(projectKey, targetId2);
586
+ workspaceAction = "reused-agents-window";
587
+ }
588
+ }
589
+ if (projectPath && existsSync(projectPath) && !targetId2) {
590
+ const cursorExecutable2 = findCursorExeDetailsImpl();
591
+ const exe2 = cursorExecutable2 && cursorExecutable2.path;
592
+ if (!exe2) {
593
+ return {
594
+ ok: false,
595
+ status: "workspace-not-ready",
596
+ port: CDP_PORT,
597
+ cursorPid: cursorPid2,
598
+ runtimeMode: effectiveRuntimeMode,
599
+ projectPath,
600
+ presentation: presentation2,
601
+ windowGuard: windowGuard2,
602
+ needsAction: "install_or_locate_cursor",
603
+ retryable: true,
604
+ nextStep: "\u8BF7\u786E\u8BA4 Cursor \u5DF2\u5B89\u88C5\u5E76\u767B\u5F55\u3002\u82E5\u4F7F\u7528\u4FBF\u643A\u7248\u6216\u81EA\u5B9A\u4E49\u76EE\u5F55\uFF0C\u8BF7\u8BBE\u7F6E CURSOR_EXE \u540E\u91CD\u65B0\u6267\u884C\u540C\u4E00\u53E5\u521D\u59CB\u5316\u547D\u4EE4\u3002",
605
+ message: `CCE \u5DF2\u8FDE\u63A5 Cursor\uFF0C\u4F46\u8FD8\u4E0D\u80FD\u6253\u5F00\u5DE5\u4F5C\u533A ${projectPath}\uFF0C\u56E0\u4E3A\u6CA1\u6709\u627E\u5230 Cursor \u7A0B\u5E8F\u3002`
606
+ };
607
+ }
608
+ const beforeTargetIds = new Set(currentTargets.map((target2) => target2.id));
609
+ const opener = spawnImpl(exe2, ["--new-window", projectPath], {
610
+ detached: true,
611
+ stdio: "ignore",
612
+ windowsHide: effectiveRuntimeMode === "minimal"
613
+ });
614
+ opener.unref();
615
+ workspaceAction = "opened-new-window";
616
+ const openedTarget2 = await waitForNewCdpTarget(beforeTargetIds, 12e3, projectPath, listCdpPageTargetsImpl);
617
+ if (!openedTarget2) {
618
+ return {
619
+ ok: false,
620
+ status: "workspace-not-ready",
621
+ port: CDP_PORT,
622
+ cursorPid: cursorPid2,
623
+ runtimeMode: effectiveRuntimeMode,
624
+ projectPath,
625
+ presentation: presentation2,
626
+ windowGuard: windowGuard2,
627
+ workspaceAction,
628
+ cursorExecutable: exe2,
629
+ cursorExecutableSource: cursorExecutable2.source,
630
+ needsAction: "retry_initialization",
631
+ retryable: true,
632
+ nextStep: "\u8BF7\u7B49\u5F85 Cursor \u5B8C\u6210\u6253\u5F00\u9879\u76EE\uFF0C\u7136\u540E\u91CD\u65B0\u6267\u884C\u540C\u4E00\u53E5\u521D\u59CB\u5316\u547D\u4EE4\u3002",
633
+ message: `Cursor \u5DF2\u6253\u5F00\u9879\u76EE\uFF0C\u4F46 CCE \u8FD8\u6CA1\u6709\u786E\u8BA4\u5DE5\u4F5C\u533A ${projectPath} \u5DF2\u51C6\u5907\u597D\uFF1B\u4E3A\u907F\u514D\u641C\u7D22\u9519\u9879\u76EE\uFF0C\u672C\u6B21\u521D\u59CB\u5316\u5DF2\u5B89\u5168\u505C\u6B62\u3002`
634
+ };
635
+ }
636
+ targetId2 = openedTarget2.id;
637
+ PROJECT_TARGETS.set(projectKey, targetId2);
638
+ }
639
+ return {
640
+ ok: true,
641
+ status: "already",
642
+ port: CDP_PORT,
643
+ cursorPid: cursorPid2,
644
+ runtimeMode: effectiveRuntimeMode,
645
+ projectPath,
646
+ presentation: presentation2,
647
+ windowGuard: windowGuard2,
648
+ targetId: targetId2,
649
+ workspaceAction,
650
+ message: workspaceAction === "reused-agents-window" ? `CDP ${CDP_PORT} \u5DF2\u54CD\u5E94\u4E14\u662F Cursor\uFF1B\u5DF2\u590D\u7528 Agents Window\uFF08${targetId2}\uFF09\uFF0C\u4E0D\u4F1A\u518D\u6253\u5F00 IDE \u65B0\u7A97\u53E3\u3002` : `CDP ${CDP_PORT} \u5DF2\u54CD\u5E94\u4E14\u662F Cursor\uFF1B\u76EE\u6807\u5DE5\u4F5C\u533A\u5DF2\u7ED1\u5B9A\u5230 CDP target ${targetId2 || "default"}\u3002`
651
+ };
652
+ }
653
+ return {
654
+ ok: false,
655
+ status: "port-not-cursor",
656
+ port: CDP_PORT,
657
+ needsAction: "free_cce_port",
658
+ retryable: true,
659
+ nextStep: `\u672C\u673A\u7AEF\u53E3 ${CDP_PORT} \u6B63\u88AB\u5176\u4ED6\u7A0B\u5E8F\u4F7F\u7528\u3002\u5173\u95ED\u5360\u7528\u5B83\u7684\u7A0B\u5E8F\u540E\uFF0C\u91CD\u65B0\u6267\u884C\u540C\u4E00\u53E5\u521D\u59CB\u5316\u547D\u4EE4\u3002`,
660
+ message: `CCE \u73B0\u5728\u65E0\u6CD5\u8FDE\u63A5 Cursor\uFF0C\u56E0\u4E3A\u6240\u9700\u7684\u672C\u673A\u7AEF\u53E3 ${CDP_PORT} \u6B63\u88AB\u5176\u4ED6\u7A0B\u5E8F\u5360\u7528\u3002`
661
+ };
662
+ }
663
+ if (cursorRunningImpl()) {
664
+ const cursorExecutable2 = findCursorExeDetailsImpl();
665
+ return {
666
+ ok: false,
667
+ status: "running-no-debug",
668
+ port: CDP_PORT,
669
+ cursorExecutable: cursorExecutable2 && cursorExecutable2.path || null,
670
+ cursorExecutableSource: cursorExecutable2 && cursorExecutable2.source || null,
671
+ needsAction: "close_cursor_and_retry",
672
+ retryable: true,
673
+ nextStep: projectPath ? `\u4FDD\u5B58\u624B\u5934\u5185\u5BB9\u5E76\u6B63\u5E38\u9000\u51FA Cursor \u4E00\u6B21\uFF0C\u7136\u540E\u518D\u6B21\u8BF4\u201C\u521D\u59CB\u5316 CCE \u5DE5\u4F5C\u533A\u4E3A ${projectPath}\u201D\u3002` : "\u4FDD\u5B58\u624B\u5934\u5185\u5BB9\u5E76\u6B63\u5E38\u9000\u51FA Cursor \u4E00\u6B21\uFF0C\u7136\u540E\u91CD\u8BD5\u521A\u624D\u7684 CCE \u64CD\u4F5C\u3002",
674
+ message: "Cursor \u5DF2\u7ECF\u63D0\u524D\u6253\u5F00\uFF0CCCE \u65E0\u6CD5\u5728\u8FD0\u884C\u4E2D\u4E3A\u5B83\u8865\u4E0A\u8FDE\u63A5\u80FD\u529B\u3002\u4E3A\u4FDD\u62A4\u672A\u4FDD\u5B58\u5185\u5BB9\uFF0CCursor Bridge \u4E0D\u4F1A\u5F3A\u5236\u5173\u95ED\u5B83\u3002"
675
+ };
676
+ }
677
+ const cursorExecutable = findCursorExeDetailsImpl();
678
+ const exe = cursorExecutable && cursorExecutable.path;
679
+ if (!exe) {
680
+ return {
681
+ ok: false,
682
+ status: "no-exe",
683
+ port: CDP_PORT,
684
+ needsAction: "install_or_locate_cursor",
685
+ retryable: true,
686
+ nextStep: "\u8BF7\u5148\u5B89\u88C5\u5E76\u767B\u5F55 Cursor\u3002\u82E5\u4F7F\u7528\u4FBF\u643A\u7248\u6216\u81EA\u5B9A\u4E49\u76EE\u5F55\uFF0C\u8BF7\u8BBE\u7F6E CURSOR_EXE \u540E\u91CD\u65B0\u6267\u884C\u540C\u4E00\u53E5\u521D\u59CB\u5316\u547D\u4EE4\u3002",
687
+ message: "\u6CA1\u6709\u627E\u5230 Cursor\u3002\u6807\u51C6 Windows \u4E0E macOS \u5B89\u88C5\u4F1A\u81EA\u52A8\u8BC6\u522B\uFF0C\u901A\u5E38\u4E0D\u9700\u8981\u586B\u5199\u7A0B\u5E8F\u8DEF\u5F84\u3002"
688
+ };
689
+ }
690
+ const launchPort = resolveCursorLaunchCdpPort(CDP_PORT);
691
+ const args = [`--remote-debugging-port=${launchPort}`, `--remote-allow-origins=http://localhost:${launchPort}`];
692
+ if (effectiveRuntimeMode === "minimal") {
693
+ args.push(
694
+ "--disable-background-timer-throttling",
695
+ "--disable-renderer-backgrounding",
696
+ "--disable-backgrounding-occluded-windows"
697
+ );
698
+ }
699
+ if (projectPath && existsSync(projectPath)) args.push(projectPath);
700
+ const child = spawnImpl(exe, args, {
701
+ detached: true,
702
+ stdio: "ignore",
703
+ windowsHide: effectiveRuntimeMode === "minimal"
704
+ });
705
+ child.unref();
706
+ const startupWindowGuard = effectiveRuntimeMode === "minimal" ? startMinimalWindowGuard(child.pid) : null;
707
+ const up = await waitForCdp(waitMs);
708
+ if (!up) {
709
+ return {
710
+ ok: false,
711
+ status: "timeout",
712
+ exe,
713
+ port: CDP_PORT,
714
+ cursorPid: child.pid || null,
715
+ runtimeMode: effectiveRuntimeMode,
716
+ projectPath,
717
+ windowGuard: startupWindowGuard,
718
+ cursorExecutable: exe,
719
+ cursorExecutableSource: cursorExecutable.source,
720
+ needsAction: "retry_initialization",
721
+ retryable: true,
722
+ nextStep: "\u8BF7\u7A0D\u7B49\u7247\u523B\uFF0C\u7136\u540E\u91CD\u65B0\u6267\u884C\u540C\u4E00\u53E5\u521D\u59CB\u5316\u547D\u4EE4\u3002",
723
+ message: "Cursor \u5DF2\u7ECF\u542F\u52A8\uFF0C\u4F46 CCE \u8FD8\u6CA1\u51C6\u5907\u597D\uFF1B\u65E0\u9700\u4FEE\u6539\u4EFB\u4F55\u7AEF\u53E3\u8BBE\u7F6E\u3002"
724
+ };
725
+ }
726
+ const cursorPid = findCursorPidByPort(CDP_PORT) || child.pid || null;
727
+ const openedTarget = await waitForNewCdpTarget(/* @__PURE__ */ new Set(), 12e3, projectPath, listCdpPageTargetsImpl);
728
+ const targetId = openedTarget && openedTarget.id || null;
729
+ if (projectPath && !targetId) {
730
+ return {
731
+ ok: false,
732
+ status: "workspace-not-ready",
733
+ exe,
734
+ port: CDP_PORT,
735
+ cursorPid,
736
+ runtimeMode: effectiveRuntimeMode,
737
+ projectPath,
738
+ windowGuard: startupWindowGuard,
739
+ cursorExecutable: exe,
740
+ cursorExecutableSource: cursorExecutable.source,
741
+ needsAction: "retry_initialization",
742
+ retryable: true,
743
+ nextStep: "\u8BF7\u7B49\u5F85 Cursor \u5B8C\u6210\u6253\u5F00\u9879\u76EE\uFF0C\u7136\u540E\u91CD\u65B0\u6267\u884C\u540C\u4E00\u53E5\u521D\u59CB\u5316\u547D\u4EE4\u3002",
744
+ message: `Cursor \u5DF2\u542F\u52A8\uFF0C\u4F46 CCE \u8FD8\u6CA1\u6709\u786E\u8BA4\u5DE5\u4F5C\u533A ${projectPath} \u5DF2\u51C6\u5907\u597D\uFF1B\u4E3A\u907F\u514D\u641C\u7D22\u9519\u9879\u76EE\uFF0C\u672C\u6B21\u521D\u59CB\u5316\u5DF2\u5B89\u5168\u505C\u6B62\u3002`
745
+ };
746
+ }
747
+ if (projectPath && targetId) PROJECT_TARGETS.set(normalizeProjectKey(projectPath), targetId);
748
+ const windowGuard = effectiveRuntimeMode === "minimal" && cursorPid ? startMinimalWindowGuard(cursorPid) : null;
749
+ const target = projectPath ? `\u6253\u5F00 ${projectPath}` : "\u6062\u590D\u4E0A\u6B21\u5DE5\u4F5C\u533A";
750
+ const presentation = effectiveRuntimeMode === "minimal" ? setCursorWindowPresentation({ action: "hide", port: CDP_PORT, pid: cursorPid }) : null;
751
+ return {
752
+ ok: true,
753
+ status: "launched",
754
+ exe,
755
+ port: CDP_PORT,
756
+ cursorPid,
757
+ runtimeMode: effectiveRuntimeMode,
758
+ projectPath,
759
+ presentation,
760
+ windowGuard,
761
+ startupWindowGuard,
762
+ cursorExecutable: exe,
763
+ cursorExecutableSource: cursorExecutable.source,
764
+ targetId,
765
+ workspaceAction: projectPath ? "launched-project" : "launched-last-workspace",
766
+ message: `\u5DF2\u542F\u52A8 Cursor\uFF08${exe}\uFF0C${target}\uFF09\uFF0CCDP ${CDP_PORT} \u5C31\u7EEA\u3002`
767
+ };
768
+ }
769
+ function normalizeProjectKey(projectPath) {
770
+ return projectPath ? resolve2(String(projectPath)).replace(/\\/g, "/").toLowerCase() : "";
771
+ }
772
+ function targetTitleMatchesProject(title, projectPath) {
773
+ const name = basename2(String(projectPath || "")).trim().toLowerCase();
774
+ if (!name) return false;
775
+ const extension = extname(name);
776
+ const candidates = [...new Set([name, extension ? name.slice(0, -extension.length) : name].filter(Boolean))];
777
+ const normalizedTitle = String(title || "").trim().toLowerCase();
778
+ return candidates.some((candidate) => normalizedTitle === candidate || normalizedTitle.startsWith(candidate + " - ") || normalizedTitle.includes(" - " + candidate + " - "));
779
+ }
780
+ function isAgentsWindowTitle(title) {
781
+ const normalized = String(title || "").trim().toLowerCase();
782
+ return normalized === "cursor agents" || normalized.startsWith("cursor agents - ");
783
+ }
784
+ function targetCanServeProject(title, projectPath) {
785
+ if (!projectPath) return true;
786
+ return targetTitleMatchesProject(title, projectPath) || isAgentsWindowTitle(title);
787
+ }
788
+ function selectAgentsWindowTarget(targets) {
789
+ return (Array.isArray(targets) ? targets : []).find((target) => target && target.id && isAgentsWindowTitle(target.title)) || null;
790
+ }
791
+ async function listCdpPageTargets(timeoutMs = 1500) {
792
+ return new Promise((done) => {
793
+ const req = http.get({ host: CDP_HOST, port: CDP_PORT, path: "/json/list" }, (res) => {
794
+ let data = "";
795
+ res.on("data", (chunk) => {
796
+ data += chunk;
797
+ });
798
+ res.on("end", () => {
799
+ try {
800
+ const targets = JSON.parse(data);
801
+ done(Array.isArray(targets) ? targets.filter((target) => target && target.type === "page" && target.id) : []);
802
+ } catch {
803
+ done([]);
804
+ }
805
+ });
806
+ });
807
+ req.on("error", () => done([]));
808
+ req.setTimeout(timeoutMs, () => {
809
+ try {
810
+ req.destroy();
811
+ } catch {
812
+ }
813
+ done([]);
814
+ });
815
+ });
816
+ }
817
+ function selectNewCdpTarget(beforeTargetIds, targets, projectPath = "") {
818
+ const before = beforeTargetIds instanceof Set ? beforeTargetIds : new Set(beforeTargetIds || []);
819
+ const fresh = (targets || []).filter((target) => target && target.id && !before.has(target.id));
820
+ return fresh.find((target) => targetTitleMatchesProject(target.title, projectPath)) || fresh[0] || null;
821
+ }
822
+ async function waitForNewCdpTarget(beforeTargetIds, maxMs = 12e3, projectPath = "", listImpl = listCdpPageTargets) {
823
+ const started = Date.now();
824
+ while (Date.now() - started < maxMs) {
825
+ const target = selectNewCdpTarget(beforeTargetIds, await listImpl(), projectPath);
826
+ if (target) return target;
827
+ await new Promise((resolveWait) => setTimeout(resolveWait, 300));
828
+ }
829
+ return null;
830
+ }
831
+ var CDP_PORT, CDP_ORIGIN, CDP_HOST, PROJECT_TARGETS, CODEX_THREAD_PROJECTS, loadModule;
832
+ var init_cursor_ensure_core = __esm({
833
+ "cursor-ensure-core.mjs"() {
834
+ init_cursor_runtime();
835
+ CDP_PORT = Number(process.env.CURSOR_BRIDGE_CDP_PORT || 9223);
836
+ CDP_ORIGIN = `http://localhost:${CDP_PORT}`;
837
+ CDP_HOST = "127.0.0.1";
838
+ PROJECT_TARGETS = /* @__PURE__ */ new Map();
839
+ CODEX_THREAD_PROJECTS = /* @__PURE__ */ new Map();
840
+ loadModule = createNodeRequire(import.meta.url);
841
+ }
842
+ });
843
+
844
+ // cursor-lifecycle-supervisor.mjs
845
+ import net from "node:net";
846
+ import {
847
+ writeFileSync as writeFileSync2,
848
+ unlinkSync,
849
+ existsSync as existsSync2,
850
+ openSync,
851
+ closeSync,
852
+ readFileSync as readFileSync2,
853
+ appendFileSync,
854
+ renameSync as renameSync2,
855
+ statSync
856
+ } from "node:fs";
857
+ import { fileURLToPath, pathToFileURL } from "node:url";
858
+
859
+ // lifecycle-paths.mjs
860
+ import { createHash } from "node:crypto";
861
+ import { homedir } from "node:os";
862
+ import { join } from "node:path";
863
+ import { mkdirSync } from "node:fs";
864
+ function defaultLifecycleDir() {
865
+ if (process.env.CURSOR_BRIDGE_LIFECYCLE_DIR) return process.env.CURSOR_BRIDGE_LIFECYCLE_DIR;
866
+ if (process.platform === "win32") {
867
+ const root2 = process.env.LOCALAPPDATA || join(homedir(), "AppData", "Local");
868
+ return join(root2, "cursor-bridge", "lifecycle");
869
+ }
870
+ const root = process.env.XDG_RUNTIME_DIR || process.env.XDG_STATE_HOME || join(homedir(), ".local", "state");
871
+ return join(root, "cursor-bridge", "lifecycle");
872
+ }
873
+ function ensureLifecycleDir(dir = defaultLifecycleDir()) {
874
+ mkdirSync(dir, { recursive: true });
875
+ return dir;
876
+ }
877
+ function lifecycleEndpointTag(dir) {
878
+ return createHash("sha256").update(String(dir), "utf8").digest("hex").slice(0, 24);
879
+ }
880
+ function supervisorSockPath(dir = defaultLifecycleDir()) {
881
+ if (process.env.CURSOR_BRIDGE_SUPERVISOR_SOCK) return process.env.CURSOR_BRIDGE_SUPERVISOR_SOCK;
882
+ if (process.platform === "win32") {
883
+ return `\\\\.\\pipe\\cursor-bridge-lifecycle-${lifecycleEndpointTag(dir)}`;
884
+ }
885
+ return join(dir, "supervisor.sock");
886
+ }
887
+ function supervisorPidPath(dir = defaultLifecycleDir()) {
888
+ return join(dir, "supervisor.pid");
889
+ }
890
+ function supervisorLockPath(dir = defaultLifecycleDir()) {
891
+ return join(dir, "supervisor.lock");
892
+ }
893
+ function supervisorLogPath(dir = defaultLifecycleDir()) {
894
+ return join(dir, "supervisor.log");
895
+ }
896
+
897
+ // cursor-lifecycle-supervisor.mjs
898
+ var LOG_MAX_BYTES = 256 * 1024;
899
+ function parseArgs(argv = process.argv.slice(2)) {
900
+ const out = {};
901
+ for (let i = 0; i < argv.length; i += 1) {
902
+ const a = argv[i];
903
+ if (a === "--lifecycle-supervisor") continue;
904
+ if (a.startsWith("--lifecycle-dir=")) out.dir = a.slice("--lifecycle-dir=".length);
905
+ else if (a === "--lifecycle-dir") out.dir = argv[++i];
906
+ else if (a.startsWith("--sock=")) out.sock = a.slice("--sock=".length);
907
+ else if (a === "--sock") out.sock = argv[++i];
908
+ else if (a.startsWith("--boot-env=")) out.bootEnv = a.slice("--boot-env=".length);
909
+ else if (a === "--boot-env") out.bootEnv = argv[++i];
910
+ else if (a.startsWith("--ensure-module=")) out.ensureModule = a.slice("--ensure-module=".length);
911
+ else if (a === "--ensure-module") out.ensureModule = argv[++i];
912
+ else if (a.startsWith("--idle-ms=")) out.idleMs = Number(a.slice("--idle-ms=".length));
913
+ else if (a === "--idle-ms") out.idleMs = Number(argv[++i]);
914
+ else if (a.startsWith("--runtime-fingerprint=")) out.runtimeFingerprint = a.slice("--runtime-fingerprint=".length);
915
+ else if (a === "--runtime-fingerprint") out.runtimeFingerprint = argv[++i];
916
+ }
917
+ return out;
918
+ }
919
+ function tryRemove(path) {
920
+ try {
921
+ if (path && existsSync2(path)) unlinkSync(path);
922
+ } catch {
923
+ }
924
+ }
925
+ function applyBootEnv(bootEnvPath) {
926
+ if (!bootEnvPath || !existsSync2(bootEnvPath)) return { applied: false, deleted: false };
927
+ try {
928
+ const parsed = JSON.parse(readFileSync2(bootEnvPath, "utf8"));
929
+ if (parsed && typeof parsed === "object") {
930
+ for (const [key, value] of Object.entries(parsed)) {
931
+ if (value == null) continue;
932
+ if (process.env[key] == null || process.env[key] === "") {
933
+ process.env[key] = String(value);
934
+ }
935
+ }
936
+ }
937
+ tryRemove(bootEnvPath);
938
+ return { applied: true, deleted: !existsSync2(bootEnvPath) };
939
+ } catch (error) {
940
+ console.error("[cursor-lifecycle-supervisor] boot-env read failed:", error instanceof Error ? error.message : error);
941
+ return { applied: false, deleted: false, error: error instanceof Error ? error.message : String(error) };
942
+ }
943
+ }
944
+ function rotateLogIfNeeded(logPath) {
945
+ try {
946
+ if (!existsSync2(logPath)) return;
947
+ const size = statSync(logPath).size;
948
+ if (size < LOG_MAX_BYTES) return;
949
+ const rotated = `${logPath}.1`;
950
+ tryRemove(rotated);
951
+ renameSync2(logPath, rotated);
952
+ } catch {
953
+ }
954
+ }
955
+ function writeSupervisorDiag(logPath, event, fields = {}) {
956
+ if (!logPath || !event) return;
957
+ try {
958
+ rotateLogIfNeeded(logPath);
959
+ const safe = {
960
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
961
+ event: String(event),
962
+ supervisorPid: process.pid
963
+ };
964
+ if (fields.adapterPid != null) safe.adapterPid = fields.adapterPid;
965
+ if (fields.reason != null) safe.reason = String(fields.reason).slice(0, 200);
966
+ if (fields.status != null) safe.status = String(fields.status);
967
+ if (fields.ok != null) safe.ok = !!fields.ok;
968
+ if (fields.clients != null) safe.clients = Number(fields.clients);
969
+ if (fields.ensureCount != null) safe.ensureCount = Number(fields.ensureCount);
970
+ if (fields.sock != null) safe.sock = String(fields.sock).slice(0, 260);
971
+ if (fields.dir != null) safe.dir = String(fields.dir).slice(0, 260);
972
+ if (fields.error != null) safe.error = String(fields.error).slice(0, 400);
973
+ if (fields.code != null) safe.code = fields.code;
974
+ appendFileSync(logPath, `${JSON.stringify(safe)}
975
+ `, { encoding: "utf8" });
976
+ } catch {
977
+ }
978
+ }
979
+ function log(...args) {
980
+ console.error("[cursor-lifecycle-supervisor]", ...args);
981
+ }
982
+ async function loadEnsure(ensureModule) {
983
+ const override = ensureModule || process.env.CURSOR_BRIDGE_ENSURE_MODULE;
984
+ if (override) {
985
+ const mod = await import(pathToFileURL(override).href);
986
+ if (typeof mod.ensureCursorRunningLocal !== "function") {
987
+ throw new Error(`ensure module missing ensureCursorRunningLocal: ${override}`);
988
+ }
989
+ return mod.ensureCursorRunningLocal;
990
+ }
991
+ const { ensureCursorRunningLocal: ensureCursorRunningLocal2 } = await Promise.resolve().then(() => (init_cursor_ensure_core(), cursor_ensure_core_exports));
992
+ return ensureCursorRunningLocal2;
993
+ }
994
+ function writePid(pidPath) {
995
+ writeFileSync2(pidPath, `${process.pid}
996
+ `, { encoding: "utf8" });
997
+ }
998
+ function acquireOrExit(lockPath, diagLog) {
999
+ try {
1000
+ const fd = openSync(lockPath, "wx");
1001
+ closeSync(fd);
1002
+ writeFileSync2(lockPath, `${process.pid}
1003
+ `, { encoding: "utf8" });
1004
+ return true;
1005
+ } catch (error) {
1006
+ if (error && error.code === "EEXIST") {
1007
+ try {
1008
+ const existing = Number(String(readFileSync2(lockPath, "utf8")).trim());
1009
+ if (existing && existing !== process.pid) {
1010
+ try {
1011
+ process.kill(existing, 0);
1012
+ log(`another supervisor holds lock pid=${existing}; exiting`);
1013
+ writeSupervisorDiag(diagLog, "fatal", { reason: "lock-held", error: `pid=${existing}` });
1014
+ return false;
1015
+ } catch {
1016
+ tryRemove(lockPath);
1017
+ return acquireOrExit(lockPath, diagLog);
1018
+ }
1019
+ }
1020
+ } catch {
1021
+ }
1022
+ log("lock busy; exiting");
1023
+ writeSupervisorDiag(diagLog, "fatal", { reason: "lock-busy" });
1024
+ return false;
1025
+ }
1026
+ throw error;
1027
+ }
1028
+ }
1029
+ async function startSupervisor(options = {}) {
1030
+ const cli = parseArgs(options.argv || process.argv.slice(2));
1031
+ if (cli.bootEnv || options.bootEnv) applyBootEnv(cli.bootEnv || options.bootEnv);
1032
+ const dir = ensureLifecycleDir(options.dir || cli.dir || defaultLifecycleDir());
1033
+ const sock = options.sock || cli.sock || process.env.CURSOR_BRIDGE_SUPERVISOR_SOCK || supervisorSockPath(dir);
1034
+ const pidPath = options.pidPath || supervisorPidPath(dir);
1035
+ const lockPath = options.lockPath || supervisorLockPath(dir);
1036
+ const logPath = options.logPath || supervisorLogPath(dir);
1037
+ const idleMs = Number(
1038
+ options.idleMs ?? cli.idleMs ?? process.env.CURSOR_BRIDGE_SUPERVISOR_IDLE_MS ?? 5 * 60 * 1e3
1039
+ );
1040
+ const ensureModule = options.ensureModule || cli.ensureModule || process.env.CURSOR_BRIDGE_ENSURE_MODULE;
1041
+ const runtimeFingerprint = String(options.runtimeFingerprint || cli.runtimeFingerprint || "unknown");
1042
+ const runtimeScript = fileURLToPath(import.meta.url);
1043
+ writeSupervisorDiag(logPath, "start", { dir, sock, reason: "startSupervisor" });
1044
+ if (!acquireOrExit(lockPath, logPath)) {
1045
+ return { started: false, reason: "lock-held" };
1046
+ }
1047
+ if (process.platform !== "win32" && existsSync2(sock)) {
1048
+ tryRemove(sock);
1049
+ }
1050
+ const ensureLocal = await loadEnsure(ensureModule);
1051
+ let ensureInflight = null;
1052
+ let ensureCount = 0;
1053
+ let lastEnsure = null;
1054
+ const clients = /* @__PURE__ */ new Set();
1055
+ let idleTimer = null;
1056
+ let shuttingDown = false;
1057
+ const scheduleIdle = () => {
1058
+ if (idleTimer) clearTimeout(idleTimer);
1059
+ if (lastEnsure && lastEnsure.ok && lastEnsure.runtimeMode === "minimal") {
1060
+ writeSupervisorDiag(logPath, "idle-suppressed", {
1061
+ reason: "minimal-runtime-owns-window-guard",
1062
+ clients: clients.size,
1063
+ ensureCount
1064
+ });
1065
+ return;
1066
+ }
1067
+ if (!(idleMs > 0)) return;
1068
+ idleTimer = setTimeout(() => {
1069
+ if (clients.size > 0 || shuttingDown) return;
1070
+ log(`idle ${idleMs}ms with 0 clients; exiting without stopping Cursor`);
1071
+ writeSupervisorDiag(logPath, "idle", { reason: `idle-${idleMs}ms`, clients: 0, ensureCount });
1072
+ shutdown(0);
1073
+ }, idleMs);
1074
+ if (typeof idleTimer.unref === "function") idleTimer.unref();
1075
+ };
1076
+ const runEnsure = async (request = {}) => {
1077
+ if (ensureInflight) return ensureInflight;
1078
+ ensureInflight = (async () => {
1079
+ ensureCount += 1;
1080
+ const waitMs = Number(request.waitMs || 3e4);
1081
+ const result = await ensureLocal({
1082
+ waitMs,
1083
+ runtimeMode: request.runtimeMode || "normal",
1084
+ projectPath: Object.hasOwn(request, "projectPath") ? request.projectPath : null
1085
+ });
1086
+ lastEnsure = {
1087
+ ...result,
1088
+ ensureCount,
1089
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1090
+ requestReason: request.reason || null,
1091
+ requestAdapterPid: request.adapterPid || null,
1092
+ requestRuntimeMode: request.runtimeMode || "normal",
1093
+ requestProjectPath: request.projectPath || null
1094
+ };
1095
+ writeSupervisorDiag(logPath, "ensure-result", {
1096
+ ok: !!result.ok,
1097
+ status: result.status,
1098
+ reason: request.reason || null,
1099
+ adapterPid: request.adapterPid || null,
1100
+ ensureCount
1101
+ });
1102
+ return lastEnsure;
1103
+ })();
1104
+ try {
1105
+ return await ensureInflight;
1106
+ } finally {
1107
+ ensureInflight = null;
1108
+ }
1109
+ };
1110
+ const server = net.createServer((socket) => {
1111
+ clients.add(socket);
1112
+ if (idleTimer) {
1113
+ clearTimeout(idleTimer);
1114
+ idleTimer = null;
1115
+ }
1116
+ let buffer = "";
1117
+ socket.setEncoding("utf8");
1118
+ socket.on("data", (chunk) => {
1119
+ buffer += chunk;
1120
+ let idx;
1121
+ while ((idx = buffer.indexOf("\n")) >= 0) {
1122
+ const line = buffer.slice(0, idx).trim();
1123
+ buffer = buffer.slice(idx + 1);
1124
+ if (!line) continue;
1125
+ Promise.resolve().then(() => handleLine(socket, line, {
1126
+ runEnsure,
1127
+ ensureCount: () => ensureCount,
1128
+ lastEnsure: () => lastEnsure,
1129
+ clients,
1130
+ isEnsureInflight: () => Boolean(ensureInflight),
1131
+ shutdown,
1132
+ runtimeFingerprint,
1133
+ runtimeScript
1134
+ })).catch((error) => {
1135
+ try {
1136
+ socket.write(`${JSON.stringify({
1137
+ type: "error",
1138
+ ok: false,
1139
+ error: error instanceof Error ? error.message : String(error)
1140
+ })}
1141
+ `);
1142
+ } catch {
1143
+ }
1144
+ });
1145
+ }
1146
+ });
1147
+ socket.on("close", () => {
1148
+ clients.delete(socket);
1149
+ scheduleIdle();
1150
+ });
1151
+ socket.on("error", () => {
1152
+ clients.delete(socket);
1153
+ scheduleIdle();
1154
+ });
1155
+ });
1156
+ const shutdown = (code = 0) => {
1157
+ if (shuttingDown) return;
1158
+ shuttingDown = true;
1159
+ writeSupervisorDiag(logPath, "cleanup", { reason: "shutdown", code, clients: clients.size, ensureCount });
1160
+ try {
1161
+ server.close();
1162
+ } catch {
1163
+ }
1164
+ tryRemove(pidPath);
1165
+ tryRemove(lockPath);
1166
+ if (process.platform !== "win32") tryRemove(sock);
1167
+ process.exit(code);
1168
+ };
1169
+ process.on("SIGINT", () => shutdown(0));
1170
+ process.on("SIGTERM", () => shutdown(0));
1171
+ await new Promise((resolve3, reject) => {
1172
+ server.once("error", reject);
1173
+ server.listen(sock, () => {
1174
+ server.removeListener("error", reject);
1175
+ resolve3();
1176
+ });
1177
+ });
1178
+ writePid(pidPath);
1179
+ log(`listening sock=${sock} pid=${process.pid} dir=${dir}`);
1180
+ writeSupervisorDiag(logPath, "listen", { sock, dir, reason: "listening" });
1181
+ scheduleIdle();
1182
+ return { started: true, sock, pid: process.pid, dir, server, shutdown, logPath };
1183
+ }
1184
+ async function handleLine(socket, line, ctx) {
1185
+ let msg;
1186
+ try {
1187
+ msg = JSON.parse(line);
1188
+ } catch {
1189
+ socket.write(`${JSON.stringify({ type: "error", ok: false, error: "invalid-json" })}
1190
+ `);
1191
+ return;
1192
+ }
1193
+ const id = msg.id;
1194
+ try {
1195
+ if (msg.type === "ping") {
1196
+ socket.write(`${JSON.stringify({
1197
+ type: "pong",
1198
+ id,
1199
+ ok: true,
1200
+ supervisorPid: process.pid,
1201
+ clients: ctx.clients.size,
1202
+ ensureCount: ctx.ensureCount(),
1203
+ runtimeFingerprint: ctx.runtimeFingerprint,
1204
+ runtimeScript: ctx.runtimeScript
1205
+ })}
1206
+ `);
1207
+ return;
1208
+ }
1209
+ if (msg.type === "status") {
1210
+ socket.write(`${JSON.stringify({
1211
+ type: "status-result",
1212
+ id,
1213
+ ok: true,
1214
+ supervisorPid: process.pid,
1215
+ clients: ctx.clients.size,
1216
+ ensureCount: ctx.ensureCount(),
1217
+ lastEnsure: ctx.lastEnsure(),
1218
+ runtimeFingerprint: ctx.runtimeFingerprint,
1219
+ runtimeScript: ctx.runtimeScript
1220
+ })}
1221
+ `);
1222
+ return;
1223
+ }
1224
+ if (msg.type === "ensure") {
1225
+ const result = await ctx.runEnsure(msg);
1226
+ socket.write(`${JSON.stringify({
1227
+ type: "ensure-result",
1228
+ id,
1229
+ supervisorPid: process.pid,
1230
+ reusedSupervisor: true,
1231
+ launchReason: result.status === "launched" ? "supervisor-spawned-cursor" : result.status === "already" ? "supervisor-cursor-already" : `supervisor-${result.status}`,
1232
+ ...result,
1233
+ runtimeFingerprint: ctx.runtimeFingerprint,
1234
+ runtimeScript: ctx.runtimeScript
1235
+ })}
1236
+ `);
1237
+ return;
1238
+ }
1239
+ if (msg.type === "shutdown_if_idle") {
1240
+ if (msg.confirmation !== "ROLL_CURSOR_LIFECYCLE_SUPERVISOR") {
1241
+ socket.write(`${JSON.stringify({ type: "error", id, ok: false, error: "invalid-shutdown-confirmation" })}
1242
+ `);
1243
+ return;
1244
+ }
1245
+ const busy = ctx.isEnsureInflight() || ctx.clients.size > 1;
1246
+ socket.write(`${JSON.stringify({
1247
+ type: "shutdown-result",
1248
+ id,
1249
+ ok: true,
1250
+ restarting: !busy,
1251
+ busy,
1252
+ runtimeFingerprint: ctx.runtimeFingerprint,
1253
+ targetRuntimeFingerprint: msg.targetRuntimeFingerprint || null
1254
+ })}
1255
+ `);
1256
+ if (!busy) setTimeout(() => ctx.shutdown(0), 25);
1257
+ return;
1258
+ }
1259
+ socket.write(`${JSON.stringify({ type: "error", id, ok: false, error: `unknown-type:${msg.type}` })}
1260
+ `);
1261
+ } catch (error) {
1262
+ socket.write(`${JSON.stringify({
1263
+ type: "error",
1264
+ id,
1265
+ ok: false,
1266
+ error: error instanceof Error ? error.message : String(error)
1267
+ })}
1268
+ `);
1269
+ }
1270
+ }
1271
+ var isMain = import.meta.url === pathToFileURL(process.argv[1] || "").href || process.argv.includes("--lifecycle-supervisor") || process.env.CURSOR_BRIDGE_ROLE === "supervisor";
1272
+ if (isMain) {
1273
+ startSupervisor().catch((error) => {
1274
+ log("fatal", error);
1275
+ try {
1276
+ const dir = ensureLifecycleDir(process.env.CURSOR_BRIDGE_LIFECYCLE_DIR || defaultLifecycleDir());
1277
+ writeSupervisorDiag(supervisorLogPath(dir), "fatal", {
1278
+ reason: "start-failed",
1279
+ error: error instanceof Error ? error.message : String(error)
1280
+ });
1281
+ } catch {
1282
+ }
1283
+ process.exit(1);
1284
+ });
1285
+ }
1286
+ export {
1287
+ applyBootEnv,
1288
+ startSupervisor,
1289
+ writeSupervisorDiag
1290
+ };