bitfab-cli 0.2.113 → 0.2.114

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 +408 -141
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5152,8 +5152,8 @@ var require_utils = __commonJS({
5152
5152
  }
5153
5153
  return ind;
5154
5154
  }
5155
- function removeDotSegments(path18) {
5156
- let input = path18;
5155
+ function removeDotSegments(path19) {
5156
+ let input = path19;
5157
5157
  const output = [];
5158
5158
  let nextSlash = -1;
5159
5159
  let len = 0;
@@ -5352,8 +5352,8 @@ var require_schemes = __commonJS({
5352
5352
  wsComponent.secure = void 0;
5353
5353
  }
5354
5354
  if (wsComponent.resourceName) {
5355
- const [path18, query] = wsComponent.resourceName.split("?");
5356
- wsComponent.path = path18 && path18 !== "/" ? path18 : void 0;
5355
+ const [path19, query] = wsComponent.resourceName.split("?");
5356
+ wsComponent.path = path19 && path19 !== "/" ? path19 : void 0;
5357
5357
  wsComponent.query = query;
5358
5358
  wsComponent.resourceName = void 0;
5359
5359
  }
@@ -8715,12 +8715,12 @@ var require_dist = __commonJS({
8715
8715
  throw new Error(`Unknown format "${name}"`);
8716
8716
  return f;
8717
8717
  };
8718
- function addFormats(ajv, list, fs20, exportName) {
8718
+ function addFormats(ajv, list, fs21, exportName) {
8719
8719
  var _a2;
8720
8720
  var _b;
8721
8721
  (_a2 = (_b = ajv.opts.code).formats) !== null && _a2 !== void 0 ? _a2 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
8722
8722
  for (const f of list)
8723
- ajv.addFormat(f, fs20[f]);
8723
+ ajv.addFormat(f, fs21[f]);
8724
8724
  }
8725
8725
  module.exports = exports = formatsPlugin;
8726
8726
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -8922,18 +8922,18 @@ async function closeStudio(client, message) {
8922
8922
  data: message ? { message } : {}
8923
8923
  });
8924
8924
  }
8925
- async function navigateStudio(client, path18) {
8926
- if (!isValidStudioRoute(path18)) {
8927
- throw new Error(`Studio route not allowed: ${path18}`);
8925
+ async function navigateStudio(client, path19) {
8926
+ if (!isValidStudioRoute(path19)) {
8927
+ throw new Error(`Studio route not allowed: ${path19}`);
8928
8928
  }
8929
8929
  return pushAgentSessionEvent(client, {
8930
8930
  type: "agent:navigate",
8931
- data: { path: path18 }
8931
+ data: { path: path19 }
8932
8932
  });
8933
8933
  }
8934
8934
  var STUDIO_NAVIGATE_ACK_TIMEOUT_MS = 12e3;
8935
8935
  var PING_PONG_TIMEOUT_MS = 5e3;
8936
- function awaitNavigateAck(client, path18, ackTimeoutMs, startCursor) {
8936
+ function awaitNavigateAck(client, path19, ackTimeoutMs, startCursor) {
8937
8937
  const abortController = new AbortController();
8938
8938
  let timer = null;
8939
8939
  const result = new Promise((resolve) => {
@@ -8941,7 +8941,7 @@ function awaitNavigateAck(client, path18, ackTimeoutMs, startCursor) {
8941
8941
  resolve({ acked: false, reason: "timeout" });
8942
8942
  abortController.abort();
8943
8943
  }, ackTimeoutMs);
8944
- const pathOnly = path18.split("?")[0];
8944
+ const pathOnly = path19.split("?")[0];
8945
8945
  pollAgentSessionEvents({
8946
8946
  serviceUrl: client.serviceUrl,
8947
8947
  apiKey: client.apiKey,
@@ -9027,18 +9027,18 @@ function awaitPong(client, pingTs) {
9027
9027
  });
9028
9028
  });
9029
9029
  }
9030
- async function navigateStudioAndAwaitAck(client, path18, opts = {}) {
9030
+ async function navigateStudioAndAwaitAck(client, path19, opts = {}) {
9031
9031
  const ackTimeoutMs = opts.ackTimeoutMs ?? STUDIO_NAVIGATE_ACK_TIMEOUT_MS;
9032
- if (!isValidStudioRoute(path18)) {
9032
+ if (!isValidStudioRoute(path19)) {
9033
9033
  return { acked: false, reason: "invalid-route" };
9034
9034
  }
9035
9035
  const startCursor = await fetchStreamTip(client);
9036
9036
  try {
9037
- await navigateStudio(client, path18);
9037
+ await navigateStudio(client, path19);
9038
9038
  } catch {
9039
9039
  return { acked: false, reason: "push-failed" };
9040
9040
  }
9041
- const result = await awaitNavigateAck(client, path18, ackTimeoutMs, startCursor);
9041
+ const result = await awaitNavigateAck(client, path19, ackTimeoutMs, startCursor);
9042
9042
  if (result.acked || result.reason !== "timeout") {
9043
9043
  return result;
9044
9044
  }
@@ -9397,6 +9397,103 @@ function updateActiveStudioSession(updates) {
9397
9397
  writeStateFile(sessionFilePath(), updated);
9398
9398
  return updated;
9399
9399
  }
9400
+ function isProcessAlive(pid) {
9401
+ try {
9402
+ process.kill(pid, 0);
9403
+ return true;
9404
+ } catch (err) {
9405
+ return err.code === "EPERM";
9406
+ }
9407
+ }
9408
+ function processStartToken(pid) {
9409
+ try {
9410
+ const out = execFileSync2("ps", ["-o", "lstart=", "-p", String(pid)], {
9411
+ encoding: "utf-8",
9412
+ stdio: ["ignore", "pipe", "ignore"],
9413
+ timeout: 2e3
9414
+ }).trim();
9415
+ return out || null;
9416
+ } catch {
9417
+ return null;
9418
+ }
9419
+ }
9420
+ function monitorLockPath() {
9421
+ return path6.join(SESSIONS_DIR, `${shortHash(resolveAgentSessionKey())}.monitor.lock`);
9422
+ }
9423
+ function monitorLockBody(pid) {
9424
+ return `${pid}
9425
+ ${processStartToken(pid) ?? ""}`;
9426
+ }
9427
+ function isMonitorOwnerLive(owner) {
9428
+ if (!isProcessAlive(owner.pid)) {
9429
+ return false;
9430
+ }
9431
+ if (!owner.start) {
9432
+ return true;
9433
+ }
9434
+ const current = processStartToken(owner.pid);
9435
+ if (current === null) {
9436
+ return true;
9437
+ }
9438
+ return current === owner.start;
9439
+ }
9440
+ function readLockOwner(lock) {
9441
+ try {
9442
+ const [pidLine = "", ...rest] = fs7.readFileSync(lock, "utf-8").split("\n");
9443
+ const pid = Number.parseInt(pidLine.trim(), 10);
9444
+ if (!Number.isInteger(pid)) {
9445
+ return null;
9446
+ }
9447
+ return { pid, start: rest.join("\n").trim() };
9448
+ } catch {
9449
+ return null;
9450
+ }
9451
+ }
9452
+ function stampMonitorPid() {
9453
+ const current = readStateFile(sessionFilePath());
9454
+ if (current) {
9455
+ writeStateFile(sessionFilePath(), { ...current, pollerPid: process.pid });
9456
+ }
9457
+ }
9458
+ function tryClaimStudioMonitor() {
9459
+ const lock = monitorLockPath();
9460
+ fs7.mkdirSync(path6.dirname(lock), { recursive: true });
9461
+ for (let attempt = 0; attempt < 10; attempt++) {
9462
+ try {
9463
+ const fd = fs7.openSync(lock, "wx");
9464
+ fs7.writeSync(fd, monitorLockBody(process.pid));
9465
+ fs7.closeSync(fd);
9466
+ stampMonitorPid();
9467
+ return true;
9468
+ } catch (err) {
9469
+ if (err.code !== "EEXIST") {
9470
+ throw err;
9471
+ }
9472
+ const owner = readLockOwner(lock);
9473
+ if (owner?.pid === process.pid) {
9474
+ return true;
9475
+ }
9476
+ if (owner && isMonitorOwnerLive(owner)) {
9477
+ return false;
9478
+ }
9479
+ try {
9480
+ fs7.unlinkSync(lock);
9481
+ } catch {
9482
+ }
9483
+ }
9484
+ }
9485
+ stampMonitorPid();
9486
+ return true;
9487
+ }
9488
+ function releaseStudioMonitor() {
9489
+ try {
9490
+ const lock = monitorLockPath();
9491
+ if (readLockOwner(lock)?.pid === process.pid) {
9492
+ fs7.unlinkSync(lock);
9493
+ }
9494
+ } catch {
9495
+ }
9496
+ }
9400
9497
 
9401
9498
  // ../bitfab-plugin-lib/dist/browser.js
9402
9499
  import { execFileSync as execFileSync3, execSync, spawn } from "child_process";
@@ -9795,7 +9892,7 @@ function extractCommandName(argv) {
9795
9892
  }
9796
9893
 
9797
9894
  // ../bitfab-plugin-lib/dist/studioChannel.js
9798
- import crypto6 from "crypto";
9895
+ import crypto7 from "crypto";
9799
9896
 
9800
9897
  // ../bitfab-plugin-lib/dist/commands/createStudioSession.js
9801
9898
  function ensureStudioPath(p5) {
@@ -10238,6 +10335,92 @@ var DaemonClient = class {
10238
10335
  }
10239
10336
  };
10240
10337
 
10338
+ // ../bitfab-plugin-lib/dist/studioEventLog.js
10339
+ import crypto6 from "crypto";
10340
+ import fs10 from "fs";
10341
+ import os10 from "os";
10342
+ import path9 from "path";
10343
+ var SESSIONS_DIR2 = path9.join(os10.homedir(), ".config", "bitfab", "sessions");
10344
+ function shortHash2(input) {
10345
+ return crypto6.createHash("sha256").update(input).digest("hex").slice(0, 16);
10346
+ }
10347
+ function eventLogPathForKey(key) {
10348
+ return path9.join(SESSIONS_DIR2, `${shortHash2(key)}.events.jsonl`);
10349
+ }
10350
+ function studioEventToLogLine(event, sessionId) {
10351
+ const data = event.data ?? {};
10352
+ const line = {
10353
+ event: event.type.replace("studio:", ""),
10354
+ sessionId,
10355
+ ...data
10356
+ };
10357
+ delete line.token;
10358
+ return line;
10359
+ }
10360
+ function appendStudioEvent(key, line) {
10361
+ try {
10362
+ const target = eventLogPathForKey(key);
10363
+ fs10.mkdirSync(path9.dirname(target), { recursive: true });
10364
+ fs10.appendFileSync(target, `${JSON.stringify(line)}
10365
+ `);
10366
+ } catch {
10367
+ }
10368
+ }
10369
+ function truncateStudioEventLog(key) {
10370
+ try {
10371
+ fs10.rmSync(eventLogPathForKey(key), { force: true });
10372
+ } catch {
10373
+ }
10374
+ }
10375
+ function readStudioEventLogTail(key, fromLine = 1) {
10376
+ try {
10377
+ const raw = fs10.readFileSync(eventLogPathForKey(key), "utf-8");
10378
+ const all = raw.split("\n").filter((l) => l.length > 0);
10379
+ const start = Math.max(0, fromLine - 1);
10380
+ const slice = all.slice(start);
10381
+ const lines = [];
10382
+ for (const l of slice) {
10383
+ try {
10384
+ lines.push(JSON.parse(l));
10385
+ } catch {
10386
+ }
10387
+ }
10388
+ return { lines, nextLine: all.length + 1 };
10389
+ } catch {
10390
+ return { lines: [], nextLine: fromLine };
10391
+ }
10392
+ }
10393
+ function tailStudioEvents(key, onLine, opts) {
10394
+ const pollMs = opts.pollMs ?? 200;
10395
+ let cursor = opts.fromLine ?? 1;
10396
+ return new Promise((resolve) => {
10397
+ if (opts.abortSignal.aborted) {
10398
+ resolve();
10399
+ return;
10400
+ }
10401
+ const tick = () => {
10402
+ const { lines, nextLine } = readStudioEventLogTail(key, cursor);
10403
+ cursor = nextLine;
10404
+ for (const line of lines) {
10405
+ if (opts.abortSignal.aborted) {
10406
+ return;
10407
+ }
10408
+ onLine(line);
10409
+ }
10410
+ };
10411
+ const timer = setInterval(() => {
10412
+ if (!opts.abortSignal.aborted) {
10413
+ tick();
10414
+ }
10415
+ }, pollMs);
10416
+ opts.abortSignal.addEventListener("abort", () => {
10417
+ clearInterval(timer);
10418
+ resolve();
10419
+ }, { once: true });
10420
+ tick();
10421
+ });
10422
+ }
10423
+
10241
10424
  // ../bitfab-plugin-lib/dist/studioUrl.js
10242
10425
  var DEFAULT_STUDIO_PATH = "/studio";
10243
10426
  function resolveStudioInitialPath(input) {
@@ -10247,12 +10430,12 @@ function resolveStudioInitialPath(input) {
10247
10430
  return DEFAULT_STUDIO_PATH;
10248
10431
  }
10249
10432
  function buildStudioSessionUrl(args) {
10250
- const { serviceUrl, path: path18, sessionId } = args;
10251
- if (path18.includes("session=")) {
10252
- return `${serviceUrl}${path18}`;
10433
+ const { serviceUrl, path: path19, sessionId } = args;
10434
+ if (path19.includes("session=")) {
10435
+ return `${serviceUrl}${path19}`;
10253
10436
  }
10254
- const separator = path18.includes("?") ? "&" : "?";
10255
- return `${serviceUrl}${path18}${separator}session=${encodeURIComponent(sessionId)}`;
10437
+ const separator = path19.includes("?") ? "&" : "?";
10438
+ return `${serviceUrl}${path19}${separator}session=${encodeURIComponent(sessionId)}`;
10256
10439
  }
10257
10440
 
10258
10441
  // ../bitfab-plugin-lib/dist/studioChannel.js
@@ -10266,10 +10449,13 @@ var DirectChannel = class {
10266
10449
  setApiKey(apiKey) {
10267
10450
  this.apiKey = apiKey;
10268
10451
  }
10269
- async openOrNavigate(path18, opts) {
10452
+ isPersistent() {
10453
+ return false;
10454
+ }
10455
+ async openOrNavigate(path19, opts) {
10270
10456
  if (!this.apiKey) {
10271
- const sessionId2 = opts?.sessionId ?? crypto6.randomUUID();
10272
- const initialPath = resolveStudioInitialPath(path18);
10457
+ const sessionId2 = opts?.sessionId ?? crypto7.randomUUID();
10458
+ const initialPath = resolveStudioInitialPath(path19);
10273
10459
  const url2 = buildStudioSessionUrl({
10274
10460
  serviceUrl: this.serviceUrl,
10275
10461
  path: initialPath,
@@ -10290,18 +10476,19 @@ var DirectChannel = class {
10290
10476
  serviceUrl: existing.serviceUrl,
10291
10477
  apiKey: this.apiKey,
10292
10478
  sessionId: existing.sessionId
10293
- }, path18);
10479
+ }, path19);
10294
10480
  if (!ack.acked) {
10295
10481
  throw new StudioNavigationError(ack.reason ?? "unknown", ack.blockedReason, existing.sessionId);
10296
10482
  }
10297
10483
  return { sessionId: existing.sessionId, opened: false };
10298
10484
  }
10299
- const sessionId = opts?.sessionId ?? crypto6.randomUUID();
10485
+ truncateStudioEventLog(resolveAgentSessionKey());
10486
+ const sessionId = opts?.sessionId ?? crypto7.randomUUID();
10300
10487
  const session = await createStudioSession({
10301
10488
  serviceUrl: this.serviceUrl,
10302
10489
  apiKey: this.apiKey,
10303
10490
  sessionId,
10304
- initialPath: path18,
10491
+ initialPath: path19,
10305
10492
  clientHeaders: opts?.clientHeaders
10306
10493
  });
10307
10494
  return { sessionId: session.sessionId, opened: true };
@@ -10365,17 +10552,21 @@ var DaemonChannel = class _DaemonChannel {
10365
10552
  setApiKey(apiKey) {
10366
10553
  this.apiKey = apiKey;
10367
10554
  }
10368
- async openOrNavigate(path18, opts) {
10555
+ isPersistent() {
10556
+ return true;
10557
+ }
10558
+ async openOrNavigate(path19, opts) {
10369
10559
  const openRes = await this.client.open({
10370
10560
  key: this.key,
10371
10561
  serviceUrl: this.serviceUrl,
10372
10562
  apiKey: this.apiKey,
10373
10563
  sessionId: opts?.sessionId,
10374
- initialPath: path18,
10375
- clientHeaders: opts?.clientHeaders
10564
+ initialPath: path19,
10565
+ clientHeaders: opts?.clientHeaders,
10566
+ focusTarget: opts?.focusTarget
10376
10567
  });
10377
10568
  if (openRes.reconnected) {
10378
- const navRes = await this.client.navigate(this.key, path18);
10569
+ const navRes = await this.client.navigate(this.key, path19);
10379
10570
  if (!navRes.acked) {
10380
10571
  this.client.destroy();
10381
10572
  throw new StudioNavigationError(navRes.reason ?? "unknown", void 0, openRes.sessionId);
@@ -10384,7 +10575,7 @@ var DaemonChannel = class _DaemonChannel {
10384
10575
  sessionId: openRes.sessionId,
10385
10576
  serviceUrl: this.serviceUrl,
10386
10577
  source: "daemon",
10387
- currentPath: path18
10578
+ currentPath: path19
10388
10579
  });
10389
10580
  return { sessionId: openRes.sessionId, opened: false };
10390
10581
  }
@@ -10392,7 +10583,7 @@ var DaemonChannel = class _DaemonChannel {
10392
10583
  sessionId: openRes.sessionId,
10393
10584
  serviceUrl: this.serviceUrl,
10394
10585
  source: "daemon",
10395
- currentPath: path18
10586
+ currentPath: path19
10396
10587
  });
10397
10588
  return { sessionId: openRes.sessionId, opened: true };
10398
10589
  }
@@ -10445,9 +10636,9 @@ function isDaemonEnabled() {
10445
10636
  function withDirectFallback(primary, apiKey, serviceUrl) {
10446
10637
  let active = primary;
10447
10638
  return {
10448
- async openOrNavigate(path18, opts) {
10639
+ async openOrNavigate(path19, opts) {
10449
10640
  try {
10450
- return await active.openOrNavigate(path18, opts);
10641
+ return await active.openOrNavigate(path19, opts);
10451
10642
  } catch (err) {
10452
10643
  if (err instanceof StudioNavigationError || active !== primary) {
10453
10644
  throw err;
@@ -10455,7 +10646,7 @@ function withDirectFallback(primary, apiKey, serviceUrl) {
10455
10646
  console.error(`daemon failed, falling back to direct: ${err instanceof Error ? err.message : String(err)}`);
10456
10647
  active.destroy();
10457
10648
  active = new DirectChannel(apiKey, serviceUrl);
10458
- return active.openOrNavigate(path18, opts);
10649
+ return active.openOrNavigate(path19, opts);
10459
10650
  }
10460
10651
  },
10461
10652
  async close(sessionId, message) {
@@ -10473,6 +10664,9 @@ function withDirectFallback(primary, apiKey, serviceUrl) {
10473
10664
  setApiKey(key) {
10474
10665
  active.setApiKey(key);
10475
10666
  },
10667
+ isPersistent() {
10668
+ return active.isPersistent();
10669
+ },
10476
10670
  destroy() {
10477
10671
  active.destroy();
10478
10672
  }
@@ -10503,12 +10697,12 @@ var semver3 = __toESM(require_semver2(), 1);
10503
10697
 
10504
10698
  // ../bitfab-plugin-lib/dist/installedSdk.js
10505
10699
  var semver2 = __toESM(require_semver2(), 1);
10506
- import fs11 from "fs";
10507
- import path10 from "path";
10700
+ import fs12 from "fs";
10701
+ import path11 from "path";
10508
10702
 
10509
10703
  // ../bitfab-plugin-lib/dist/workspaces.js
10510
- import fs10 from "fs";
10511
- import path9 from "path";
10704
+ import fs11 from "fs";
10705
+ import path10 from "path";
10512
10706
 
10513
10707
  // ../bitfab-plugin-lib/dist/analytics.js
10514
10708
  async function reportHandoff(apiKey, pluginVersion, platform2, body) {
@@ -10532,11 +10726,11 @@ async function reportHandoff(apiKey, pluginVersion, platform2, body) {
10532
10726
  }
10533
10727
 
10534
10728
  // ../bitfab-plugin-lib/dist/commands/openStudioTo.js
10535
- import crypto7 from "crypto";
10729
+ import crypto8 from "crypto";
10536
10730
 
10537
10731
  // ../bitfab-plugin-lib/dist/frontmostApp.js
10538
10732
  import { execFileSync as execFileSync5, spawn as spawn2 } from "child_process";
10539
- import os10 from "os";
10733
+ import os11 from "os";
10540
10734
  function findAncestorApp() {
10541
10735
  try {
10542
10736
  let pid = process.ppid;
@@ -10597,7 +10791,7 @@ function focusItermSession(sessionId) {
10597
10791
  spawnQuiet("osascript", ["-e", script]);
10598
10792
  }
10599
10793
  function getFrontmostApp() {
10600
- const platform2 = os10.platform();
10794
+ const platform2 = os11.platform();
10601
10795
  try {
10602
10796
  if (platform2 === "darwin") {
10603
10797
  return execFileSync5("osascript", [
@@ -10631,7 +10825,7 @@ function focusApp(identifier) {
10631
10825
  if (!identifier) {
10632
10826
  return;
10633
10827
  }
10634
- const platform2 = os10.platform();
10828
+ const platform2 = os11.platform();
10635
10829
  if (platform2 === "darwin") {
10636
10830
  spawnQuiet("osascript", [
10637
10831
  "-e",
@@ -10660,26 +10854,54 @@ function focusApp(identifier) {
10660
10854
  spawnQuiet("powershell.exe", ["-NoProfile", "-Command", script]);
10661
10855
  }
10662
10856
  }
10663
- function recordFocus() {
10664
- const platform2 = os10.platform();
10857
+ function captureFocusTarget() {
10858
+ const platform2 = os11.platform();
10665
10859
  if (platform2 === "linux") {
10666
10860
  const windowId = process.env.WINDOWID;
10667
10861
  if (windowId) {
10668
- return () => spawnQuiet("xdotool", ["windowactivate", windowId]);
10862
+ return { kind: "linux-window", windowId };
10669
10863
  }
10670
10864
  }
10671
10865
  if (platform2 === "darwin") {
10672
10866
  const app = findAncestorApp();
10673
10867
  if (app) {
10674
- const itermSessionId = process.env.ITERM_SESSION_ID ?? null;
10675
- return () => focusAncestorApp(app, itermSessionId);
10868
+ return {
10869
+ kind: "mac-ancestor",
10870
+ bundleName: app.bundleName,
10871
+ executableName: app.executableName,
10872
+ itermSessionId: process.env.ITERM_SESSION_ID ?? null
10873
+ };
10676
10874
  }
10677
10875
  }
10678
10876
  const frontmost = getFrontmostApp();
10679
- return () => focusApp(frontmost);
10877
+ return frontmost ? { kind: "frontmost", identifier: frontmost } : null;
10878
+ }
10879
+ function applyFocusTarget(target) {
10880
+ if (!target) {
10881
+ return;
10882
+ }
10883
+ switch (target.kind) {
10884
+ case "linux-window":
10885
+ spawnQuiet("xdotool", ["windowactivate", target.windowId]);
10886
+ return;
10887
+ case "mac-ancestor":
10888
+ focusAncestorApp({
10889
+ bundleName: target.bundleName,
10890
+ executableName: target.executableName
10891
+ }, target.itermSessionId);
10892
+ return;
10893
+ case "frontmost":
10894
+ focusApp(target.identifier);
10895
+ return;
10896
+ }
10680
10897
  }
10681
10898
 
10682
10899
  // ../bitfab-plugin-lib/dist/commands/openStudioTo.js
10900
+ function lineToAgentEvent(line) {
10901
+ const name = typeof line.event === "string" ? line.event : "";
10902
+ const type = name.includes(":") ? name : `studio:${name}`;
10903
+ return { id: "", type, data: line };
10904
+ }
10683
10905
  var WINDOW_CLOSE_GRACE_PERIOD_MS = 1e4;
10684
10906
  var LOGIN_TIMEOUT_MS = 10 * 60 * 1e3;
10685
10907
  function createEventSubscription(channel, sessionId, onEvent, restoreFocus) {
@@ -10746,23 +10968,92 @@ function createEventSubscription(channel, sessionId, onEvent, restoreFocus) {
10746
10968
  done: sub.done
10747
10969
  };
10748
10970
  }
10749
- async function openStudioTo(path18, opts = {}) {
10971
+ async function openStudioTo(path19, opts = {}) {
10750
10972
  const config2 = getConfig();
10751
10973
  const serviceUrl = config2.serviceUrl;
10752
10974
  const apiKey = !opts.forceLogin && hasCredentials() ? config2.apiKey : null;
10753
- const noop = () => {
10754
- };
10755
- const restoreFocus = recordFocus();
10975
+ const key = resolveAgentSessionKey();
10976
+ const focusTarget = captureFocusTarget();
10977
+ const restoreFocus = () => applyFocusTarget(focusTarget);
10756
10978
  const channel = await resolveChannel(apiKey ?? "", serviceUrl);
10979
+ const finish = (sessionId2, opened2, resolvedApiKey) => {
10980
+ const persistent = channel.isPersistent();
10981
+ const base = {
10982
+ sessionId: sessionId2,
10983
+ serviceUrl,
10984
+ apiKey: resolvedApiKey,
10985
+ opened: opened2,
10986
+ persistent
10987
+ };
10988
+ const wantsEvents = opts.tail || Boolean(opts.onEvent);
10989
+ if (persistent) {
10990
+ if (!wantsEvents || opts.tail) {
10991
+ if (!opts.tail) {
10992
+ restoreFocus();
10993
+ }
10994
+ return {
10995
+ ...base,
10996
+ abort: () => channel.destroy(),
10997
+ done: Promise.resolve()
10998
+ };
10999
+ }
11000
+ const subscription = createEventSubscription(channel, sessionId2, opts.onEvent ?? (() => {
11001
+ }), restoreFocus);
11002
+ return { ...base, ...subscription };
11003
+ }
11004
+ if (!wantsEvents) {
11005
+ restoreFocus();
11006
+ return {
11007
+ ...base,
11008
+ abort: () => channel.destroy(),
11009
+ done: Promise.resolve()
11010
+ };
11011
+ }
11012
+ if (tryClaimStudioMonitor()) {
11013
+ const userOnEvent2 = opts.onEvent ?? (() => {
11014
+ });
11015
+ const sink = (event) => {
11016
+ appendStudioEvent(key, studioEventToLogLine(event, sessionId2));
11017
+ userOnEvent2(event);
11018
+ };
11019
+ const subscription = createEventSubscription(channel, sessionId2, sink, restoreFocus);
11020
+ return {
11021
+ ...base,
11022
+ abort: subscription.abort,
11023
+ done: subscription.done.finally(() => releaseStudioMonitor())
11024
+ };
11025
+ }
11026
+ if (opts.tail) {
11027
+ channel.destroy();
11028
+ return { ...base, abort: () => {
11029
+ }, done: Promise.resolve() };
11030
+ }
11031
+ const userOnEvent = opts.onEvent ?? (() => {
11032
+ });
11033
+ const abortController = new AbortController();
11034
+ const done = tailStudioEvents(key, (line) => userOnEvent(lineToAgentEvent(line)), {
11035
+ fromLine: readStudioEventLogTail(key).nextLine,
11036
+ abortSignal: abortController.signal
11037
+ });
11038
+ return {
11039
+ ...base,
11040
+ abort: () => {
11041
+ abortController.abort();
11042
+ channel.destroy();
11043
+ },
11044
+ done
11045
+ };
11046
+ };
10757
11047
  if (!apiKey) {
10758
- const sessionId2 = crypto7.randomUUID();
10759
- const destination = opts.freshWindowPath ?? path18;
11048
+ const sessionId2 = crypto8.randomUUID();
11049
+ const destination = opts.freshWindowPath ?? path19;
10760
11050
  const destSeparator = destination.includes("?") ? "&" : "?";
10761
11051
  const redirectPath = `${destination}${destSeparator}session=${encodeURIComponent(sessionId2)}&pluginLogin=true`;
10762
11052
  const signInInitialPath = `/studio/sign-in?redirect_url=${encodeURIComponent(redirectPath)}&session=${encodeURIComponent(sessionId2)}`;
10763
11053
  await channel.openOrNavigate(signInInitialPath, {
10764
11054
  clientHeaders: opts.clientHeaders,
10765
- sessionId: sessionId2
11055
+ sessionId: sessionId2,
11056
+ focusTarget: focusTarget ?? void 0
10766
11057
  });
10767
11058
  const signInUrl = `${serviceUrl}${signInInitialPath}`;
10768
11059
  opts.onLoginRequired?.(sessionId2, signInUrl);
@@ -10790,37 +11081,13 @@ async function openStudioTo(path18, opts = {}) {
10790
11081
  saveCredentials(loginApiKey);
10791
11082
  opts.onAuthenticated?.(sessionId2);
10792
11083
  channel.setApiKey(loginApiKey);
10793
- if (opts.onEvent) {
10794
- const subscription2 = createEventSubscription(channel, sessionId2, opts.onEvent, restoreFocus);
10795
- return {
10796
- sessionId: sessionId2,
10797
- serviceUrl,
10798
- apiKey: loginApiKey,
10799
- opened: true,
10800
- ...subscription2
10801
- };
10802
- }
10803
- restoreFocus();
10804
- return {
10805
- sessionId: sessionId2,
10806
- serviceUrl,
10807
- apiKey: loginApiKey,
10808
- opened: true,
10809
- abort: noop,
10810
- done: Promise.resolve()
10811
- };
11084
+ return finish(sessionId2, true, loginApiKey);
10812
11085
  }
10813
- const { sessionId, opened } = await channel.openOrNavigate(path18, {
10814
- clientHeaders: opts.clientHeaders
11086
+ const { sessionId, opened } = await channel.openOrNavigate(path19, {
11087
+ clientHeaders: opts.clientHeaders,
11088
+ focusTarget: focusTarget ?? void 0
10815
11089
  });
10816
- const subscription = opts.onEvent ? createEventSubscription(channel, sessionId, opts.onEvent, restoreFocus) : { abort: () => channel.destroy(), done: Promise.resolve() };
10817
- return {
10818
- sessionId,
10819
- serviceUrl,
10820
- apiKey,
10821
- opened,
10822
- ...subscription
10823
- };
11090
+ return finish(sessionId, opened, apiKey);
10824
11091
  }
10825
11092
 
10826
11093
  // ../bitfab-plugin-lib/dist/commands/login.js
@@ -11683,10 +11950,10 @@ function mergeDefs(...defs) {
11683
11950
  function cloneDef(schema) {
11684
11951
  return mergeDefs(schema._zod.def);
11685
11952
  }
11686
- function getElementAtPath(obj, path18) {
11687
- if (!path18)
11953
+ function getElementAtPath(obj, path19) {
11954
+ if (!path19)
11688
11955
  return obj;
11689
- return path18.reduce((acc, key) => acc?.[key], obj);
11956
+ return path19.reduce((acc, key) => acc?.[key], obj);
11690
11957
  }
11691
11958
  function promiseAllObject(promisesObj) {
11692
11959
  const keys = Object.keys(promisesObj);
@@ -12069,11 +12336,11 @@ function aborted(x, startIndex = 0) {
12069
12336
  }
12070
12337
  return false;
12071
12338
  }
12072
- function prefixIssues(path18, issues) {
12339
+ function prefixIssues(path19, issues) {
12073
12340
  return issues.map((iss) => {
12074
12341
  var _a2;
12075
12342
  (_a2 = iss).path ?? (_a2.path = []);
12076
- iss.path.unshift(path18);
12343
+ iss.path.unshift(path19);
12077
12344
  return iss;
12078
12345
  });
12079
12346
  }
@@ -12256,7 +12523,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
12256
12523
  }
12257
12524
  function treeifyError(error48, mapper = (issue2) => issue2.message) {
12258
12525
  const result = { errors: [] };
12259
- const processError = (error49, path18 = []) => {
12526
+ const processError = (error49, path19 = []) => {
12260
12527
  var _a2, _b;
12261
12528
  for (const issue2 of error49.issues) {
12262
12529
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -12266,7 +12533,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
12266
12533
  } else if (issue2.code === "invalid_element") {
12267
12534
  processError({ issues: issue2.issues }, issue2.path);
12268
12535
  } else {
12269
- const fullpath = [...path18, ...issue2.path];
12536
+ const fullpath = [...path19, ...issue2.path];
12270
12537
  if (fullpath.length === 0) {
12271
12538
  result.errors.push(mapper(issue2));
12272
12539
  continue;
@@ -12298,8 +12565,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
12298
12565
  }
12299
12566
  function toDotPath(_path) {
12300
12567
  const segs = [];
12301
- const path18 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
12302
- for (const seg of path18) {
12568
+ const path19 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
12569
+ for (const seg of path19) {
12303
12570
  if (typeof seg === "number")
12304
12571
  segs.push(`[${seg}]`);
12305
12572
  else if (typeof seg === "symbol")
@@ -24276,13 +24543,13 @@ function resolveRef(ref, ctx) {
24276
24543
  if (!ref.startsWith("#")) {
24277
24544
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
24278
24545
  }
24279
- const path18 = ref.slice(1).split("/").filter(Boolean);
24280
- if (path18.length === 0) {
24546
+ const path19 = ref.slice(1).split("/").filter(Boolean);
24547
+ if (path19.length === 0) {
24281
24548
  return ctx.rootSchema;
24282
24549
  }
24283
24550
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
24284
- if (path18[0] === defsKey) {
24285
- const key = path18[1];
24551
+ if (path19[0] === defsKey) {
24552
+ const key = path19[1];
24286
24553
  if (!key || !ctx.defs[key]) {
24287
24554
  throw new Error(`Reference not found: ${ref}`);
24288
24555
  }
@@ -24719,18 +24986,18 @@ import { tmpdir } from "os";
24719
24986
  import { join } from "path";
24720
24987
 
24721
24988
  // ../bitfab-plugin-lib/dist/activePreviewSession.js
24722
- import fs12 from "fs";
24723
- import os11 from "os";
24724
- import path11 from "path";
24725
- var FILE_PATH = path11.join(os11.homedir(), ".config", "bitfab", "active-preview-session.json");
24989
+ import fs13 from "fs";
24990
+ import os12 from "os";
24991
+ import path12 from "path";
24992
+ var FILE_PATH = path12.join(os12.homedir(), ".config", "bitfab", "active-preview-session.json");
24726
24993
 
24727
24994
  // ../bitfab-plugin-lib/dist/commands/status.js
24728
- import path13 from "path";
24995
+ import path14 from "path";
24729
24996
 
24730
24997
  // ../bitfab-plugin-lib/dist/updates.js
24731
- import fs13 from "fs";
24732
- import os12 from "os";
24733
- import path12 from "path";
24998
+ import fs14 from "fs";
24999
+ import os13 from "os";
25000
+ import path13 from "path";
24734
25001
 
24735
25002
  // ../bitfab-plugin-lib/dist/commands/update.js
24736
25003
  import { execSync as execSync2 } from "child_process";
@@ -24739,15 +25006,15 @@ import { execSync as execSync2 } from "child_process";
24739
25006
  var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
24740
25007
 
24741
25008
  // ../bitfab-plugin-lib/dist/hooks/captureHook.js
24742
- import fs14 from "fs";
25009
+ import fs15 from "fs";
24743
25010
 
24744
25011
  // ../bitfab-plugin-lib/dist/hooks/userPromptSubmit.js
24745
- import fs15 from "fs";
25012
+ import fs16 from "fs";
24746
25013
 
24747
25014
  // ../bitfab-plugin-lib/dist/installScope.js
24748
- import fs16 from "fs";
24749
- import os13 from "os";
24750
- import path14 from "path";
25015
+ import fs17 from "fs";
25016
+ import os14 from "os";
25017
+ import path15 from "path";
24751
25018
 
24752
25019
  // ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/helpers/util.js
24753
25020
  var util;
@@ -25108,8 +25375,8 @@ function getErrorMap2() {
25108
25375
 
25109
25376
  // ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/helpers/parseUtil.js
25110
25377
  var makeIssue = (params) => {
25111
- const { data, path: path18, errorMaps, issueData } = params;
25112
- const fullPath = [...path18, ...issueData.path || []];
25378
+ const { data, path: path19, errorMaps, issueData } = params;
25379
+ const fullPath = [...path19, ...issueData.path || []];
25113
25380
  const fullIssue = {
25114
25381
  ...issueData,
25115
25382
  path: fullPath
@@ -25224,11 +25491,11 @@ var errorUtil;
25224
25491
 
25225
25492
  // ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/types.js
25226
25493
  var ParseInputLazyPath = class {
25227
- constructor(parent, value, path18, key) {
25494
+ constructor(parent, value, path19, key) {
25228
25495
  this._cachedPath = [];
25229
25496
  this.parent = parent;
25230
25497
  this.data = value;
25231
- this._path = path18;
25498
+ this._path = path19;
25232
25499
  this._key = key;
25233
25500
  }
25234
25501
  get path() {
@@ -30124,9 +30391,9 @@ import process3 from "process";
30124
30391
 
30125
30392
  // src/claude.ts
30126
30393
  import { spawnSync } from "child_process";
30127
- import fs17 from "fs";
30128
- import os14 from "os";
30129
- import path15 from "path";
30394
+ import fs18 from "fs";
30395
+ import os15 from "os";
30396
+ import path16 from "path";
30130
30397
  import * as p from "@clack/prompts";
30131
30398
  var REPO = "Project-White-Rabbit/bitfab-claude-plugin";
30132
30399
  var MARKETPLACE = "bitfab";
@@ -30146,7 +30413,7 @@ function runClaudeInstall() {
30146
30413
  }
30147
30414
  s.start("Enabling auto-updates");
30148
30415
  const changed = enableAutoUpdate(
30149
- path15.join(os14.homedir(), ".claude", "settings.json")
30416
+ path16.join(os15.homedir(), ".claude", "settings.json")
30150
30417
  );
30151
30418
  if (changed) {
30152
30419
  s.stop("Auto-updates enabled");
@@ -30237,8 +30504,8 @@ function runClaude(args) {
30237
30504
  }
30238
30505
  function enableAutoUpdate(settingsPath) {
30239
30506
  let settings = {};
30240
- if (fs17.existsSync(settingsPath)) {
30241
- const raw = fs17.readFileSync(settingsPath, "utf-8");
30507
+ if (fs18.existsSync(settingsPath)) {
30508
+ const raw = fs18.readFileSync(settingsPath, "utf-8");
30242
30509
  settings = raw.trim() === "" ? {} : JSON.parse(raw);
30243
30510
  }
30244
30511
  const marketplaces = settings.extraKnownMarketplaces ?? {};
@@ -30252,17 +30519,17 @@ function enableAutoUpdate(settingsPath) {
30252
30519
  autoUpdate: true
30253
30520
  };
30254
30521
  settings.extraKnownMarketplaces = marketplaces;
30255
- fs17.mkdirSync(path15.dirname(settingsPath), { recursive: true });
30256
- fs17.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
30522
+ fs18.mkdirSync(path16.dirname(settingsPath), { recursive: true });
30523
+ fs18.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
30257
30524
  `);
30258
30525
  return true;
30259
30526
  }
30260
30527
 
30261
30528
  // src/codex.ts
30262
30529
  import { spawnSync as spawnSync2 } from "child_process";
30263
- import fs18 from "fs";
30264
- import os15 from "os";
30265
- import path16 from "path";
30530
+ import fs19 from "fs";
30531
+ import os16 from "os";
30532
+ import path17 from "path";
30266
30533
  import * as p2 from "@clack/prompts";
30267
30534
  var REPO2 = "Project-White-Rabbit/bitfab-codex-plugin";
30268
30535
  var PLUGIN_KEY2 = "bitfab@bitfab";
@@ -30290,7 +30557,7 @@ function runCodexInstall() {
30290
30557
  }
30291
30558
  s.start("Enabling bitfab plugin in config");
30292
30559
  const changed = enableCodexPlugin(
30293
- path16.join(os15.homedir(), ".codex", "config.toml")
30560
+ path17.join(os16.homedir(), ".codex", "config.toml")
30294
30561
  );
30295
30562
  if (changed) {
30296
30563
  s.stop("Plugin enabled");
@@ -30353,8 +30620,8 @@ function runCodexUpdate(args, skipPermissions) {
30353
30620
  }
30354
30621
  function enableCodexPlugin(configPath) {
30355
30622
  let content = "";
30356
- if (fs18.existsSync(configPath)) {
30357
- content = fs18.readFileSync(configPath, "utf-8");
30623
+ if (fs19.existsSync(configPath)) {
30624
+ content = fs19.readFileSync(configPath, "utf-8");
30358
30625
  }
30359
30626
  const header = `[plugins."${PLUGIN_KEY2}"]`;
30360
30627
  const desiredSection = `${header}
@@ -30377,8 +30644,8 @@ enabled = true
30377
30644
 
30378
30645
  ${desiredSection}`;
30379
30646
  }
30380
- fs18.mkdirSync(path16.dirname(configPath), { recursive: true });
30381
- fs18.writeFileSync(configPath, next);
30647
+ fs19.mkdirSync(path17.dirname(configPath), { recursive: true });
30648
+ fs19.writeFileSync(configPath, next);
30382
30649
  return true;
30383
30650
  }
30384
30651
  function escapeRegex2(s) {
@@ -30387,7 +30654,7 @@ function escapeRegex2(s) {
30387
30654
 
30388
30655
  // src/cursor.ts
30389
30656
  import { spawn as spawn4, spawnSync as spawnSync3 } from "child_process";
30390
- import os16 from "os";
30657
+ import os17 from "os";
30391
30658
  import * as p3 from "@clack/prompts";
30392
30659
  var REPO3 = "Project-White-Rabbit/bitfab-cursor-plugin";
30393
30660
  var ADD_PLUGIN_CMD = `/add-plugin ${REPO3}`;
@@ -30459,13 +30726,13 @@ function copyToClipboard(text) {
30459
30726
  return result.status === 0;
30460
30727
  }
30461
30728
  function clipboardCommand() {
30462
- if (os16.platform() === "darwin") {
30729
+ if (os17.platform() === "darwin") {
30463
30730
  return { bin: "pbcopy", args: [] };
30464
30731
  }
30465
- if (os16.platform() === "linux") {
30732
+ if (os17.platform() === "linux") {
30466
30733
  return { bin: "xclip", args: ["-selection", "clipboard"] };
30467
30734
  }
30468
- if (os16.platform() === "win32") {
30735
+ if (os17.platform() === "win32") {
30469
30736
  return { bin: "clip", args: [] };
30470
30737
  }
30471
30738
  return null;
@@ -30489,12 +30756,12 @@ var platform = {
30489
30756
  };
30490
30757
 
30491
30758
  // src/version.ts
30492
- import fs19 from "fs";
30493
- import path17 from "path";
30759
+ import fs20 from "fs";
30760
+ import path18 from "path";
30494
30761
  import { fileURLToPath as fileURLToPath3 } from "url";
30495
30762
  var CLI_VERSION = JSON.parse(
30496
- fs19.readFileSync(
30497
- path17.join(path17.dirname(fileURLToPath3(import.meta.url)), "../package.json"),
30763
+ fs20.readFileSync(
30764
+ path18.join(path18.dirname(fileURLToPath3(import.meta.url)), "../package.json"),
30498
30765
  "utf-8"
30499
30766
  )
30500
30767
  ).version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitfab-cli",
3
- "version": "0.2.113",
3
+ "version": "0.2.114",
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",