bitfab-cli 0.2.50 → 0.2.51

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 +154 -13
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7418,6 +7418,7 @@ function buildBitfabRequestHeaders(apiKey, pluginVersion, platform2) {
7418
7418
  }
7419
7419
 
7420
7420
  // ../bitfab-plugin-lib/dist/activeStudioSession.js
7421
+ import { execFileSync as execFileSync2 } from "child_process";
7421
7422
  import crypto3 from "crypto";
7422
7423
  import fs6 from "fs";
7423
7424
  import os6 from "os";
@@ -7461,6 +7462,22 @@ function isProcessAlive(pid) {
7461
7462
  return err.code === "EPERM";
7462
7463
  }
7463
7464
  }
7465
+ function processStartToken(pid) {
7466
+ try {
7467
+ const out = execFileSync2("ps", ["-o", "lstart=", "-p", String(pid)], {
7468
+ encoding: "utf-8",
7469
+ stdio: ["ignore", "pipe", "ignore"],
7470
+ // Bound it: a local `ps -p` is instant, but with no timeout a wedged `ps`
7471
+ // (severe load, a stuck process table) could block the monitor claim. On
7472
+ // timeout this throws, we return null, and the caller degrades to a plain
7473
+ // liveness check — never a hang.
7474
+ timeout: 2e3
7475
+ }).trim();
7476
+ return out || null;
7477
+ } catch {
7478
+ return null;
7479
+ }
7480
+ }
7464
7481
  function clearActiveStudioSession(sessionId, opts = {}) {
7465
7482
  try {
7466
7483
  const current = readActiveStudioSessionRaw();
@@ -7500,6 +7517,92 @@ function readActiveStudioSessionRaw() {
7500
7517
  function readActiveStudioSession() {
7501
7518
  return readActiveStudioSessionRaw();
7502
7519
  }
7520
+ function monitorLockPath() {
7521
+ return path5.join(os6.homedir(), ".config", "bitfab", `active-studio-session.${sessionScope()}.monitor.lock`);
7522
+ }
7523
+ function tryClaimStudioMonitor() {
7524
+ const lock = monitorLockPath();
7525
+ fs6.mkdirSync(path5.dirname(lock), { recursive: true });
7526
+ for (let attempt = 0; attempt < 10; attempt++) {
7527
+ try {
7528
+ const fd = fs6.openSync(lock, "wx");
7529
+ fs6.writeSync(fd, monitorLockBody(process.pid));
7530
+ fs6.closeSync(fd);
7531
+ stampMonitorPid();
7532
+ return true;
7533
+ } catch (err) {
7534
+ if (err.code !== "EEXIST") {
7535
+ throw err;
7536
+ }
7537
+ const owner = readLockOwner(lock);
7538
+ if (owner?.pid === process.pid) {
7539
+ return true;
7540
+ }
7541
+ if (owner && isMonitorOwnerLive(owner)) {
7542
+ return false;
7543
+ }
7544
+ try {
7545
+ fs6.unlinkSync(lock);
7546
+ } catch {
7547
+ }
7548
+ }
7549
+ }
7550
+ stampMonitorPid();
7551
+ return true;
7552
+ }
7553
+ function claimStudioMonitor() {
7554
+ try {
7555
+ const lock = monitorLockPath();
7556
+ fs6.mkdirSync(path5.dirname(lock), { recursive: true });
7557
+ fs6.writeFileSync(lock, monitorLockBody(process.pid));
7558
+ } catch {
7559
+ }
7560
+ stampMonitorPid();
7561
+ }
7562
+ function releaseStudioMonitor() {
7563
+ try {
7564
+ const lock = monitorLockPath();
7565
+ if (readLockOwner(lock)?.pid === process.pid) {
7566
+ fs6.unlinkSync(lock);
7567
+ }
7568
+ } catch {
7569
+ }
7570
+ }
7571
+ function monitorLockBody(pid) {
7572
+ return `${pid}
7573
+ ${processStartToken(pid) ?? ""}`;
7574
+ }
7575
+ function isMonitorOwnerLive(owner) {
7576
+ if (!isProcessAlive(owner.pid)) {
7577
+ return false;
7578
+ }
7579
+ if (!owner.start) {
7580
+ return true;
7581
+ }
7582
+ const current = processStartToken(owner.pid);
7583
+ if (current === null) {
7584
+ return true;
7585
+ }
7586
+ return current === owner.start;
7587
+ }
7588
+ function readLockOwner(lock) {
7589
+ try {
7590
+ const [pidLine = "", ...rest] = fs6.readFileSync(lock, "utf-8").split("\n");
7591
+ const pid = Number.parseInt(pidLine.trim(), 10);
7592
+ if (!Number.isInteger(pid)) {
7593
+ return null;
7594
+ }
7595
+ return { pid, start: rest.join("\n").trim() };
7596
+ } catch {
7597
+ return null;
7598
+ }
7599
+ }
7600
+ function stampMonitorPid() {
7601
+ const current = readActiveStudioSessionRaw();
7602
+ if (current) {
7603
+ writeRecord({ ...current, pid: process.pid });
7604
+ }
7605
+ }
7503
7606
 
7504
7607
  // ../bitfab-plugin-lib/dist/studioTeardown.js
7505
7608
  var WINDOW_CLOSE_GRACE_PERIOD_MS = 1e4;
@@ -7859,13 +7962,13 @@ async function reportHandoff(apiKey, pluginVersion, platform2, body) {
7859
7962
  import crypto4 from "crypto";
7860
7963
 
7861
7964
  // ../bitfab-plugin-lib/dist/frontmostApp.js
7862
- import { execFileSync as execFileSync2, spawn as spawn2 } from "child_process";
7965
+ import { execFileSync as execFileSync3, spawn as spawn2 } from "child_process";
7863
7966
  import os8 from "os";
7864
7967
  function findAncestorApp() {
7865
7968
  try {
7866
7969
  let pid = process.ppid;
7867
7970
  while (pid > 1) {
7868
- const output = execFileSync2("ps", ["-o", "ppid=,comm=", "-p", String(pid)], { stdio: "pipe", encoding: "utf-8" }).trim();
7971
+ const output = execFileSync3("ps", ["-o", "ppid=,comm=", "-p", String(pid)], { stdio: "pipe", encoding: "utf-8" }).trim();
7869
7972
  const match = output.match(/^\s*(\d+)\s+(.+)$/);
7870
7973
  if (!match) {
7871
7974
  break;
@@ -7924,19 +8027,19 @@ function getFrontmostApp() {
7924
8027
  const platform2 = os8.platform();
7925
8028
  try {
7926
8029
  if (platform2 === "darwin") {
7927
- return execFileSync2("osascript", [
8030
+ return execFileSync3("osascript", [
7928
8031
  "-e",
7929
8032
  'tell application "System Events" to get name of first process whose frontmost is true'
7930
8033
  ], { stdio: "pipe", encoding: "utf-8" }).trim();
7931
8034
  }
7932
8035
  if (platform2 === "linux") {
7933
- return execFileSync2("xdotool", ["getactivewindow"], {
8036
+ return execFileSync3("xdotool", ["getactivewindow"], {
7934
8037
  stdio: "pipe",
7935
8038
  encoding: "utf-8"
7936
8039
  }).trim();
7937
8040
  }
7938
8041
  if (platform2 === "win32") {
7939
- return execFileSync2("powershell.exe", [
8042
+ return execFileSync3("powershell.exe", [
7940
8043
  "-NoProfile",
7941
8044
  "-Command",
7942
8045
  `Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();' -Name WinFocus -Namespace Bitfab; [Bitfab.WinFocus]::GetForegroundWindow().ToInt64()`
@@ -8115,7 +8218,7 @@ async function openStudioTo(path15, opts = {}) {
8115
8218
  serviceUrl,
8116
8219
  windowPid: windowPid ?? void 0
8117
8220
  });
8118
- const poller2 = startEventPoller(serviceUrl, loginApiKey, sessionId2, opts.onEvent, restoreFocus);
8221
+ const poller2 = startEventPoller(serviceUrl, loginApiKey, sessionId2, opts.onEvent, restoreFocus, { claim: opts.monitor !== "wait" });
8119
8222
  return {
8120
8223
  sessionId: sessionId2,
8121
8224
  serviceUrl,
@@ -8145,13 +8248,34 @@ async function openStudioTo(path15, opts = {}) {
8145
8248
  if (!ack.acked) {
8146
8249
  throw new StudioNavigationError(ack.reason ?? "unknown", ack.blockedReason, existing.sessionId);
8147
8250
  }
8148
- const poller2 = opts.onEvent ? startEventPoller(existing.serviceUrl, apiKey, existing.sessionId, opts.onEvent, restoreFocus) : { abort: noop, done: Promise.resolve() };
8251
+ if (opts.onEvent && opts.monitor === "wait") {
8252
+ const poller2 = startEventPoller(existing.serviceUrl, apiKey, existing.sessionId, opts.onEvent, restoreFocus, { claim: false });
8253
+ return {
8254
+ sessionId: existing.sessionId,
8255
+ serviceUrl: existing.serviceUrl,
8256
+ apiKey,
8257
+ opened: false,
8258
+ ...poller2
8259
+ };
8260
+ }
8261
+ if (opts.onEvent && opts.monitor === "start" && tryClaimStudioMonitor()) {
8262
+ const poller2 = startEventPoller(existing.serviceUrl, apiKey, existing.sessionId, opts.onEvent, restoreFocus, { claim: true });
8263
+ return {
8264
+ sessionId: existing.sessionId,
8265
+ serviceUrl: existing.serviceUrl,
8266
+ apiKey,
8267
+ opened: false,
8268
+ ...poller2
8269
+ };
8270
+ }
8271
+ restoreFocus();
8149
8272
  return {
8150
8273
  sessionId: existing.sessionId,
8151
8274
  serviceUrl: existing.serviceUrl,
8152
8275
  apiKey,
8153
8276
  opened: false,
8154
- ...poller2
8277
+ abort: noop,
8278
+ done: Promise.resolve()
8155
8279
  };
8156
8280
  }
8157
8281
  const sessionId = crypto4.randomUUID();
@@ -8162,7 +8286,7 @@ async function openStudioTo(path15, opts = {}) {
8162
8286
  initialPath: path15,
8163
8287
  clientHeaders: opts.clientHeaders
8164
8288
  });
8165
- const poller = opts.onEvent ? startEventPoller(session.serviceUrl, apiKey, session.sessionId, opts.onEvent, restoreFocus) : { abort: noop, done: Promise.resolve() };
8289
+ const poller = opts.onEvent ? startEventPoller(session.serviceUrl, apiKey, session.sessionId, opts.onEvent, restoreFocus, { claim: opts.monitor !== "wait" }) : { abort: noop, done: Promise.resolve() };
8166
8290
  return {
8167
8291
  sessionId: session.sessionId,
8168
8292
  serviceUrl: session.serviceUrl,
@@ -8192,13 +8316,13 @@ async function loginViaExistingWindow(args) {
8192
8316
  }
8193
8317
  saveCredentials(loginApiKey);
8194
8318
  args.opts.onAuthenticated?.(sessionId);
8195
- if (args.opts.onEvent) {
8319
+ if (args.opts.onEvent && (args.opts.monitor === "wait" || args.opts.monitor === "start" && tryClaimStudioMonitor())) {
8196
8320
  await waitForSessionReady({
8197
8321
  serviceUrl,
8198
8322
  apiKey: loginApiKey,
8199
8323
  sessionId
8200
8324
  });
8201
- const poller = startEventPoller(serviceUrl, loginApiKey, sessionId, args.opts.onEvent, args.restoreFocus);
8325
+ const poller = startEventPoller(serviceUrl, loginApiKey, sessionId, args.opts.onEvent, args.restoreFocus, { claim: args.opts.monitor !== "wait" });
8202
8326
  return {
8203
8327
  sessionId,
8204
8328
  serviceUrl,
@@ -8262,15 +8386,24 @@ function awaitReauthLogin(args) {
8262
8386
  });
8263
8387
  });
8264
8388
  }
8265
- function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus) {
8389
+ function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus, { claim = true } = {}) {
8390
+ if (claim) {
8391
+ claimStudioMonitor();
8392
+ }
8266
8393
  const abortController = new AbortController();
8267
8394
  let endedNotified = false;
8395
+ const releaseLock = () => {
8396
+ if (claim) {
8397
+ releaseStudioMonitor();
8398
+ }
8399
+ };
8268
8400
  const notifyEnded = (baseEvent) => {
8269
8401
  if (!endedNotified) {
8270
8402
  endedNotified = true;
8271
8403
  restoreFocus();
8272
8404
  onEvent({ ...baseEvent, type: "studio:session-ended" });
8273
8405
  }
8406
+ releaseLock();
8274
8407
  abortController.abort();
8275
8408
  };
8276
8409
  const arbiter = createWindowCloseArbiter({
@@ -8309,6 +8442,7 @@ function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus)
8309
8442
  if (event.type === "studio:not-member") {
8310
8443
  restoreFocus();
8311
8444
  onEvent(event);
8445
+ releaseLock();
8312
8446
  abortController.abort();
8313
8447
  return;
8314
8448
  }
@@ -8325,8 +8459,15 @@ function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus)
8325
8459
  if (!abortController.signal.aborted) {
8326
8460
  console.error(`studio agent-event poll terminated: ${err.message}`);
8327
8461
  }
8462
+ releaseLock();
8328
8463
  });
8329
- return { abort: () => abortController.abort(), done };
8464
+ return {
8465
+ abort: () => {
8466
+ releaseLock();
8467
+ abortController.abort();
8468
+ },
8469
+ done
8470
+ };
8330
8471
  }
8331
8472
 
8332
8473
  // ../bitfab-plugin-lib/dist/commands/login.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitfab-cli",
3
- "version": "0.2.50",
3
+ "version": "0.2.51",
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",