bitfab-cli 0.2.97 → 0.2.99

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.
Files changed (2) hide show
  1. package/dist/index.js +97 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9834,7 +9834,7 @@ async function createStudioSession({ serviceUrl, apiKey, sessionId, initialPath
9834
9834
  import net2 from "net";
9835
9835
 
9836
9836
  // ../bitfab-plugin-lib/dist/daemon/ensure.js
9837
- import { fork } from "child_process";
9837
+ import { execFileSync as execFileSync4, fork } from "child_process";
9838
9838
  import crypto5 from "crypto";
9839
9839
  import fs9 from "fs";
9840
9840
  import net from "net";
@@ -9921,6 +9921,21 @@ async function waitForSocket(socketPath, timeoutMs) {
9921
9921
  }
9922
9922
  throw new Error(`daemon did not become ready within ${timeoutMs}ms`);
9923
9923
  }
9924
+ async function waitForLockHolderDaemon(socketPath, timeoutMs, localBuild = localBuildFingerprint()) {
9925
+ const deadline = Date.now() + timeoutMs;
9926
+ while (Date.now() < deadline) {
9927
+ const ping = await pingSocket(socketPath);
9928
+ if (ping.alive && (localBuild === "unknown" || !ping.build || ping.build === localBuild)) {
9929
+ return;
9930
+ }
9931
+ await new Promise((r) => setTimeout(r, ENSURE_POLL_INTERVAL_MS));
9932
+ }
9933
+ const finalPing = await pingSocket(socketPath);
9934
+ if (finalPing.alive) {
9935
+ return;
9936
+ }
9937
+ throw new Error(`daemon did not become ready within ${timeoutMs}ms`);
9938
+ }
9924
9939
  function localBuildFingerprint() {
9925
9940
  try {
9926
9941
  const thisDir = path8.dirname(fileURLToPath2(import.meta.url));
@@ -9931,17 +9946,63 @@ function localBuildFingerprint() {
9931
9946
  return "unknown";
9932
9947
  }
9933
9948
  }
9934
- async function killDaemon(pidPath, socketPath) {
9935
- const pid = readPid(pidPath);
9936
- if (pid !== null) {
9949
+ function findDaemonPids(socketPath) {
9950
+ let out;
9951
+ try {
9952
+ out = execFileSync4("ps", ["-axww", "-o", "pid=,command="], {
9953
+ encoding: "utf-8"
9954
+ });
9955
+ } catch {
9956
+ return [];
9957
+ }
9958
+ const pids = [];
9959
+ for (const line of out.split("\n")) {
9960
+ const match = line.trim().match(/^(\d+)\s+(.*)$/);
9961
+ if (!match) {
9962
+ continue;
9963
+ }
9964
+ const pid = Number.parseInt(match[1], 10);
9965
+ const command = match[2];
9966
+ if (pid === process.pid || Number.isNaN(pid)) {
9967
+ continue;
9968
+ }
9969
+ if (command.includes("daemon/main.js") && commandTargetsSocket(command, socketPath)) {
9970
+ pids.push(pid);
9971
+ }
9972
+ }
9973
+ return pids;
9974
+ }
9975
+ function commandTargetsSocket(command, socketPath) {
9976
+ let idx = command.indexOf(socketPath);
9977
+ while (idx !== -1) {
9978
+ const after = command[idx + socketPath.length];
9979
+ if (after === void 0 || after === " ") {
9980
+ return true;
9981
+ }
9982
+ idx = command.indexOf(socketPath, idx + 1);
9983
+ }
9984
+ return false;
9985
+ }
9986
+ async function reapAllDaemons(socketPath, pidPath) {
9987
+ const pids = new Set(findDaemonPids(socketPath));
9988
+ const pidfilePid = readPid(pidPath);
9989
+ if (pidfilePid !== null) {
9990
+ pids.add(pidfilePid);
9991
+ }
9992
+ for (const pid of pids) {
9937
9993
  try {
9938
9994
  process.kill(pid, "SIGTERM");
9939
9995
  } catch {
9940
9996
  }
9941
- const deadline = Date.now() + 5e3;
9942
- while (processIsAlive(pid) && Date.now() < deadline) {
9943
- await new Promise((r) => setTimeout(r, 50));
9997
+ }
9998
+ const deadline = Date.now() + 5e3;
9999
+ while (Date.now() < deadline) {
10000
+ if (![...pids].some(processIsAlive)) {
10001
+ break;
9944
10002
  }
10003
+ await new Promise((r) => setTimeout(r, 50));
10004
+ }
10005
+ for (const pid of pids) {
9945
10006
  if (processIsAlive(pid)) {
9946
10007
  try {
9947
10008
  process.kill(pid, "SIGKILL");
@@ -10015,18 +10076,17 @@ async function ensureDaemon(socketPath = SOCKET_PATH, pidPath = PID_FILE_PATH) {
10015
10076
  const localBuild = localBuildFingerprint();
10016
10077
  if (ping.build && ping.build !== localBuild && localBuild !== "unknown") {
10017
10078
  console.error(`[daemon] build mismatch (running: ${ping.build}, local: ${localBuild}), restarting`);
10018
- await killDaemon(pidPath, socketPath);
10019
10079
  } else {
10020
10080
  return;
10021
10081
  }
10022
10082
  }
10023
10083
  }
10024
10084
  if (!acquireLock(socketPath)) {
10025
- await waitForSocket(socketPath, ENSURE_TIMEOUT_MS);
10085
+ await waitForLockHolderDaemon(socketPath, ENSURE_TIMEOUT_MS);
10026
10086
  return;
10027
10087
  }
10028
10088
  try {
10029
- cleanupStaleFiles(socketPath, pidPath);
10089
+ await reapAllDaemons(socketPath, pidPath);
10030
10090
  spawnDaemon(socketPath, pidPath);
10031
10091
  await waitForSocket(socketPath, ENSURE_TIMEOUT_MS);
10032
10092
  } finally {
@@ -10128,6 +10188,19 @@ var DaemonClient = class {
10128
10188
  throw new Error(res.error);
10129
10189
  }
10130
10190
  }
10191
+ /**
10192
+ * Forget a session from the daemon's in-memory map (the recovery path after
10193
+ * a `not-responding` gate). Matched daemon-side on the studio sessionId, so a
10194
+ * session re-bound under the same key is never touched. Returns how many
10195
+ * entries were dropped (0 or 1).
10196
+ */
10197
+ async clear(sessionId) {
10198
+ const res = await this.sendCommand({ cmd: "clear", sessionId });
10199
+ if (!res.ok) {
10200
+ throw new Error(res.error);
10201
+ }
10202
+ return res.cleared;
10203
+ }
10131
10204
  async detach(key) {
10132
10205
  const res = await this.sendCommand({ cmd: "detach", key });
10133
10206
  if (!res.ok) {
@@ -10239,6 +10312,8 @@ var DirectChannel = class {
10239
10312
  forceCloseWindow(sessionId) {
10240
10313
  closeStudioWindowsBySession(sessionId);
10241
10314
  }
10315
+ async clearSession(_sessionId) {
10316
+ }
10242
10317
  subscribe(sessionId, onEvent, onError) {
10243
10318
  const abortController = new AbortController();
10244
10319
  const done = (async () => {
@@ -10330,6 +10405,10 @@ var DaemonChannel = class _DaemonChannel {
10330
10405
  }
10331
10406
  forceCloseWindow(_sessionId) {
10332
10407
  }
10408
+ async clearSession(sessionId) {
10409
+ await this.client.clear(sessionId).catch(() => {
10410
+ });
10411
+ }
10333
10412
  subscribe(_sessionId, onEvent, onError) {
10334
10413
  const done = new Promise((resolve) => {
10335
10414
  this.doneResolve = resolve;
@@ -10385,6 +10464,9 @@ function withDirectFallback(primary, apiKey, serviceUrl) {
10385
10464
  forceCloseWindow(sessionId) {
10386
10465
  active.forceCloseWindow(sessionId);
10387
10466
  },
10467
+ async clearSession(sessionId) {
10468
+ await active.clearSession(sessionId);
10469
+ },
10388
10470
  subscribe(sessionId, onEvent, onError) {
10389
10471
  return active.subscribe(sessionId, onEvent, onError);
10390
10472
  },
@@ -10453,13 +10535,13 @@ async function reportHandoff(apiKey, pluginVersion, platform2, body) {
10453
10535
  import crypto7 from "crypto";
10454
10536
 
10455
10537
  // ../bitfab-plugin-lib/dist/frontmostApp.js
10456
- import { execFileSync as execFileSync4, spawn as spawn2 } from "child_process";
10538
+ import { execFileSync as execFileSync5, spawn as spawn2 } from "child_process";
10457
10539
  import os10 from "os";
10458
10540
  function findAncestorApp() {
10459
10541
  try {
10460
10542
  let pid = process.ppid;
10461
10543
  while (pid > 1) {
10462
- const output = execFileSync4("ps", ["-o", "ppid=,comm=", "-p", String(pid)], { stdio: "pipe", encoding: "utf-8" }).trim();
10544
+ const output = execFileSync5("ps", ["-o", "ppid=,comm=", "-p", String(pid)], { stdio: "pipe", encoding: "utf-8" }).trim();
10463
10545
  const match = output.match(/^\s*(\d+)\s+(.+)$/);
10464
10546
  if (!match) {
10465
10547
  break;
@@ -10518,19 +10600,19 @@ function getFrontmostApp() {
10518
10600
  const platform2 = os10.platform();
10519
10601
  try {
10520
10602
  if (platform2 === "darwin") {
10521
- return execFileSync4("osascript", [
10603
+ return execFileSync5("osascript", [
10522
10604
  "-e",
10523
10605
  'tell application "System Events" to get name of first process whose frontmost is true'
10524
10606
  ], { stdio: "pipe", encoding: "utf-8" }).trim();
10525
10607
  }
10526
10608
  if (platform2 === "linux") {
10527
- return execFileSync4("xdotool", ["getactivewindow"], {
10609
+ return execFileSync5("xdotool", ["getactivewindow"], {
10528
10610
  stdio: "pipe",
10529
10611
  encoding: "utf-8"
10530
10612
  }).trim();
10531
10613
  }
10532
10614
  if (platform2 === "win32") {
10533
- return execFileSync4("powershell.exe", [
10615
+ return execFileSync5("powershell.exe", [
10534
10616
  "-NoProfile",
10535
10617
  "-Command",
10536
10618
  `Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();' -Name WinFocus -Namespace Bitfab; [Bitfab.WinFocus]::GetForegroundWindow().ToInt64()`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitfab-cli",
3
- "version": "0.2.97",
3
+ "version": "0.2.99",
4
4
  "description": "Install and configure the Bitfab plugin in Claude Code, Codex, or Cursor.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",