pi-cursor-bridge 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -313,6 +313,7 @@ __export(cursor_ensure_core_exports, {
313
313
  resolveProjectPath: () => resolveProjectPath,
314
314
  selectAgentsWindowTarget: () => selectAgentsWindowTarget,
315
315
  selectNewCdpTarget: () => selectNewCdpTarget,
316
+ selectReusableProjectTarget: () => selectReusableProjectTarget,
316
317
  targetCanServeProject: () => targetCanServeProject,
317
318
  targetTitleMatchesProject: () => targetTitleMatchesProject,
318
319
  waitForCdp: () => waitForCdp
@@ -607,15 +608,18 @@ async function ensureCursorRunningLocal(options = {}) {
607
608
  const projectPath = Object.hasOwn(options, "projectPath") ? options.projectPath ? resolve2(String(options.projectPath)) : null : resolveProjectPath();
608
609
  const listCdpPageTargetsImpl = options.listCdpPageTargetsImpl || listCdpPageTargets;
609
610
  const spawnImpl = options.spawnImpl || spawn2;
611
+ const sleepImpl = options.sleepImpl || ((ms) => new Promise((resolveWait) => setTimeout(resolveWait, ms)));
612
+ const waitForCdpImpl = options.waitForCdpImpl || waitForCdp;
613
+ const findCursorPidByPortImpl = options.findCursorPidByPortImpl || findCursorPidByPort;
610
614
  const allowSpawn = options.allowSpawn !== false;
611
615
  const allowProcessControl = options.allowProcessControl !== false;
612
616
  if (await cdpUpImpl()) {
613
617
  const isCursor = await cdpIsCursorImpl();
614
618
  if (isCursor) {
615
- const cursorPid2 = allowProcessControl ? findCursorPidByPort(CDP_PORT) : null;
619
+ const cursorPid2 = allowProcessControl ? findCursorPidByPortImpl(CDP_PORT) : null;
616
620
  const windowGuard2 = allowProcessControl && effectiveRuntimeMode === "minimal" && cursorPid2 ? startMinimalWindowGuard(cursorPid2) : null;
617
621
  const presentation2 = effectiveRuntimeMode === "minimal" ? allowProcessControl ? setCursorWindowPresentation({ action: "hide", port: CDP_PORT, pid: cursorPid2 }) : attachedPresentation(effectiveRuntimeMode, CDP_PORT) : null;
618
- const currentTargets = await listCdpPageTargetsImpl();
622
+ let currentTargets = await listCdpPageTargetsImpl();
619
623
  const projectKey = normalizeProjectKey(projectPath);
620
624
  let targetId2 = projectKey ? PROJECT_TARGETS.get(projectKey) || null : currentTargets[0] && currentTargets[0].id || null;
621
625
  let workspaceAction = projectPath ? "reused-project-target" : "reused-last-workspace";
@@ -627,19 +631,11 @@ async function ensureCursorRunningLocal(options = {}) {
627
631
  workspaceAction = "reused-agents-window";
628
632
  }
629
633
  if (projectPath && !targetId2) {
630
- const existingTarget = currentTargets.find((target2) => targetTitleMatchesProject(target2.title, projectPath));
634
+ const existingTarget = selectReusableProjectTarget(currentTargets, projectPath);
631
635
  if (existingTarget) {
632
636
  targetId2 = existingTarget.id;
633
637
  PROJECT_TARGETS.set(projectKey, targetId2);
634
- workspaceAction = "recovered-project-target";
635
- }
636
- }
637
- if (projectPath && !targetId2) {
638
- const agentsTarget = selectAgentsWindowTarget(currentTargets);
639
- if (agentsTarget) {
640
- targetId2 = agentsTarget.id;
641
- PROJECT_TARGETS.set(projectKey, targetId2);
642
- workspaceAction = "reused-agents-window";
638
+ workspaceAction = isAgentsWindowTitle(existingTarget.title) ? "reused-agents-window" : "recovered-project-target";
643
639
  }
644
640
  }
645
641
  if (projectPath && existsSync(projectPath) && !targetId2) {
@@ -677,13 +673,39 @@ async function ensureCursorRunningLocal(options = {}) {
677
673
  message: `CCE connected to Cursor but cannot open workspace ${projectPath} because the Cursor executable was not found.`
678
674
  };
679
675
  }
680
- const beforeTargetIds = new Set(currentTargets.map((target2) => target2.id));
681
- const opened = await spawnDetachedSafely(spawnImpl, exe2, ["--new-window", projectPath], {
676
+ const settleAttempts = Math.max(1, Number(options.targetSettleAttempts ?? 8));
677
+ const settleDelayMs = Math.max(0, Number(options.targetSettleDelayMs ?? 250));
678
+ for (let attempt = 0; attempt < settleAttempts && !targetId2; attempt++) {
679
+ if (attempt > 0 && settleDelayMs > 0) await sleepImpl(settleDelayMs);
680
+ currentTargets = await listCdpPageTargetsImpl();
681
+ const reusable = selectReusableProjectTarget(currentTargets, projectPath);
682
+ if (reusable) {
683
+ targetId2 = reusable.id;
684
+ PROJECT_TARGETS.set(projectKey, targetId2);
685
+ workspaceAction = isAgentsWindowTitle(reusable.title) ? "reused-agents-window" : "recovered-project-target";
686
+ }
687
+ }
688
+ if (targetId2) {
689
+ return {
690
+ ok: true,
691
+ status: "already",
692
+ port: CDP_PORT,
693
+ cursorPid: cursorPid2,
694
+ runtimeMode: effectiveRuntimeMode,
695
+ projectPath,
696
+ presentation: presentation2,
697
+ windowGuard: windowGuard2,
698
+ targetId: targetId2,
699
+ workspaceAction,
700
+ message: workspaceAction === "reused-agents-window" ? `CDP ${CDP_PORT} responded as Cursor; Agents Window ${targetId2} was reused without opening another IDE window.` : `CDP ${CDP_PORT} responded as Cursor; the target workspace is bound to CDP target ${targetId2}.`
701
+ };
702
+ }
703
+ const reused = await spawnDetachedSafely(spawnImpl, exe2, ["--reuse-window", projectPath], {
682
704
  detached: true,
683
705
  stdio: "ignore",
684
706
  windowsHide: effectiveRuntimeMode === "minimal"
685
707
  });
686
- if (!opened.ok) {
708
+ if (!reused.ok) {
687
709
  return {
688
710
  ok: false,
689
711
  status: "spawn-blocked",
@@ -693,16 +715,16 @@ async function ensureCursorRunningLocal(options = {}) {
693
715
  projectPath,
694
716
  presentation: presentation2,
695
717
  windowGuard: windowGuard2,
696
- errorCode: opened.errorCode,
718
+ errorCode: reused.errorCode,
697
719
  needsAction: "open_workspace_in_cursor",
698
720
  retryable: true,
699
721
  nextStep: `Open workspace ${projectPath} in Cursor, then retry the same operation.`,
700
- message: `Cursor Bridge could not open a new workspace window: ${opened.error instanceof Error ? opened.error.message : String(opened.error)}`
722
+ message: `Cursor Bridge could not reuse the existing Cursor window for the workspace: ${reused.error instanceof Error ? reused.error.message : String(reused.error)}`
701
723
  };
702
724
  }
703
- workspaceAction = "opened-new-window";
704
- const openedTarget2 = await waitForNewCdpTarget(beforeTargetIds, 12e3, projectPath, listCdpPageTargetsImpl);
705
- if (!openedTarget2) {
725
+ workspaceAction = "reused-window-for-project";
726
+ const reusedTarget = await waitForProjectCdpTarget(12e3, projectPath, listCdpPageTargetsImpl, sleepImpl);
727
+ if (!reusedTarget) {
706
728
  return {
707
729
  ok: false,
708
730
  status: "workspace-not-ready",
@@ -721,7 +743,7 @@ async function ensureCursorRunningLocal(options = {}) {
721
743
  message: `Cursor opened the project, but CCE has not confirmed that workspace ${projectPath} is ready. Initialization stopped safely to avoid searching the wrong project.`
722
744
  };
723
745
  }
724
- targetId2 = openedTarget2.id;
746
+ targetId2 = reusedTarget.id;
725
747
  PROJECT_TARGETS.set(projectKey, targetId2);
726
748
  }
727
749
  return {
@@ -799,7 +821,6 @@ async function ensureCursorRunningLocal(options = {}) {
799
821
  "--disable-backgrounding-occluded-windows"
800
822
  );
801
823
  }
802
- if (projectPath && existsSync(projectPath)) args.push(projectPath);
803
824
  const launched = await spawnDetachedSafely(spawnImpl, exe, args, {
804
825
  detached: true,
805
826
  stdio: "ignore",
@@ -823,7 +844,7 @@ async function ensureCursorRunningLocal(options = {}) {
823
844
  }
824
845
  const child = launched.child;
825
846
  const startupWindowGuard = effectiveRuntimeMode === "minimal" ? startMinimalWindowGuard(child && child.pid) : null;
826
- const up = await waitForCdp(waitMs);
847
+ const up = await waitForCdpImpl(waitMs);
827
848
  if (!up) {
828
849
  return {
829
850
  ok: false,
@@ -842,7 +863,7 @@ async function ensureCursorRunningLocal(options = {}) {
842
863
  message: "Cursor started, but CCE is not ready yet. No port setting needs to be changed."
843
864
  };
844
865
  }
845
- const cursorPid = findCursorPidByPort(CDP_PORT) || child.pid || null;
866
+ const cursorPid = findCursorPidByPortImpl(CDP_PORT) || child.pid || null;
846
867
  const openedTarget = await waitForNewCdpTarget(/* @__PURE__ */ new Set(), 12e3, projectPath, listCdpPageTargetsImpl);
847
868
  const targetId = openedTarget && openedTarget.id || null;
848
869
  if (projectPath && !targetId) {
@@ -865,7 +886,8 @@ async function ensureCursorRunningLocal(options = {}) {
865
886
  }
866
887
  if (projectPath && targetId) PROJECT_TARGETS.set(normalizeProjectKey(projectPath), targetId);
867
888
  const windowGuard = effectiveRuntimeMode === "minimal" && cursorPid ? startMinimalWindowGuard(cursorPid) : null;
868
- const target = projectPath ? `opening ${projectPath}` : "restoring the previous workspace";
889
+ const launchedIntoAgents = !!(projectPath && openedTarget && isAgentsWindowTitle(openedTarget.title));
890
+ const target = launchedIntoAgents ? `restoring one Agents Window for ${projectPath}` : projectPath ? `opening ${projectPath}` : "restoring the previous workspace";
869
891
  const presentation = effectiveRuntimeMode === "minimal" ? setCursorWindowPresentation({ action: "hide", port: CDP_PORT, pid: cursorPid }) : null;
870
892
  return {
871
893
  ok: true,
@@ -881,7 +903,7 @@ async function ensureCursorRunningLocal(options = {}) {
881
903
  cursorExecutable: exe,
882
904
  cursorExecutableSource: cursorExecutable.source,
883
905
  targetId,
884
- workspaceAction: projectPath ? "launched-project" : "launched-last-workspace",
906
+ workspaceAction: launchedIntoAgents ? "launched-agents-window" : projectPath ? "launched-project" : "launched-last-workspace",
885
907
  message: `Cursor started (${exe}, ${target}); CDP ${CDP_PORT} is ready.`
886
908
  };
887
909
  }
@@ -907,6 +929,10 @@ function targetCanServeProject(title, projectPath) {
907
929
  function selectAgentsWindowTarget(targets) {
908
930
  return (Array.isArray(targets) ? targets : []).find((target) => target && target.id && isAgentsWindowTitle(target.title)) || null;
909
931
  }
932
+ function selectReusableProjectTarget(targets, projectPath) {
933
+ const pages = Array.isArray(targets) ? targets : [];
934
+ return pages.find((target) => target && target.id && targetTitleMatchesProject(target.title, projectPath)) || selectAgentsWindowTarget(pages) || null;
935
+ }
910
936
  async function listCdpPageTargets(timeoutMs = 1500) {
911
937
  return new Promise((done) => {
912
938
  const req = http.get({ host: CDP_HOST, port: CDP_PORT, path: "/json/list" }, (res) => {
@@ -936,7 +962,8 @@ async function listCdpPageTargets(timeoutMs = 1500) {
936
962
  function selectNewCdpTarget(beforeTargetIds, targets, projectPath = "") {
937
963
  const before = beforeTargetIds instanceof Set ? beforeTargetIds : new Set(beforeTargetIds || []);
938
964
  const fresh = (targets || []).filter((target) => target && target.id && !before.has(target.id));
939
- return fresh.find((target) => targetTitleMatchesProject(target.title, projectPath)) || fresh[0] || null;
965
+ if (projectPath) return selectReusableProjectTarget(fresh, projectPath);
966
+ return fresh[0] || null;
940
967
  }
941
968
  async function waitForNewCdpTarget(beforeTargetIds, maxMs = 12e3, projectPath = "", listImpl = listCdpPageTargets) {
942
969
  const started = Date.now();
@@ -947,6 +974,15 @@ async function waitForNewCdpTarget(beforeTargetIds, maxMs = 12e3, projectPath =
947
974
  }
948
975
  return null;
949
976
  }
977
+ async function waitForProjectCdpTarget(maxMs, projectPath, listImpl = listCdpPageTargets, sleepImpl = (ms) => new Promise((resolveWait) => setTimeout(resolveWait, ms))) {
978
+ const started = Date.now();
979
+ while (Date.now() - started < maxMs) {
980
+ const target = selectReusableProjectTarget(await listImpl(), projectPath);
981
+ if (target) return target;
982
+ await sleepImpl(300);
983
+ }
984
+ return null;
985
+ }
950
986
  var CDP_PORT, CDP_ORIGIN, CDP_HOST, PROJECT_TARGETS, CODEX_THREAD_PROJECTS, loadModule;
951
987
  var init_cursor_ensure_core = __esm({
952
988
  "cursor-ensure-core.mjs"() {
@@ -10,7 +10,7 @@ const hostWorkspaceId = hostCwd.replace(/\\/g, "/").toLowerCase();
10
10
  export default createStdioMcpExtension({
11
11
  label: "Cursor Bridge",
12
12
  clientName: "pi-cursor-bridge",
13
- packageVersion: "0.1.7",
13
+ packageVersion: "0.1.9",
14
14
  serverName: "cursor-bridge",
15
15
  serverScript: join(packageRoot, "dist", "cursor-bridge.mjs"),
16
16
  cwd: hostCwd,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cursor-bridge",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Use Cursor Context Engine and bounded Cursor Agent execution from the Pi coding agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -47,6 +47,6 @@
47
47
  },
48
48
  "piPackage": {
49
49
  "embeddedProduct": "Cursor Bridge",
50
- "embeddedProductVersion": "5.6.2"
50
+ "embeddedProductVersion": "5.7.1"
51
51
  }
52
52
  }