bitfab-cli 0.2.75 → 0.2.77

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 +104 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6801,10 +6801,6 @@ var require_dist = __commonJS({
6801
6801
  // src/index.ts
6802
6802
  import { cancel as cancel2 } from "@clack/prompts";
6803
6803
 
6804
- // src/init.ts
6805
- import { execSync as execSync3 } from "child_process";
6806
- import * as p4 from "@clack/prompts";
6807
-
6808
6804
  // ../bitfab-plugin-lib/dist/errors.js
6809
6805
  var NotAuthenticatedError = class extends Error {
6810
6806
  constructor(message) {
@@ -7614,7 +7610,7 @@ function printBlockingNotice(until) {
7614
7610
 
7615
7611
  // ../bitfab-plugin-lib/dist/studioTeardown.js
7616
7612
  var WINDOW_CLOSE_GRACE_PERIOD_MS = 1e4;
7617
- function createWindowCloseArbiter(handlers) {
7613
+ function createWindowCloseArbiter(handlers, graceMs = WINDOW_CLOSE_GRACE_PERIOD_MS) {
7618
7614
  let graceTimer = null;
7619
7615
  const cancel3 = () => {
7620
7616
  if (graceTimer) {
@@ -7629,7 +7625,7 @@ function createWindowCloseArbiter(handlers) {
7629
7625
  graceTimer = setTimeout(() => {
7630
7626
  graceTimer = null;
7631
7627
  handlers.onConfirmed(event);
7632
- }, WINDOW_CLOSE_GRACE_PERIOD_MS);
7628
+ }, graceMs);
7633
7629
  return;
7634
7630
  }
7635
7631
  if (event.type === "studio:session-started" && graceTimer) {
@@ -8196,6 +8192,11 @@ async function openStudioTo(path15, opts = {}) {
8196
8192
  const signInPath = `/studio/sign-in?redirect_url=${encodeURIComponent(redirectPath)}`;
8197
8193
  const signInUrl = `${serviceUrl}${signInPath}&session=${encodeURIComponent(sessionId2)}`;
8198
8194
  const windowPid = openChromelessWindow(signInUrl);
8195
+ writeActiveStudioSession({
8196
+ sessionId: sessionId2,
8197
+ serviceUrl,
8198
+ windowPid: windowPid ?? void 0
8199
+ });
8199
8200
  opts.onLoginRequired?.(sessionId2, signInUrl);
8200
8201
  const abortController = new AbortController();
8201
8202
  const timer = setTimeout(() => abortController.abort(), LOGIN_TIMEOUT_MS);
@@ -8222,11 +8223,6 @@ async function openStudioTo(path15, opts = {}) {
8222
8223
  apiKey: loginApiKey,
8223
8224
  sessionId: sessionId2
8224
8225
  });
8225
- writeActiveStudioSession({
8226
- sessionId: sessionId2,
8227
- serviceUrl,
8228
- windowPid: windowPid ?? void 0
8229
- });
8230
8226
  const poller2 = startEventPoller(serviceUrl, loginApiKey, sessionId2, opts.onEvent, restoreFocus, { claim: opts.monitor !== "wait" });
8231
8227
  return {
8232
8228
  sessionId: sessionId2,
@@ -8395,7 +8391,7 @@ function awaitReauthLogin(args) {
8395
8391
  });
8396
8392
  });
8397
8393
  }
8398
- function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus, { claim = true } = {}) {
8394
+ function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus, { claim = true, fromStreamStart = false, graceMs, forceClear = false } = {}) {
8399
8395
  printBlockingNotice(BLOCKING_NOTICE_UNTIL);
8400
8396
  if (claim) {
8401
8397
  claimStudioMonitor();
@@ -8418,15 +8414,19 @@ function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus,
8418
8414
  };
8419
8415
  const arbiter = createWindowCloseArbiter({
8420
8416
  onConfirmed: (event) => {
8421
- clearActiveStudioSession(sessionId);
8417
+ if (forceClear) {
8418
+ clearActiveStudioSession(sessionId, { force: true });
8419
+ } else {
8420
+ clearActiveStudioSession(sessionId);
8421
+ }
8422
8422
  notifyEnded(event);
8423
8423
  },
8424
8424
  onRefuted: () => {
8425
8425
  console.error("Studio reconnected after page refresh");
8426
8426
  }
8427
- });
8427
+ }, graceMs);
8428
8428
  const done = (async () => {
8429
- const startCursor = await fetchStreamTip({ serviceUrl, apiKey, sessionId });
8429
+ const startCursor = fromStreamStart ? void 0 : await fetchStreamTip({ serviceUrl, apiKey, sessionId });
8430
8430
  if (abortController.signal.aborted) {
8431
8431
  return;
8432
8432
  }
@@ -8479,8 +8479,62 @@ function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus,
8479
8479
  done
8480
8480
  };
8481
8481
  }
8482
+ var LOGIN_MONITOR_DRAIN_MS = 8e3;
8483
+ async function runLoginWindowMonitor(sessionId) {
8484
+ const config2 = getConfig();
8485
+ const apiKey = config2.apiKey;
8486
+ if (!apiKey) {
8487
+ return;
8488
+ }
8489
+ const poller = startEventPoller(
8490
+ config2.serviceUrl,
8491
+ apiKey,
8492
+ sessionId,
8493
+ () => {
8494
+ },
8495
+ () => {
8496
+ },
8497
+ // claim: false — this is a transient cleanup reader with a no-op event
8498
+ // handler, NOT the session's real monitor. If it claimed the lock, a
8499
+ // subsequent `openStudioTo` reuse with monitor:"start" (e.g. a flow's
8500
+ // backgrounded open right after a PARKED login) would lose the atomic
8501
+ // claim, assume a live monitor is forwarding events, and fire-and-exit —
8502
+ // leaving the session with no real monitor while this one forwards
8503
+ // nothing. Reading without claiming lets it clean up the pointer
8504
+ // (window-closed → clear) without shadowing the real monitor.
8505
+ //
8506
+ // forceClear: the clear runs in a detached child while the login/init
8507
+ // parent that WROTE the pointer may still be alive (init keeps running with
8508
+ // exitOnComplete:false, even spawnSync-ing the editor), so a non-force clear
8509
+ // would be refused by the writer-alive guard and the closed window's
8510
+ // pointer would leak. Safe — the clear still matches on the unique
8511
+ // sessionId.
8512
+ { claim: false, fromStreamStart: true, graceMs: 0, forceClear: true }
8513
+ );
8514
+ let drainTimer;
8515
+ const drain = new Promise((resolve) => {
8516
+ drainTimer = setTimeout(resolve, LOGIN_MONITOR_DRAIN_MS);
8517
+ });
8518
+ await Promise.race([poller.done, drain]);
8519
+ if (drainTimer) {
8520
+ clearTimeout(drainTimer);
8521
+ }
8522
+ poller.abort();
8523
+ }
8482
8524
 
8483
8525
  // ../bitfab-plugin-lib/dist/commands/login.js
8526
+ function trySpawnDetachedMonitor(sessionId) {
8527
+ try {
8528
+ const pid = spawnDetached(process.execPath, [
8529
+ ...process.argv.slice(1),
8530
+ "--drain",
8531
+ sessionId
8532
+ ]);
8533
+ return pid != null;
8534
+ } catch {
8535
+ return false;
8536
+ }
8537
+ }
8484
8538
  async function verifyToken(serviceUrl, token) {
8485
8539
  try {
8486
8540
  const res = await fetch(`${serviceUrl}/api/plugin/whoami`, {
@@ -8496,6 +8550,18 @@ async function verifyToken(serviceUrl, token) {
8496
8550
  }
8497
8551
  async function runLogin(platform2, pluginVersion, options) {
8498
8552
  const exitOnComplete = options?.exitOnComplete ?? true;
8553
+ const drainIdx = process.argv.indexOf("--drain");
8554
+ if (drainIdx !== -1) {
8555
+ const sessionId = process.argv[drainIdx + 1];
8556
+ if (!sessionId) {
8557
+ throw new Error("--drain requires a sessionId argument");
8558
+ }
8559
+ await runLoginWindowMonitor(sessionId);
8560
+ if (exitOnComplete) {
8561
+ process.exit(0);
8562
+ }
8563
+ return;
8564
+ }
8499
8565
  const force = options?.force ?? process.argv.includes("--force");
8500
8566
  const config2 = getConfig();
8501
8567
  if (!force && hasCredentials() && config2.apiKey) {
@@ -8510,7 +8576,6 @@ Already logged in as ${identity}. Run the login command with --force to re-authe
8510
8576
  return;
8511
8577
  }
8512
8578
  }
8513
- console.error("NOTE: this command blocks until sign-in completes in the browser (10-minute timeout).");
8514
8579
  console.log("\nOpening Studio to sign in...");
8515
8580
  try {
8516
8581
  const result = await openStudioTo("/studio", {
@@ -8522,7 +8587,6 @@ If the browser didn't open, visit this URL manually:
8522
8587
  ${signInUrl}`);
8523
8588
  }
8524
8589
  });
8525
- result.abort();
8526
8590
  const apiKey = getConfig().apiKey;
8527
8591
  if (apiKey) {
8528
8592
  await reportHandoff(apiKey, pluginVersion, platform2, {
@@ -8537,6 +8601,9 @@ ${signInUrl}`);
8537
8601
  ${greeting}`);
8538
8602
  console.log("Bitfab MCP tools are now active. (via studio)");
8539
8603
  }
8604
+ if (!trySpawnDetachedMonitor(result.sessionId)) {
8605
+ await runLoginWindowMonitor(result.sessionId);
8606
+ }
8540
8607
  if (exitOnComplete) {
8541
8608
  process.exit(0);
8542
8609
  }
@@ -27780,6 +27847,10 @@ var McpZodTypeKind;
27780
27847
  // ../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_@cfworker+json-schema@4.1.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
27781
27848
  import process3 from "process";
27782
27849
 
27850
+ // src/init.ts
27851
+ import { execSync as execSync3 } from "child_process";
27852
+ import * as p4 from "@clack/prompts";
27853
+
27783
27854
  // src/claude.ts
27784
27855
  import { spawnSync } from "child_process";
27785
27856
  import fs15 from "fs";
@@ -28423,7 +28494,22 @@ function parseArgs2(argv) {
28423
28494
  }
28424
28495
  return { command, editor, skipPermissions, rest };
28425
28496
  }
28497
+ async function handleDrainArg(argv) {
28498
+ const drainIdx = argv.indexOf("--drain");
28499
+ if (drainIdx === -1) {
28500
+ return false;
28501
+ }
28502
+ const sessionId = argv[drainIdx + 1];
28503
+ if (!sessionId) {
28504
+ throw new Error("--drain requires a sessionId argument");
28505
+ }
28506
+ await runLoginWindowMonitor(sessionId);
28507
+ return true;
28508
+ }
28426
28509
  async function main() {
28510
+ if (await handleDrainArg(process.argv)) {
28511
+ return;
28512
+ }
28427
28513
  const { command, editor, skipPermissions, rest } = parseArgs2(
28428
28514
  process.argv.slice(2)
28429
28515
  );
@@ -28480,5 +28566,6 @@ main().catch((err) => {
28480
28566
  process.exit(1);
28481
28567
  });
28482
28568
  export {
28569
+ handleDrainArg,
28483
28570
  parseArgs2 as parseArgs
28484
28571
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitfab-cli",
3
- "version": "0.2.75",
3
+ "version": "0.2.77",
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",