bitfab-cli 0.2.83 → 0.2.85

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 +965 -834
  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(path15) {
5156
- let input = path15;
5155
+ function removeDotSegments(path18) {
5156
+ let input = path18;
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 [path15, query] = wsComponent.resourceName.split("?");
5356
- wsComponent.path = path15 && path15 !== "/" ? path15 : void 0;
5355
+ const [path18, query] = wsComponent.resourceName.split("?");
5356
+ wsComponent.path = path18 && path18 !== "/" ? path18 : void 0;
5357
5357
  wsComponent.query = query;
5358
5358
  wsComponent.resourceName = void 0;
5359
5359
  }
@@ -5505,10 +5505,10 @@ var require_fast_uri = __commonJS({
5505
5505
  function normalize(uri, options) {
5506
5506
  if (typeof uri === "string") {
5507
5507
  uri = /** @type {T} */
5508
- serialize(parse3(uri, options), options);
5508
+ serialize2(parse3(uri, options), options);
5509
5509
  } else if (typeof uri === "object") {
5510
5510
  uri = /** @type {T} */
5511
- parse3(serialize(uri, options), options);
5511
+ parse3(serialize2(uri, options), options);
5512
5512
  }
5513
5513
  return uri;
5514
5514
  }
@@ -5516,13 +5516,13 @@ var require_fast_uri = __commonJS({
5516
5516
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
5517
5517
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
5518
5518
  schemelessOptions.skipEscape = true;
5519
- return serialize(resolved, schemelessOptions);
5519
+ return serialize2(resolved, schemelessOptions);
5520
5520
  }
5521
5521
  function resolveComponent(base, relative, options, skipNormalization) {
5522
5522
  const target = {};
5523
5523
  if (!skipNormalization) {
5524
- base = parse3(serialize(base, options), options);
5525
- relative = parse3(serialize(relative, options), options);
5524
+ base = parse3(serialize2(base, options), options);
5525
+ relative = parse3(serialize2(relative, options), options);
5526
5526
  }
5527
5527
  options = options || {};
5528
5528
  if (!options.tolerant && relative.scheme) {
@@ -5574,19 +5574,19 @@ var require_fast_uri = __commonJS({
5574
5574
  function equal(uriA, uriB, options) {
5575
5575
  if (typeof uriA === "string") {
5576
5576
  uriA = unescape(uriA);
5577
- uriA = serialize(normalizeComponentEncoding(parse3(uriA, options), true), { ...options, skipEscape: true });
5577
+ uriA = serialize2(normalizeComponentEncoding(parse3(uriA, options), true), { ...options, skipEscape: true });
5578
5578
  } else if (typeof uriA === "object") {
5579
- uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true });
5579
+ uriA = serialize2(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true });
5580
5580
  }
5581
5581
  if (typeof uriB === "string") {
5582
5582
  uriB = unescape(uriB);
5583
- uriB = serialize(normalizeComponentEncoding(parse3(uriB, options), true), { ...options, skipEscape: true });
5583
+ uriB = serialize2(normalizeComponentEncoding(parse3(uriB, options), true), { ...options, skipEscape: true });
5584
5584
  } else if (typeof uriB === "object") {
5585
- uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true });
5585
+ uriB = serialize2(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true });
5586
5586
  }
5587
5587
  return uriA.toLowerCase() === uriB.toLowerCase();
5588
5588
  }
5589
- function serialize(cmpts, opts) {
5589
+ function serialize2(cmpts, opts) {
5590
5590
  const component = {
5591
5591
  host: cmpts.host,
5592
5592
  scheme: cmpts.scheme,
@@ -5742,7 +5742,7 @@ var require_fast_uri = __commonJS({
5742
5742
  resolve,
5743
5743
  resolveComponent,
5744
5744
  equal,
5745
- serialize,
5745
+ serialize: serialize2,
5746
5746
  parse: parse3
5747
5747
  };
5748
5748
  module.exports = fastUri;
@@ -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, fs18, exportName) {
8718
+ function addFormats(ajv, list, fs20, 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, fs18[f]);
8723
+ ajv.addFormat(f, fs20[f]);
8724
8724
  }
8725
8725
  module.exports = exports = formatsPlugin;
8726
8726
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -8731,6 +8731,10 @@ var require_dist = __commonJS({
8731
8731
  // src/index.ts
8732
8732
  import { cancel as cancel2 } from "@clack/prompts";
8733
8733
 
8734
+ // src/init.ts
8735
+ import { execSync as execSync3 } from "child_process";
8736
+ import * as p4 from "@clack/prompts";
8737
+
8734
8738
  // ../bitfab-plugin-lib/dist/errors.js
8735
8739
  var NotAuthenticatedError = class extends Error {
8736
8740
  constructor(message) {
@@ -8738,6 +8742,18 @@ var NotAuthenticatedError = class extends Error {
8738
8742
  this.name = "NotAuthenticatedError";
8739
8743
  }
8740
8744
  };
8745
+ var StudioNavigationError = class extends Error {
8746
+ reason;
8747
+ blockedReason;
8748
+ staleSessionId;
8749
+ constructor(reason, blockedReason, staleSessionId) {
8750
+ super(blockedReason ? `Studio navigation failed (${reason}): ${blockedReason}` : `Studio navigation failed: ${reason}`);
8751
+ this.reason = reason;
8752
+ this.blockedReason = blockedReason;
8753
+ this.staleSessionId = staleSessionId;
8754
+ this.name = "StudioNavigationError";
8755
+ }
8756
+ };
8741
8757
 
8742
8758
  // ../bitfab-plugin-lib/dist/studioRoutes.js
8743
8759
  var STUDIO_ROUTE_PATTERNS = [
@@ -8805,7 +8821,7 @@ async function pollAgentSessionEvents(opts) {
8805
8821
  const interval = opts.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
8806
8822
  const client = {
8807
8823
  serviceUrl: opts.serviceUrl,
8808
- apiKey: opts.apiKey,
8824
+ apiKey: opts.apiKey ?? "",
8809
8825
  sessionId: opts.sessionId
8810
8826
  };
8811
8827
  let since = opts.startCursor ?? null;
@@ -8816,8 +8832,9 @@ async function pollAgentSessionEvents(opts) {
8816
8832
  if (since) {
8817
8833
  url2.searchParams.set("since", since);
8818
8834
  }
8835
+ const authHeaders = opts.apiKey ? { Authorization: `Bearer ${opts.apiKey}` } : { [STUDIO_SESSION_SCOPE_HEADER]: STUDIO_SESSION_SCOPE_VALUE };
8819
8836
  const res = await fetch(url2.toString(), {
8820
- headers: { Authorization: `Bearer ${opts.apiKey}` },
8837
+ headers: authHeaders,
8821
8838
  signal: opts.abortSignal
8822
8839
  });
8823
8840
  if (res.ok) {
@@ -8857,124 +8874,13 @@ async function pollAgentSessionEvents(opts) {
8857
8874
  });
8858
8875
  }
8859
8876
  }
8860
- async function pollLoginEvents(opts) {
8861
- const interval = opts.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
8862
- let since = null;
8863
- while (!opts.abortSignal.aborted) {
8864
- try {
8865
- const url2 = new URL(`${opts.serviceUrl}/api/studio/sessions/poll`);
8866
- url2.searchParams.set("session", opts.sessionId);
8867
- if (since) {
8868
- url2.searchParams.set("since", since);
8869
- }
8870
- const res = await fetch(url2.toString(), {
8871
- signal: opts.abortSignal
8872
- });
8873
- if (res.ok) {
8874
- const body = await res.json();
8875
- for (const event of body.events) {
8876
- since = event.id;
8877
- if (event.type === "studio:authenticated" && typeof event.data.token === "string") {
8878
- return event.data.token;
8879
- }
8880
- }
8881
- }
8882
- } catch {
8883
- if (opts.abortSignal.aborted) {
8884
- break;
8885
- }
8886
- }
8887
- await new Promise((resolve) => {
8888
- const onAbort = () => {
8889
- clearTimeout(timer);
8890
- resolve();
8891
- };
8892
- const timer = setTimeout(() => {
8893
- opts.abortSignal.removeEventListener("abort", onAbort);
8894
- resolve();
8895
- }, interval);
8896
- opts.abortSignal.addEventListener("abort", onAbort, { once: true });
8897
- });
8898
- }
8899
- throw new Error("Login polling aborted");
8900
- }
8901
8877
  var STUDIO_SESSION_SCOPE_HEADER = "x-bitfab-auth-scope";
8902
8878
  var STUDIO_SESSION_SCOPE_VALUE = "session";
8903
- async function requestSessionReauth(opts) {
8904
- try {
8905
- const res = await fetch(`${opts.serviceUrl}/api/studio/events`, {
8906
- method: "POST",
8907
- headers: {
8908
- "Content-Type": "application/json",
8909
- [STUDIO_SESSION_SCOPE_HEADER]: STUDIO_SESSION_SCOPE_VALUE
8910
- },
8911
- body: JSON.stringify({
8912
- session: opts.sessionId,
8913
- type: "agent:request-reauth",
8914
- data: { redirectPath: opts.redirectPath }
8915
- })
8916
- });
8917
- if (!res.ok) {
8918
- return { ok: false };
8919
- }
8920
- const body = await res.json();
8921
- return {
8922
- ok: true,
8923
- pushedEventId: body.id,
8924
- signInUrl: `${opts.serviceUrl}${body.signInPath}`
8925
- };
8926
- } catch {
8927
- return { ok: false };
8928
- }
8929
- }
8930
- async function pollSessionAuthEvents(opts) {
8931
- const interval = opts.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
8932
- let since = opts.startCursor ?? null;
8933
- while (!opts.abortSignal.aborted) {
8934
- try {
8935
- const url2 = new URL(`${opts.serviceUrl}/api/studio/events`);
8936
- url2.searchParams.set("session", opts.sessionId);
8937
- if (since) {
8938
- url2.searchParams.set("since", since);
8939
- }
8940
- const res = await fetch(url2.toString(), {
8941
- headers: {
8942
- [STUDIO_SESSION_SCOPE_HEADER]: STUDIO_SESSION_SCOPE_VALUE
8943
- },
8944
- signal: opts.abortSignal
8945
- });
8946
- if (res.ok) {
8947
- const body = await res.json();
8948
- for (const event of body.events) {
8949
- since = event.id;
8950
- opts.onEvent(event);
8951
- }
8952
- if (body.cursor) {
8953
- since = body.cursor;
8954
- }
8955
- }
8956
- } catch {
8957
- if (opts.abortSignal.aborted) {
8958
- return;
8959
- }
8960
- }
8961
- await new Promise((resolve) => {
8962
- const onAbort = () => {
8963
- clearTimeout(timer);
8964
- resolve();
8965
- };
8966
- const timer = setTimeout(() => {
8967
- opts.abortSignal.removeEventListener("abort", onAbort);
8968
- resolve();
8969
- }, interval);
8970
- opts.abortSignal.addEventListener("abort", onAbort, { once: true });
8971
- });
8972
- }
8973
- }
8974
8879
  async function fetchStreamTip(client) {
8975
8880
  const PAGE_LIMIT = 500;
8976
8881
  const MAX_PAGES = 10;
8977
8882
  let tip;
8883
+ const authHeaders = client.apiKey ? { Authorization: `Bearer ${client.apiKey}` } : { [STUDIO_SESSION_SCOPE_HEADER]: STUDIO_SESSION_SCOPE_VALUE };
8978
8884
  try {
8979
8885
  for (let page = 0; page < MAX_PAGES; page++) {
8980
8886
  const url2 = new URL(`${client.serviceUrl}/api/studio/events`);
@@ -8984,7 +8890,7 @@ async function fetchStreamTip(client) {
8984
8890
  url2.searchParams.set("since", tip);
8985
8891
  }
8986
8892
  const res = await fetch(url2.toString(), {
8987
- headers: { Authorization: `Bearer ${client.apiKey}` }
8893
+ headers: authHeaders
8988
8894
  });
8989
8895
  if (!res.ok) {
8990
8896
  return tip;
@@ -9004,18 +8910,24 @@ async function fetchStreamTip(client) {
9004
8910
  }
9005
8911
  return tip;
9006
8912
  }
9007
- async function navigateStudio(client, path15) {
9008
- if (!isValidStudioRoute(path15)) {
9009
- throw new Error(`Studio route not allowed: ${path15}`);
8913
+ async function closeStudio(client, message) {
8914
+ return pushAgentSessionEvent(client, {
8915
+ type: "agent:close-studio",
8916
+ data: message ? { message } : {}
8917
+ });
8918
+ }
8919
+ async function navigateStudio(client, path18) {
8920
+ if (!isValidStudioRoute(path18)) {
8921
+ throw new Error(`Studio route not allowed: ${path18}`);
9010
8922
  }
9011
8923
  return pushAgentSessionEvent(client, {
9012
8924
  type: "agent:navigate",
9013
- data: { path: path15 }
8925
+ data: { path: path18 }
9014
8926
  });
9015
8927
  }
9016
8928
  var STUDIO_NAVIGATE_ACK_TIMEOUT_MS = 12e3;
9017
8929
  var PING_PONG_TIMEOUT_MS = 5e3;
9018
- function awaitNavigateAck(client, path15, ackTimeoutMs, startCursor) {
8930
+ function awaitNavigateAck(client, path18, ackTimeoutMs, startCursor) {
9019
8931
  const abortController = new AbortController();
9020
8932
  let timer = null;
9021
8933
  const result = new Promise((resolve) => {
@@ -9023,7 +8935,7 @@ function awaitNavigateAck(client, path15, ackTimeoutMs, startCursor) {
9023
8935
  resolve({ acked: false, reason: "timeout" });
9024
8936
  abortController.abort();
9025
8937
  }, ackTimeoutMs);
9026
- const pathOnly = path15.split("?")[0];
8938
+ const pathOnly = path18.split("?")[0];
9027
8939
  pollAgentSessionEvents({
9028
8940
  serviceUrl: client.serviceUrl,
9029
8941
  apiKey: client.apiKey,
@@ -9109,18 +9021,18 @@ function awaitPong(client, pingTs) {
9109
9021
  });
9110
9022
  });
9111
9023
  }
9112
- async function navigateStudioAndAwaitAck(client, path15, opts = {}) {
9024
+ async function navigateStudioAndAwaitAck(client, path18, opts = {}) {
9113
9025
  const ackTimeoutMs = opts.ackTimeoutMs ?? STUDIO_NAVIGATE_ACK_TIMEOUT_MS;
9114
- if (!isValidStudioRoute(path15)) {
9026
+ if (!isValidStudioRoute(path18)) {
9115
9027
  return { acked: false, reason: "invalid-route" };
9116
9028
  }
9117
9029
  const startCursor = await fetchStreamTip(client);
9118
9030
  try {
9119
- await navigateStudio(client, path15);
9031
+ await navigateStudio(client, path18);
9120
9032
  } catch {
9121
9033
  return { acked: false, reason: "push-failed" };
9122
9034
  }
9123
- const result = await awaitNavigateAck(client, path15, ackTimeoutMs, startCursor);
9035
+ const result = await awaitNavigateAck(client, path18, ackTimeoutMs, startCursor);
9124
9036
  if (result.acked || result.reason !== "timeout") {
9125
9037
  return result;
9126
9038
  }
@@ -9181,9 +9093,9 @@ var GLOBAL_CONFIG_FILE = path3.join(GLOBAL_CONFIG_DIR, "config.json");
9181
9093
  var GLOBAL_CREDENTIALS_FILE = path3.join(GLOBAL_CONFIG_DIR, "credentials.json");
9182
9094
  var PROJECT_CONFIG_RELATIVE = path3.join(".bitfab", "config.local.json");
9183
9095
  var PROJECT_CREDENTIALS_RELATIVE = path3.join(".bitfab", "credentials.local.json");
9184
- function readJsonFile(filePath2) {
9096
+ function readJsonFile(filePath) {
9185
9097
  try {
9186
- const content = fs3.readFileSync(filePath2, "utf-8");
9098
+ const content = fs3.readFileSync(filePath, "utf-8");
9187
9099
  return JSON.parse(content);
9188
9100
  } catch {
9189
9101
  return null;
@@ -9204,18 +9116,18 @@ function findProjectFile(relativePath, startDir = process.cwd()) {
9204
9116
  }
9205
9117
  }
9206
9118
  function getProjectConfigData() {
9207
- const filePath2 = findProjectFile(PROJECT_CONFIG_RELATIVE);
9208
- if (!filePath2) {
9119
+ const filePath = findProjectFile(PROJECT_CONFIG_RELATIVE);
9120
+ if (!filePath) {
9209
9121
  return null;
9210
9122
  }
9211
- return readJsonFile(filePath2);
9123
+ return readJsonFile(filePath);
9212
9124
  }
9213
9125
  function getProjectCredentialsData() {
9214
- const filePath2 = findProjectFile(PROJECT_CREDENTIALS_RELATIVE);
9215
- if (!filePath2) {
9126
+ const filePath = findProjectFile(PROJECT_CREDENTIALS_RELATIVE);
9127
+ if (!filePath) {
9216
9128
  return null;
9217
9129
  }
9218
- return readJsonFile(filePath2) ?? {};
9130
+ return readJsonFile(filePath) ?? {};
9219
9131
  }
9220
9132
  function getConfigData() {
9221
9133
  return readJsonFile(GLOBAL_CONFIG_FILE) ?? {};
@@ -9345,10 +9257,34 @@ function buildBitfabRequestHeaders(apiKey, pluginVersion, platform2) {
9345
9257
 
9346
9258
  // ../bitfab-plugin-lib/dist/activeStudioSession.js
9347
9259
  import { execFileSync as execFileSync2 } from "child_process";
9260
+ import crypto4 from "crypto";
9261
+ import fs7 from "fs";
9262
+ import os7 from "os";
9263
+ import path6 from "path";
9264
+
9265
+ // ../bitfab-plugin-lib/dist/agentSessionKey.js
9348
9266
  import crypto3 from "crypto";
9349
9267
  import fs6 from "fs";
9350
9268
  import os6 from "os";
9351
9269
  import path5 from "path";
9270
+ var FALLBACK_KEY_PATH = path5.join(os6.homedir(), ".config", "bitfab", "agent-session-key");
9271
+ function readOrCreateFallbackKey() {
9272
+ try {
9273
+ const existing = fs6.readFileSync(FALLBACK_KEY_PATH, "utf-8").trim();
9274
+ if (existing) {
9275
+ return existing;
9276
+ }
9277
+ } catch {
9278
+ }
9279
+ const key = crypto3.randomUUID();
9280
+ fs6.mkdirSync(path5.dirname(FALLBACK_KEY_PATH), { recursive: true });
9281
+ fs6.writeFileSync(FALLBACK_KEY_PATH, `${key}
9282
+ `);
9283
+ return key;
9284
+ }
9285
+ function resolveAgentSessionKey() {
9286
+ return process.env.CLAUDE_CODE_SESSION_ID ?? process.env.CURSOR_SESSION_ID ?? process.env.CODEX_THREAD_ID ?? readOrCreateFallbackKey();
9287
+ }
9352
9288
 
9353
9289
  // ../bitfab-plugin-lib/dist/getAgentSessionId.js
9354
9290
  function getAgentSessionId() {
@@ -9356,224 +9292,181 @@ function getAgentSessionId() {
9356
9292
  }
9357
9293
 
9358
9294
  // ../bitfab-plugin-lib/dist/activeStudioSession.js
9295
+ var SESSIONS_DIR = path6.join(os7.homedir(), ".config", "bitfab", "sessions");
9359
9296
  function shortHash(input) {
9360
- return crypto3.createHash("sha256").update(input).digest("hex").slice(0, 16);
9297
+ return crypto4.createHash("sha256").update(input).digest("hex").slice(0, 16);
9361
9298
  }
9362
- function sessionScope() {
9363
- return shortHash(getAgentSessionId() ?? process.cwd());
9299
+ function sessionFilePathForKey(key) {
9300
+ return path6.join(SESSIONS_DIR, `${shortHash(key)}.json`);
9364
9301
  }
9365
- function filePath() {
9366
- return path5.join(os6.homedir(), ".config", "bitfab", `active-studio-session.${sessionScope()}.json`);
9302
+ function sessionFilePath() {
9303
+ return sessionFilePathForKey(resolveAgentSessionKey());
9367
9304
  }
9368
- function writeRecord(record2) {
9369
- const target = filePath();
9370
- fs6.mkdirSync(path5.dirname(target), { recursive: true });
9305
+ function writeStateFile(target, state) {
9306
+ fs7.mkdirSync(path6.dirname(target), { recursive: true });
9371
9307
  const tmp = `${target}.${process.pid}.tmp`;
9372
- fs6.writeFileSync(tmp, `${JSON.stringify(record2, null, 2)}
9308
+ fs7.writeFileSync(tmp, `${JSON.stringify(state, null, 2)}
9373
9309
  `);
9374
- fs6.renameSync(tmp, target);
9375
- }
9376
- function writeActiveStudioSession(session) {
9377
- writeRecord({
9378
- ...session,
9379
- pid: process.pid,
9380
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
9381
- });
9310
+ fs7.renameSync(tmp, target);
9382
9311
  }
9383
- function isProcessAlive(pid) {
9312
+ function readStateFile(filePath) {
9384
9313
  try {
9385
- process.kill(pid, 0);
9386
- return true;
9387
- } catch (err) {
9388
- return err.code === "EPERM";
9389
- }
9390
- }
9391
- function processStartToken(pid) {
9392
- try {
9393
- const out = execFileSync2("ps", ["-o", "lstart=", "-p", String(pid)], {
9394
- encoding: "utf-8",
9395
- stdio: ["ignore", "pipe", "ignore"],
9396
- // Bound it: a local `ps -p` is instant, but with no timeout a wedged `ps`
9397
- // (severe load, a stuck process table) could block the monitor claim. On
9398
- // timeout this throws, we return null, and the caller degrades to a plain
9399
- // liveness check — never a hang.
9400
- timeout: 2e3
9401
- }).trim();
9402
- return out || null;
9314
+ const raw = fs7.readFileSync(filePath, "utf-8");
9315
+ const parsed = JSON.parse(raw);
9316
+ if (typeof parsed.sessionId !== "string" || typeof parsed.serviceUrl !== "string") {
9317
+ return null;
9318
+ }
9319
+ return parsed;
9403
9320
  } catch {
9404
9321
  return null;
9405
9322
  }
9406
9323
  }
9407
- function clearActiveStudioSession(sessionId, opts = {}) {
9408
- try {
9409
- const current = readActiveStudioSessionRaw();
9410
- if (!current || current.sessionId !== sessionId) {
9411
- return false;
9412
- }
9413
- if (!opts.force && current.pid !== process.pid && isProcessAlive(current.pid)) {
9414
- return false;
9415
- }
9416
- fs6.unlinkSync(filePath());
9417
- return true;
9418
- } catch {
9419
- return false;
9420
- }
9324
+ function legacySessionScope() {
9325
+ return shortHash(getAgentSessionId() ?? process.cwd());
9421
9326
  }
9422
- function readActiveStudioSessionRaw() {
9327
+ function legacyFilePath() {
9328
+ return path6.join(os7.homedir(), ".config", "bitfab", `active-studio-session.${legacySessionScope()}.json`);
9329
+ }
9330
+ function readLegacySession() {
9423
9331
  try {
9424
- const raw = fs6.readFileSync(filePath(), "utf-8");
9332
+ const raw = fs7.readFileSync(legacyFilePath(), "utf-8");
9425
9333
  const parsed = JSON.parse(raw);
9426
9334
  if (parsed.closedAt != null || parsed.endedAt != null) {
9427
9335
  return null;
9428
9336
  }
9429
- if (typeof parsed.sessionId === "string" && typeof parsed.serviceUrl === "string" && typeof parsed.pid === "number" && typeof parsed.startedAt === "string") {
9430
- return {
9431
- sessionId: parsed.sessionId,
9432
- serviceUrl: parsed.serviceUrl,
9433
- pid: parsed.pid,
9434
- startedAt: parsed.startedAt,
9435
- ...typeof parsed.windowPid === "number" ? { windowPid: parsed.windowPid } : {}
9436
- };
9337
+ if (typeof parsed.sessionId !== "string" || typeof parsed.serviceUrl !== "string" || typeof parsed.pid !== "number" || typeof parsed.startedAt !== "string") {
9338
+ return null;
9437
9339
  }
9438
- return null;
9340
+ const legacy = parsed;
9341
+ return {
9342
+ sessionId: legacy.sessionId,
9343
+ serviceUrl: legacy.serviceUrl,
9344
+ windowPid: legacy.windowPid ?? null,
9345
+ pollerPid: legacy.pid,
9346
+ source: "direct",
9347
+ openedAt: legacy.startedAt,
9348
+ currentPath: null,
9349
+ authenticated: true,
9350
+ windowState: "open",
9351
+ agentIntent: "open",
9352
+ lastBrowserPingAt: null,
9353
+ browserConnected: false,
9354
+ lastEventAt: null,
9355
+ lastEventCursor: null,
9356
+ agentSessionKey: null
9357
+ };
9439
9358
  } catch {
9440
9359
  return null;
9441
9360
  }
9442
9361
  }
9443
- function readActiveStudioSession() {
9444
- return readActiveStudioSessionRaw();
9362
+ function writeActiveStudioSession(init) {
9363
+ const state = {
9364
+ sessionId: init.sessionId,
9365
+ serviceUrl: init.serviceUrl,
9366
+ windowPid: init.windowPid ?? null,
9367
+ pollerPid: process.pid,
9368
+ source: init.source ?? "direct",
9369
+ openedAt: (/* @__PURE__ */ new Date()).toISOString(),
9370
+ currentPath: init.currentPath ?? null,
9371
+ authenticated: init.authenticated ?? true,
9372
+ windowState: "open",
9373
+ agentIntent: "open",
9374
+ lastBrowserPingAt: null,
9375
+ browserConnected: false,
9376
+ lastEventAt: null,
9377
+ lastEventCursor: null,
9378
+ agentSessionKey: null
9379
+ };
9380
+ writeStateFile(sessionFilePath(), state);
9445
9381
  }
9446
- function monitorLockPath() {
9447
- return path5.join(os6.homedir(), ".config", "bitfab", `active-studio-session.${sessionScope()}.monitor.lock`);
9382
+ function readActiveStudioSession() {
9383
+ return readStateFile(sessionFilePath()) ?? readLegacySession();
9448
9384
  }
9449
- function tryClaimStudioMonitor() {
9450
- const lock = monitorLockPath();
9451
- fs6.mkdirSync(path5.dirname(lock), { recursive: true });
9452
- for (let attempt = 0; attempt < 10; attempt++) {
9453
- try {
9454
- const fd = fs6.openSync(lock, "wx");
9455
- fs6.writeSync(fd, monitorLockBody(process.pid));
9456
- fs6.closeSync(fd);
9457
- stampMonitorPid();
9458
- return true;
9459
- } catch (err) {
9460
- if (err.code !== "EEXIST") {
9461
- throw err;
9462
- }
9463
- const owner = readLockOwner(lock);
9464
- if (owner?.pid === process.pid) {
9465
- return true;
9466
- }
9467
- if (owner && isMonitorOwnerLive(owner)) {
9468
- return false;
9469
- }
9470
- try {
9471
- fs6.unlinkSync(lock);
9472
- } catch {
9473
- }
9474
- }
9385
+ function updateActiveStudioSession(updates) {
9386
+ const current = readStateFile(sessionFilePath());
9387
+ if (!current) {
9388
+ return null;
9475
9389
  }
9476
- stampMonitorPid();
9477
- return true;
9390
+ const updated = { ...current, ...updates };
9391
+ writeStateFile(sessionFilePath(), updated);
9392
+ return updated;
9478
9393
  }
9479
- function claimStudioMonitor() {
9480
- try {
9481
- const lock = monitorLockPath();
9482
- fs6.mkdirSync(path5.dirname(lock), { recursive: true });
9483
- fs6.writeFileSync(lock, monitorLockBody(process.pid));
9484
- } catch {
9394
+
9395
+ // ../bitfab-plugin-lib/dist/parseArgs.js
9396
+ function formatPositionalLabel(spec) {
9397
+ const tag = spec.format && spec.format !== "string" ? `:${spec.format}` : "";
9398
+ if (spec.variadic) {
9399
+ return spec.required !== false ? `<${spec.name}${tag}> [<${spec.name}${tag}>...]` : `[<${spec.name}${tag}>...]`;
9485
9400
  }
9486
- stampMonitorPid();
9401
+ return spec.required !== false ? `<${spec.name}${tag}>` : `[${spec.name}${tag}]`;
9487
9402
  }
9488
- function releaseStudioMonitor() {
9489
- try {
9490
- const lock = monitorLockPath();
9491
- if (readLockOwner(lock)?.pid === process.pid) {
9492
- fs6.unlinkSync(lock);
9493
- }
9494
- } catch {
9403
+ function formatFlagLabel(spec) {
9404
+ if (spec.boolean) {
9405
+ return `[--${spec.name}]`;
9495
9406
  }
9407
+ const tag = spec.format && spec.format !== "string" ? `:${spec.format}` : "";
9408
+ const valueHint = spec.format === "number" ? " N" : ` <${spec.name}${tag}>`;
9409
+ const defaultHint = spec.default !== void 0 ? ` (default: ${spec.default})` : "";
9410
+ return `[--${spec.name}${valueHint}${defaultHint}]`;
9496
9411
  }
9497
- function monitorLockBody(pid) {
9498
- return `${pid}
9499
- ${processStartToken(pid) ?? ""}`;
9412
+ function buildUsage(command, positional, flags) {
9413
+ const parts = [
9414
+ `Usage: ${command}`,
9415
+ ...positional.map(formatPositionalLabel),
9416
+ ...(flags ?? []).map(formatFlagLabel)
9417
+ ];
9418
+ return parts.join(" ");
9500
9419
  }
9501
- function isMonitorOwnerLive(owner) {
9502
- if (!isProcessAlive(owner.pid)) {
9503
- return false;
9504
- }
9505
- if (!owner.start) {
9506
- return true;
9420
+ function buildHelp(command, positional, flags, description) {
9421
+ const lines = [buildUsage(command, positional, flags)];
9422
+ if (description) {
9423
+ lines.push("", description);
9507
9424
  }
9508
- const current = processStartToken(owner.pid);
9509
- if (current === null) {
9510
- return true;
9425
+ const describedArgs = positional.filter((s) => s.description);
9426
+ const flagSpecs = flags ?? [];
9427
+ const labelWidth = Math.max(0, ...describedArgs.map((s) => s.name.length), ...flagSpecs.map((s) => s.name.length + 2));
9428
+ if (describedArgs.length > 0) {
9429
+ lines.push("", "Arguments:");
9430
+ for (const spec of describedArgs) {
9431
+ lines.push(` ${spec.name.padEnd(labelWidth)} ${spec.description}`);
9432
+ }
9511
9433
  }
9512
- return current === owner.start;
9513
- }
9514
- function readLockOwner(lock) {
9515
- try {
9516
- const [pidLine = "", ...rest] = fs6.readFileSync(lock, "utf-8").split("\n");
9517
- const pid = Number.parseInt(pidLine.trim(), 10);
9518
- if (!Number.isInteger(pid)) {
9519
- return null;
9434
+ if (flagSpecs.length > 0) {
9435
+ lines.push("", "Flags:");
9436
+ for (const spec of flagSpecs) {
9437
+ const detail = [
9438
+ spec.description,
9439
+ spec.default !== void 0 ? `(default: ${spec.default})` : void 0
9440
+ ].filter(Boolean).join(" ");
9441
+ lines.push(` ${`--${spec.name}`.padEnd(labelWidth)} ${detail}`.trimEnd());
9520
9442
  }
9521
- return { pid, start: rest.join("\n").trim() };
9522
- } catch {
9523
- return null;
9524
9443
  }
9444
+ return lines.join("\n");
9525
9445
  }
9526
- function stampMonitorPid() {
9527
- const current = readActiveStudioSessionRaw();
9528
- if (current) {
9529
- writeRecord({ ...current, pid: process.pid });
9530
- }
9446
+ function helpRequested(raw) {
9447
+ return raw.includes("-h") || raw.includes("--help");
9531
9448
  }
9532
-
9533
- // ../bitfab-plugin-lib/dist/output.js
9534
- function blockingNote(until) {
9535
- return `BLOCKING: this command does not exit until ${until}. Agents: run it in the background and read its stdout incrementally. A foreground call hangs the conversation until the process exits.`;
9449
+ function printHelpIfRequested(opts) {
9450
+ const argv = opts.argv ?? process.argv;
9451
+ if (!helpRequested(argv.slice(2))) {
9452
+ return;
9453
+ }
9454
+ console.log(buildHelp(extractCommandName(argv), opts.positional ?? [], opts.flags, opts.description));
9455
+ process.exit(0);
9536
9456
  }
9537
- function printBlockingNotice(until) {
9538
- console.error(blockingNote(until));
9457
+ function extractCommandName(argv) {
9458
+ return argv[1]?.replace(/.*\//, "").replace(/\.[jt]s$/, "") ?? "command";
9539
9459
  }
9540
9460
 
9541
- // ../bitfab-plugin-lib/dist/studioTeardown.js
9542
- var WINDOW_CLOSE_GRACE_PERIOD_MS = 1e4;
9543
- function createWindowCloseArbiter(handlers, graceMs = WINDOW_CLOSE_GRACE_PERIOD_MS) {
9544
- let graceTimer = null;
9545
- const cancel3 = () => {
9546
- if (graceTimer) {
9547
- clearTimeout(graceTimer);
9548
- graceTimer = null;
9549
- }
9550
- };
9551
- return {
9552
- handleEvent(event) {
9553
- if (event.type === "studio:window-closed") {
9554
- cancel3();
9555
- graceTimer = setTimeout(() => {
9556
- graceTimer = null;
9557
- handlers.onConfirmed(event);
9558
- }, graceMs);
9559
- return;
9560
- }
9561
- if (event.type === "studio:session-started" && graceTimer) {
9562
- cancel3();
9563
- handlers.onRefuted?.();
9564
- }
9565
- },
9566
- hasPendingCandidate: () => graceTimer != null,
9567
- cancel: cancel3
9568
- };
9569
- }
9461
+ // ../bitfab-plugin-lib/dist/studioChannel.js
9462
+ import crypto6 from "crypto";
9570
9463
 
9571
9464
  // ../bitfab-plugin-lib/dist/browser.js
9572
9465
  import { execSync, spawn } from "child_process";
9573
- import fs7 from "fs";
9574
- import os7 from "os";
9466
+ import fs8 from "fs";
9467
+ import os8 from "os";
9575
9468
  function getChromiumBrowsers() {
9576
- const platform2 = os7.platform();
9469
+ const platform2 = os8.platform();
9577
9470
  if (platform2 === "darwin") {
9578
9471
  return [
9579
9472
  {
@@ -9661,7 +9554,7 @@ function getChromiumBrowsers() {
9661
9554
  function findExistingBinary(paths) {
9662
9555
  for (const candidate of paths) {
9663
9556
  if (candidate.includes("/") || candidate.includes("\\")) {
9664
- if (fs7.existsSync(candidate)) {
9557
+ if (fs8.existsSync(candidate)) {
9665
9558
  return candidate;
9666
9559
  }
9667
9560
  } else {
@@ -9675,10 +9568,10 @@ function findExistingBinary(paths) {
9675
9568
  return null;
9676
9569
  }
9677
9570
  function getDefaultBrowserId() {
9678
- const platform2 = os7.platform();
9571
+ const platform2 = os8.platform();
9679
9572
  try {
9680
9573
  if (platform2 === "darwin") {
9681
- const plistPath = `${os7.homedir()}/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist`;
9574
+ const plistPath = `${os8.homedir()}/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist`;
9682
9575
  const json2 = execSync(`plutil -convert json -o - "${plistPath}"`, {
9683
9576
  stdio: ["ignore", "pipe", "ignore"],
9684
9577
  encoding: "utf-8",
@@ -9719,7 +9612,7 @@ function matchChromiumBrowser(defaultId, browsers) {
9719
9612
  var SCREEN_FRACTION = 0.95;
9720
9613
  function getWorkArea() {
9721
9614
  try {
9722
- const platform2 = os7.platform();
9615
+ const platform2 = os8.platform();
9723
9616
  if (platform2 === "darwin") {
9724
9617
  const out = execSync(`osascript -l JavaScript -e 'ObjC.import("AppKit"); const s = $.NSScreen.mainScreen; const f = s.frame; const v = s.visibleFrame; const topY = f.size.height - (v.origin.y + v.size.height); JSON.stringify([v.origin.x, topY, v.size.width, v.size.height])'`, {
9725
9618
  stdio: ["ignore", "pipe", "ignore"],
@@ -9779,6 +9672,9 @@ function getWindowSizeArgs() {
9779
9672
  return computeWindowArgs(getWorkArea());
9780
9673
  }
9781
9674
  function spawnDetached(command, args) {
9675
+ if (process.env.VITEST || process.env.NODE_ENV === "test") {
9676
+ throw new Error("spawnDetached called in a test environment without being mocked");
9677
+ }
9782
9678
  const child = spawn(command, [...args], { detached: true, stdio: "ignore" });
9783
9679
  child.on("error", () => {
9784
9680
  });
@@ -9789,14 +9685,14 @@ function openChromiumApp(binaryPath, url2, sizeArgs) {
9789
9685
  return spawnDetached(binaryPath, [`--app=${url2}`, ...sizeArgs]);
9790
9686
  }
9791
9687
  function openOsDefault(url2) {
9792
- const platform2 = os7.platform();
9688
+ const platform2 = os8.platform();
9793
9689
  if (platform2 === "darwin") {
9794
- spawnDetached("open", [url2]);
9795
- } else if (platform2 === "win32") {
9796
- spawnDetached("cmd", ["/c", "start", "", url2]);
9797
- } else {
9798
- spawnDetached("xdg-open", [url2]);
9690
+ return spawnDetached("open", [url2]);
9691
+ }
9692
+ if (platform2 === "win32") {
9693
+ return spawnDetached("cmd", ["/c", "start", "", url2]);
9799
9694
  }
9695
+ return spawnDetached("xdg-open", [url2]);
9800
9696
  }
9801
9697
  function isCodexOnMacos() {
9802
9698
  if (process.platform !== "darwin") {
@@ -9810,7 +9706,22 @@ function shouldDisableChromelessWindows() {
9810
9706
  }
9811
9707
  return isCodexOnMacos();
9812
9708
  }
9709
+ var WINDOW_OPEN_RATE_WINDOW_MS = 3e4;
9710
+ var WINDOW_OPEN_RATE_LIMIT = 3;
9711
+ var recentOpens = [];
9813
9712
  function openChromelessWindow(url2) {
9713
+ if (process.env.VITEST || process.env.NODE_ENV === "test") {
9714
+ throw new Error("openChromelessWindow called in a test environment without being mocked");
9715
+ }
9716
+ const now = Date.now();
9717
+ while (recentOpens.length > 0 && now - recentOpens[0] > WINDOW_OPEN_RATE_WINDOW_MS) {
9718
+ recentOpens.shift();
9719
+ }
9720
+ if (recentOpens.length >= WINDOW_OPEN_RATE_LIMIT) {
9721
+ console.error(`[browser] rate limit: ${recentOpens.length} windows opened in the last ${WINDOW_OPEN_RATE_WINDOW_MS / 1e3}s, refusing to open another`);
9722
+ return null;
9723
+ }
9724
+ recentOpens.push(now);
9814
9725
  if (shouldDisableChromelessWindows()) {
9815
9726
  openOsDefault(url2);
9816
9727
  return null;
@@ -9866,11 +9777,537 @@ async function createStudioSession({ serviceUrl, apiKey, sessionId, initialPath
9866
9777
  writeActiveStudioSession({
9867
9778
  sessionId,
9868
9779
  serviceUrl,
9869
- windowPid: windowPid ?? void 0
9780
+ windowPid,
9781
+ currentPath: initialPath
9870
9782
  });
9871
9783
  return { sessionId, serviceUrl };
9872
9784
  }
9873
9785
 
9786
+ // ../bitfab-plugin-lib/dist/daemon/client.js
9787
+ import net2 from "net";
9788
+
9789
+ // ../bitfab-plugin-lib/dist/daemon/ensure.js
9790
+ import { fork } from "child_process";
9791
+ import crypto5 from "crypto";
9792
+ import fs9 from "fs";
9793
+ import net from "net";
9794
+ import path8 from "path";
9795
+ import { fileURLToPath as fileURLToPath2 } from "url";
9796
+
9797
+ // ../bitfab-plugin-lib/dist/daemon/protocol.js
9798
+ import os9 from "os";
9799
+ import path7 from "path";
9800
+ var BITFAB_CONFIG_DIR = path7.join(os9.homedir(), ".config", "bitfab");
9801
+ var SOCKET_PATH = path7.join(BITFAB_CONFIG_DIR, "studio-daemon.sock");
9802
+ var PID_FILE_PATH = path7.join(BITFAB_CONFIG_DIR, "studio-daemon.pid");
9803
+ var BROWSER_HEALTH_INTERVAL_MS = 10 * 1e3;
9804
+ var ENSURE_POLL_INTERVAL_MS = 200;
9805
+ var ENSURE_TIMEOUT_MS = 5e3;
9806
+ var PING_TIMEOUT_MS = 2e3;
9807
+ function serialize(msg) {
9808
+ return `${JSON.stringify(msg)}
9809
+ `;
9810
+ }
9811
+ function isResponse(msg) {
9812
+ return typeof msg === "object" && msg !== null && "ok" in msg;
9813
+ }
9814
+ function isPush(msg) {
9815
+ return typeof msg === "object" && msg !== null && "event" in msg;
9816
+ }
9817
+
9818
+ // ../bitfab-plugin-lib/dist/daemon/ensure.js
9819
+ var LOCK_STALE_MS = 1e4;
9820
+ function processIsAlive(pid) {
9821
+ try {
9822
+ process.kill(pid, 0);
9823
+ return true;
9824
+ } catch (err) {
9825
+ return err.code !== "ESRCH";
9826
+ }
9827
+ }
9828
+ function readPid(pidPath) {
9829
+ try {
9830
+ const raw = fs9.readFileSync(pidPath, "utf-8").trim();
9831
+ const pid = Number.parseInt(raw, 10);
9832
+ return Number.isNaN(pid) ? null : pid;
9833
+ } catch {
9834
+ return null;
9835
+ }
9836
+ }
9837
+ async function pingSocket(socketPath) {
9838
+ return new Promise((resolve) => {
9839
+ const timeout = setTimeout(() => {
9840
+ socket.destroy();
9841
+ resolve({ alive: false, build: null });
9842
+ }, PING_TIMEOUT_MS);
9843
+ const socket = net.createConnection(socketPath, () => {
9844
+ socket.write('{"cmd":"ping"}\n');
9845
+ });
9846
+ let buffer = "";
9847
+ socket.on("data", (chunk) => {
9848
+ buffer += chunk.toString();
9849
+ if (buffer.includes("\n")) {
9850
+ clearTimeout(timeout);
9851
+ socket.destroy();
9852
+ try {
9853
+ const msg = JSON.parse(buffer.split("\n")[0]);
9854
+ resolve({ alive: msg.ok === true, build: msg.build ?? null });
9855
+ } catch {
9856
+ resolve({ alive: false, build: null });
9857
+ }
9858
+ }
9859
+ });
9860
+ socket.on("error", () => {
9861
+ clearTimeout(timeout);
9862
+ resolve({ alive: false, build: null });
9863
+ });
9864
+ });
9865
+ }
9866
+ async function waitForSocket(socketPath, timeoutMs) {
9867
+ const deadline = Date.now() + timeoutMs;
9868
+ while (Date.now() < deadline) {
9869
+ const { alive } = await pingSocket(socketPath);
9870
+ if (alive) {
9871
+ return;
9872
+ }
9873
+ await new Promise((r) => setTimeout(r, ENSURE_POLL_INTERVAL_MS));
9874
+ }
9875
+ throw new Error(`daemon did not become ready within ${timeoutMs}ms`);
9876
+ }
9877
+ function localBuildFingerprint() {
9878
+ try {
9879
+ const thisDir = path8.dirname(fileURLToPath2(import.meta.url));
9880
+ const serverPath = path8.join(thisDir, "server.js");
9881
+ const content = fs9.readFileSync(serverPath);
9882
+ return crypto5.createHash("sha256").update(content).digest("hex").slice(0, 16);
9883
+ } catch {
9884
+ return "unknown";
9885
+ }
9886
+ }
9887
+ async function killDaemon(pidPath, socketPath) {
9888
+ const pid = readPid(pidPath);
9889
+ if (pid !== null) {
9890
+ try {
9891
+ process.kill(pid, "SIGTERM");
9892
+ } catch {
9893
+ }
9894
+ const deadline = Date.now() + 5e3;
9895
+ while (processIsAlive(pid) && Date.now() < deadline) {
9896
+ await new Promise((r) => setTimeout(r, 50));
9897
+ }
9898
+ if (processIsAlive(pid)) {
9899
+ try {
9900
+ process.kill(pid, "SIGKILL");
9901
+ } catch {
9902
+ }
9903
+ }
9904
+ }
9905
+ cleanupStaleFiles(socketPath, pidPath);
9906
+ }
9907
+ function spawnDaemon(socketPath, pidPath) {
9908
+ const thisDir = path8.dirname(fileURLToPath2(import.meta.url));
9909
+ const mainPath = path8.join(thisDir, "main.js");
9910
+ if (!fs9.existsSync(mainPath)) {
9911
+ throw new Error(`daemon entry point not found: ${mainPath}`);
9912
+ }
9913
+ const child = fork(mainPath, [socketPath, pidPath], {
9914
+ detached: true,
9915
+ stdio: "ignore",
9916
+ execArgv: []
9917
+ });
9918
+ child.unref();
9919
+ }
9920
+ function cleanupStaleFiles(socketPath, pidPath) {
9921
+ try {
9922
+ fs9.unlinkSync(socketPath);
9923
+ } catch {
9924
+ }
9925
+ try {
9926
+ fs9.unlinkSync(pidPath);
9927
+ } catch {
9928
+ }
9929
+ }
9930
+ function lockPath(socketPath) {
9931
+ return `${socketPath}.lock`;
9932
+ }
9933
+ function acquireLock(socketPath) {
9934
+ const lock = lockPath(socketPath);
9935
+ try {
9936
+ fs9.mkdirSync(path8.dirname(lock), { recursive: true });
9937
+ fs9.writeFileSync(lock, `${process.pid}
9938
+ `, { flag: "wx" });
9939
+ return true;
9940
+ } catch (err) {
9941
+ if (err.code !== "EEXIST") {
9942
+ return true;
9943
+ }
9944
+ try {
9945
+ const stat = fs9.statSync(lock);
9946
+ if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
9947
+ fs9.unlinkSync(lock);
9948
+ fs9.writeFileSync(lock, `${process.pid}
9949
+ `, { flag: "wx" });
9950
+ return true;
9951
+ }
9952
+ } catch {
9953
+ }
9954
+ return false;
9955
+ }
9956
+ }
9957
+ function releaseLock(socketPath) {
9958
+ try {
9959
+ fs9.unlinkSync(lockPath(socketPath));
9960
+ } catch {
9961
+ }
9962
+ }
9963
+ async function ensureDaemon(socketPath = SOCKET_PATH, pidPath = PID_FILE_PATH) {
9964
+ const pid = readPid(pidPath);
9965
+ if (pid !== null && processIsAlive(pid)) {
9966
+ const ping = await pingSocket(socketPath);
9967
+ if (ping.alive) {
9968
+ const localBuild = localBuildFingerprint();
9969
+ if (ping.build && ping.build !== localBuild && localBuild !== "unknown") {
9970
+ console.error(`[daemon] build mismatch (running: ${ping.build}, local: ${localBuild}), restarting`);
9971
+ await killDaemon(pidPath, socketPath);
9972
+ } else {
9973
+ return;
9974
+ }
9975
+ }
9976
+ }
9977
+ if (!acquireLock(socketPath)) {
9978
+ await waitForSocket(socketPath, ENSURE_TIMEOUT_MS);
9979
+ return;
9980
+ }
9981
+ try {
9982
+ cleanupStaleFiles(socketPath, pidPath);
9983
+ spawnDaemon(socketPath, pidPath);
9984
+ await waitForSocket(socketPath, ENSURE_TIMEOUT_MS);
9985
+ } finally {
9986
+ releaseLock(socketPath);
9987
+ }
9988
+ }
9989
+
9990
+ // ../bitfab-plugin-lib/dist/daemon/client.js
9991
+ var DaemonClient = class {
9992
+ socket = null;
9993
+ buffer = "";
9994
+ responseQueue = [];
9995
+ eventHandler = null;
9996
+ socketPath;
9997
+ constructor(socketPath) {
9998
+ this.socketPath = socketPath ?? SOCKET_PATH;
9999
+ }
10000
+ async connect() {
10001
+ await ensureDaemon(this.socketPath);
10002
+ return new Promise((resolve, reject) => {
10003
+ const socket = net2.createConnection(this.socketPath, () => {
10004
+ this.socket = socket;
10005
+ resolve();
10006
+ });
10007
+ socket.on("data", (chunk) => {
10008
+ this.buffer += chunk.toString();
10009
+ this.drain();
10010
+ });
10011
+ socket.on("error", (err) => {
10012
+ if (!this.socket) {
10013
+ reject(err);
10014
+ return;
10015
+ }
10016
+ this.socket = null;
10017
+ });
10018
+ socket.on("close", () => {
10019
+ this.socket = null;
10020
+ });
10021
+ });
10022
+ }
10023
+ drain() {
10024
+ let idx = this.buffer.indexOf("\n");
10025
+ while (idx !== -1) {
10026
+ const line = this.buffer.slice(0, idx).trim();
10027
+ this.buffer = this.buffer.slice(idx + 1);
10028
+ if (line) {
10029
+ try {
10030
+ const msg = JSON.parse(line);
10031
+ if (isResponse(msg) && this.responseQueue.length > 0) {
10032
+ const resolver = this.responseQueue.shift();
10033
+ resolver(msg);
10034
+ } else if (isPush(msg) && this.eventHandler) {
10035
+ this.eventHandler(msg);
10036
+ }
10037
+ } catch {
10038
+ }
10039
+ }
10040
+ idx = this.buffer.indexOf("\n");
10041
+ }
10042
+ }
10043
+ sendCommand(cmd) {
10044
+ if (!this.socket) {
10045
+ return Promise.reject(new Error("not connected"));
10046
+ }
10047
+ return new Promise((resolve, reject) => {
10048
+ this.responseQueue.push(resolve);
10049
+ try {
10050
+ this.socket.write(serialize(cmd));
10051
+ } catch (err) {
10052
+ this.responseQueue.pop();
10053
+ reject(err);
10054
+ }
10055
+ });
10056
+ }
10057
+ async open(opts) {
10058
+ const cmd = { cmd: "open", ...opts };
10059
+ const res = await this.sendCommand(cmd);
10060
+ if (!res.ok) {
10061
+ throw new Error(res.error);
10062
+ }
10063
+ return res;
10064
+ }
10065
+ async navigate(key, navPath) {
10066
+ const res = await this.sendCommand({ cmd: "navigate", key, path: navPath });
10067
+ if (!res.ok) {
10068
+ throw new Error(res.error);
10069
+ }
10070
+ return res;
10071
+ }
10072
+ async close(key) {
10073
+ const res = await this.sendCommand({ cmd: "close", key });
10074
+ if (!res.ok) {
10075
+ throw new Error(res.error);
10076
+ }
10077
+ }
10078
+ async detach(key) {
10079
+ const res = await this.sendCommand({ cmd: "detach", key });
10080
+ if (!res.ok) {
10081
+ throw new Error(res.error);
10082
+ }
10083
+ }
10084
+ async status() {
10085
+ const res = await this.sendCommand({ cmd: "status" });
10086
+ if (!res.ok) {
10087
+ throw new Error(res.error);
10088
+ }
10089
+ return res;
10090
+ }
10091
+ async ping() {
10092
+ const res = await this.sendCommand({ cmd: "ping" });
10093
+ if (!res.ok) {
10094
+ throw new Error(res.error);
10095
+ }
10096
+ }
10097
+ onEvent(handler) {
10098
+ this.eventHandler = handler;
10099
+ }
10100
+ destroy() {
10101
+ this.eventHandler = null;
10102
+ this.responseQueue = [];
10103
+ if (this.socket) {
10104
+ this.socket.destroy();
10105
+ this.socket = null;
10106
+ }
10107
+ }
10108
+ };
10109
+
10110
+ // ../bitfab-plugin-lib/dist/studioChannel.js
10111
+ var DirectChannel = class {
10112
+ apiKey;
10113
+ serviceUrl;
10114
+ constructor(apiKey, serviceUrl) {
10115
+ this.apiKey = apiKey;
10116
+ this.serviceUrl = serviceUrl;
10117
+ }
10118
+ setApiKey(apiKey) {
10119
+ this.apiKey = apiKey;
10120
+ }
10121
+ async openOrNavigate(path18, opts) {
10122
+ const existing = readActiveStudioSession();
10123
+ if (existing && existing.windowState !== "closed") {
10124
+ const ack = await navigateStudioAndAwaitAck({
10125
+ serviceUrl: existing.serviceUrl,
10126
+ apiKey: this.apiKey,
10127
+ sessionId: existing.sessionId
10128
+ }, path18);
10129
+ if (!ack.acked) {
10130
+ throw new StudioNavigationError(ack.reason ?? "unknown", ack.blockedReason, existing.sessionId);
10131
+ }
10132
+ return { sessionId: existing.sessionId, opened: false };
10133
+ }
10134
+ const sessionId = opts?.sessionId ?? crypto6.randomUUID();
10135
+ const session = await createStudioSession({
10136
+ serviceUrl: this.serviceUrl,
10137
+ apiKey: this.apiKey,
10138
+ sessionId,
10139
+ initialPath: path18,
10140
+ clientHeaders: opts?.clientHeaders
10141
+ });
10142
+ return { sessionId: session.sessionId, opened: true };
10143
+ }
10144
+ async close(sessionId, message) {
10145
+ await closeStudio({ serviceUrl: this.serviceUrl, apiKey: this.apiKey, sessionId }, message);
10146
+ }
10147
+ subscribe(sessionId, onEvent, onError) {
10148
+ const abortController = new AbortController();
10149
+ const done = (async () => {
10150
+ const startCursor = await fetchStreamTip({
10151
+ serviceUrl: this.serviceUrl,
10152
+ apiKey: this.apiKey,
10153
+ sessionId
10154
+ });
10155
+ if (abortController.signal.aborted) {
10156
+ return;
10157
+ }
10158
+ await pollAgentSessionEvents({
10159
+ serviceUrl: this.serviceUrl,
10160
+ apiKey: this.apiKey,
10161
+ sessionId,
10162
+ startCursor,
10163
+ abortSignal: abortController.signal,
10164
+ onEvent,
10165
+ onError
10166
+ });
10167
+ })().catch((err) => {
10168
+ if (!abortController.signal.aborted) {
10169
+ onError(err instanceof Error ? err : new Error(String(err)));
10170
+ }
10171
+ });
10172
+ return { abort: () => abortController.abort(), done };
10173
+ }
10174
+ destroy() {
10175
+ }
10176
+ };
10177
+ var DaemonChannel = class _DaemonChannel {
10178
+ client;
10179
+ key;
10180
+ apiKey;
10181
+ serviceUrl;
10182
+ doneResolve = null;
10183
+ constructor(client, key, apiKey, serviceUrl) {
10184
+ this.client = client;
10185
+ this.key = key;
10186
+ this.apiKey = apiKey;
10187
+ this.serviceUrl = serviceUrl;
10188
+ }
10189
+ static async connect(apiKey, serviceUrl) {
10190
+ const client = new DaemonClient();
10191
+ await client.connect();
10192
+ const key = resolveAgentSessionKey();
10193
+ return new _DaemonChannel(client, key, apiKey, serviceUrl);
10194
+ }
10195
+ setApiKey(apiKey) {
10196
+ this.apiKey = apiKey;
10197
+ }
10198
+ async openOrNavigate(path18, opts) {
10199
+ const openRes = await this.client.open({
10200
+ key: this.key,
10201
+ serviceUrl: this.serviceUrl,
10202
+ apiKey: this.apiKey,
10203
+ sessionId: opts?.sessionId,
10204
+ initialPath: path18,
10205
+ clientHeaders: opts?.clientHeaders
10206
+ });
10207
+ if (openRes.reconnected) {
10208
+ const navRes = await this.client.navigate(this.key, path18);
10209
+ if (!navRes.acked) {
10210
+ this.client.destroy();
10211
+ throw new StudioNavigationError(navRes.reason ?? "unknown", void 0, openRes.sessionId);
10212
+ }
10213
+ writeActiveStudioSession({
10214
+ sessionId: openRes.sessionId,
10215
+ serviceUrl: this.serviceUrl,
10216
+ source: "daemon",
10217
+ currentPath: path18
10218
+ });
10219
+ return { sessionId: openRes.sessionId, opened: false };
10220
+ }
10221
+ writeActiveStudioSession({
10222
+ sessionId: openRes.sessionId,
10223
+ serviceUrl: this.serviceUrl,
10224
+ source: "daemon",
10225
+ currentPath: path18
10226
+ });
10227
+ return { sessionId: openRes.sessionId, opened: true };
10228
+ }
10229
+ async close(sessionId, message) {
10230
+ await Promise.all([
10231
+ closeStudio({ serviceUrl: this.serviceUrl, apiKey: this.apiKey, sessionId }, message),
10232
+ this.client.close(this.key).catch(() => {
10233
+ })
10234
+ ]);
10235
+ }
10236
+ subscribe(_sessionId, onEvent, _onError) {
10237
+ const done = new Promise((resolve) => {
10238
+ this.doneResolve = resolve;
10239
+ });
10240
+ this.client.onEvent((push) => {
10241
+ const event = {
10242
+ id: "",
10243
+ type: push.event,
10244
+ data: push.data
10245
+ };
10246
+ onEvent(event);
10247
+ });
10248
+ return {
10249
+ abort: () => this.destroy(),
10250
+ done
10251
+ };
10252
+ }
10253
+ destroy() {
10254
+ this.client.detach(this.key).catch(() => {
10255
+ });
10256
+ this.client.destroy();
10257
+ if (this.doneResolve) {
10258
+ this.doneResolve();
10259
+ this.doneResolve = null;
10260
+ }
10261
+ }
10262
+ };
10263
+ function isDaemonEnabled() {
10264
+ return process.env.BITFAB_STUDIO_DAEMON !== "0";
10265
+ }
10266
+ function withDirectFallback(primary, apiKey, serviceUrl) {
10267
+ let active = primary;
10268
+ return {
10269
+ async openOrNavigate(path18, opts) {
10270
+ try {
10271
+ return await active.openOrNavigate(path18, opts);
10272
+ } catch (err) {
10273
+ if (err instanceof StudioNavigationError || active !== primary) {
10274
+ throw err;
10275
+ }
10276
+ console.error(`daemon failed, falling back to direct: ${err instanceof Error ? err.message : String(err)}`);
10277
+ active.destroy();
10278
+ active = new DirectChannel(apiKey, serviceUrl);
10279
+ return active.openOrNavigate(path18, opts);
10280
+ }
10281
+ },
10282
+ async close(sessionId, message) {
10283
+ await active.close(sessionId, message);
10284
+ },
10285
+ subscribe(sessionId, onEvent, onError) {
10286
+ return active.subscribe(sessionId, onEvent, onError);
10287
+ },
10288
+ setApiKey(key) {
10289
+ active.setApiKey(key);
10290
+ },
10291
+ destroy() {
10292
+ active.destroy();
10293
+ }
10294
+ };
10295
+ }
10296
+ async function resolveChannel(apiKey, serviceUrl) {
10297
+ if (isDaemonEnabled()) {
10298
+ try {
10299
+ const daemon = await DaemonChannel.connect(apiKey, serviceUrl);
10300
+ return withDirectFallback(daemon, apiKey, serviceUrl);
10301
+ } catch (err) {
10302
+ if (err instanceof StudioNavigationError) {
10303
+ throw err;
10304
+ }
10305
+ console.error(`daemon unavailable, falling back to direct: ${err instanceof Error ? err.message : String(err)}`);
10306
+ }
10307
+ }
10308
+ return new DirectChannel(apiKey, serviceUrl);
10309
+ }
10310
+
9874
10311
  // ../bitfab-plugin-lib/dist/replayCapabilities.js
9875
10312
  var semver = __toESM(require_semver2(), 1);
9876
10313
 
@@ -9879,12 +10316,12 @@ var semver3 = __toESM(require_semver2(), 1);
9879
10316
 
9880
10317
  // ../bitfab-plugin-lib/dist/installedSdk.js
9881
10318
  var semver2 = __toESM(require_semver2(), 1);
9882
- import fs9 from "fs";
9883
- import path7 from "path";
10319
+ import fs11 from "fs";
10320
+ import path10 from "path";
9884
10321
 
9885
10322
  // ../bitfab-plugin-lib/dist/workspaces.js
9886
- import fs8 from "fs";
9887
- import path6 from "path";
10323
+ import fs10 from "fs";
10324
+ import path9 from "path";
9888
10325
 
9889
10326
  // ../bitfab-plugin-lib/dist/analytics.js
9890
10327
  async function reportHandoff(apiKey, pluginVersion, platform2, body) {
@@ -9908,11 +10345,11 @@ async function reportHandoff(apiKey, pluginVersion, platform2, body) {
9908
10345
  }
9909
10346
 
9910
10347
  // ../bitfab-plugin-lib/dist/commands/openStudioTo.js
9911
- import crypto4 from "crypto";
10348
+ import crypto7 from "crypto";
9912
10349
 
9913
10350
  // ../bitfab-plugin-lib/dist/frontmostApp.js
9914
10351
  import { execFileSync as execFileSync3, spawn as spawn2 } from "child_process";
9915
- import os8 from "os";
10352
+ import os10 from "os";
9916
10353
  function findAncestorApp() {
9917
10354
  try {
9918
10355
  let pid = process.ppid;
@@ -9973,7 +10410,7 @@ function focusItermSession(sessionId) {
9973
10410
  spawnQuiet("osascript", ["-e", script]);
9974
10411
  }
9975
10412
  function getFrontmostApp() {
9976
- const platform2 = os8.platform();
10413
+ const platform2 = os10.platform();
9977
10414
  try {
9978
10415
  if (platform2 === "darwin") {
9979
10416
  return execFileSync3("osascript", [
@@ -10007,7 +10444,7 @@ function focusApp(identifier) {
10007
10444
  if (!identifier) {
10008
10445
  return;
10009
10446
  }
10010
- const platform2 = os8.platform();
10447
+ const platform2 = os10.platform();
10011
10448
  if (platform2 === "darwin") {
10012
10449
  spawnQuiet("osascript", [
10013
10450
  "-e",
@@ -10036,145 +10473,144 @@ function focusApp(identifier) {
10036
10473
  spawnQuiet("powershell.exe", ["-NoProfile", "-Command", script]);
10037
10474
  }
10038
10475
  }
10039
- function recordFocus() {
10040
- const platform2 = os8.platform();
10041
- if (platform2 === "linux") {
10042
- const windowId = process.env.WINDOWID;
10043
- if (windowId) {
10044
- return () => spawnQuiet("xdotool", ["windowactivate", windowId]);
10045
- }
10046
- }
10047
- if (platform2 === "darwin") {
10048
- const app = findAncestorApp();
10049
- if (app) {
10050
- const itermSessionId = process.env.ITERM_SESSION_ID ?? null;
10051
- return () => focusAncestorApp(app, itermSessionId);
10052
- }
10053
- }
10054
- const frontmost = getFrontmostApp();
10055
- return () => focusApp(frontmost);
10056
- }
10057
-
10058
- // ../bitfab-plugin-lib/dist/commands/sessionReady.js
10059
- var SESSION_READY_TIMEOUT_MS = 3e4;
10060
- var SESSION_READY_POLL_MS = 500;
10061
- async function waitForSessionReady(opts) {
10062
- const deadline = Date.now() + SESSION_READY_TIMEOUT_MS;
10063
- let since = null;
10064
- while (Date.now() < deadline) {
10065
- try {
10066
- const url2 = new URL(`${opts.serviceUrl}/api/studio/events`);
10067
- url2.searchParams.set("session", opts.sessionId);
10068
- if (since) {
10069
- url2.searchParams.set("since", since);
10070
- }
10071
- const res = await fetch(url2.toString(), {
10072
- headers: { Authorization: `Bearer ${opts.apiKey}` }
10073
- });
10074
- if (res.ok) {
10075
- const body = await res.json();
10076
- for (const event of body.events) {
10077
- since = event.id;
10078
- if (event.type === "studio:session-started") {
10079
- return;
10080
- }
10081
- }
10082
- }
10083
- } catch {
10476
+ function recordFocus() {
10477
+ const platform2 = os10.platform();
10478
+ if (platform2 === "linux") {
10479
+ const windowId = process.env.WINDOWID;
10480
+ if (windowId) {
10481
+ return () => spawnQuiet("xdotool", ["windowactivate", windowId]);
10482
+ }
10483
+ }
10484
+ if (platform2 === "darwin") {
10485
+ const app = findAncestorApp();
10486
+ if (app) {
10487
+ const itermSessionId = process.env.ITERM_SESSION_ID ?? null;
10488
+ return () => focusAncestorApp(app, itermSessionId);
10084
10489
  }
10085
- await new Promise((resolve) => setTimeout(resolve, SESSION_READY_POLL_MS));
10086
10490
  }
10087
- console.error("Warning: Studio session did not confirm readiness within 30s. Proceeding anyway.");
10491
+ const frontmost = getFrontmostApp();
10492
+ return () => focusApp(frontmost);
10088
10493
  }
10089
10494
 
10090
10495
  // ../bitfab-plugin-lib/dist/commands/openStudioTo.js
10496
+ var WINDOW_CLOSE_GRACE_PERIOD_MS = 1e4;
10091
10497
  var LOGIN_TIMEOUT_MS = 10 * 60 * 1e3;
10092
- var BLOCKING_NOTICE_UNTIL = "the Studio interaction completes or the session ends";
10093
- var REAUTH_ACK_TIMEOUT_MS = 2e4;
10094
- var StudioNavigationError = class extends Error {
10095
- reason;
10096
- blockedReason;
10097
- staleSessionId;
10098
- constructor(reason, blockedReason, staleSessionId) {
10099
- super(blockedReason ? `Studio navigation failed (${reason}): ${blockedReason}` : `Studio navigation failed: ${reason}`);
10100
- this.reason = reason;
10101
- this.blockedReason = blockedReason;
10102
- this.staleSessionId = staleSessionId;
10103
- this.name = "StudioNavigationError";
10104
- }
10105
- };
10106
- async function openStudioTo(path15, opts = {}) {
10498
+ function createEventSubscription(channel, sessionId, onEvent, restoreFocus) {
10499
+ let graceTimer = null;
10500
+ let endedNotified = false;
10501
+ const clearGraceTimer = () => {
10502
+ if (graceTimer) {
10503
+ clearTimeout(graceTimer);
10504
+ graceTimer = null;
10505
+ }
10506
+ };
10507
+ const notifyEnded = (baseEvent) => {
10508
+ if (!endedNotified) {
10509
+ endedNotified = true;
10510
+ restoreFocus();
10511
+ onEvent({ ...baseEvent, type: "studio:session-ended" });
10512
+ }
10513
+ clearGraceTimer();
10514
+ sub.abort();
10515
+ };
10516
+ const sub = channel.subscribe(sessionId, (event) => {
10517
+ if (event.type === "studio:window-closed") {
10518
+ clearGraceTimer();
10519
+ graceTimer = setTimeout(() => {
10520
+ graceTimer = null;
10521
+ updateActiveStudioSession({ windowState: "closed" });
10522
+ notifyEnded(event);
10523
+ }, WINDOW_CLOSE_GRACE_PERIOD_MS);
10524
+ return;
10525
+ }
10526
+ if (event.type === "studio:session-started") {
10527
+ if (graceTimer) {
10528
+ clearGraceTimer();
10529
+ console.error("Studio reconnected after page refresh");
10530
+ }
10531
+ return;
10532
+ }
10533
+ if (event.type === "studio:session-ended") {
10534
+ clearGraceTimer();
10535
+ notifyEnded(event);
10536
+ return;
10537
+ }
10538
+ if (event.type === "studio:session-closed") {
10539
+ return;
10540
+ }
10541
+ if (event.type === "studio:not-member") {
10542
+ restoreFocus();
10543
+ onEvent(event);
10544
+ sub.abort();
10545
+ return;
10546
+ }
10547
+ if (event.type === "studio:return-to-agent" || event.type === "studio:edit-with-agent") {
10548
+ restoreFocus();
10549
+ }
10550
+ onEvent(event);
10551
+ }, (err) => {
10552
+ console.error(`studio event stream: ${err.message}`);
10553
+ });
10554
+ return {
10555
+ abort: () => {
10556
+ clearGraceTimer();
10557
+ sub.abort();
10558
+ },
10559
+ done: sub.done
10560
+ };
10561
+ }
10562
+ async function openStudioTo(path18, opts = {}) {
10107
10563
  const config2 = getConfig();
10108
10564
  const serviceUrl = config2.serviceUrl;
10109
10565
  const apiKey = !opts.forceLogin && hasCredentials() ? config2.apiKey : null;
10110
10566
  const noop = () => {
10111
10567
  };
10112
10568
  const restoreFocus = recordFocus();
10569
+ const channel = await resolveChannel(apiKey ?? "", serviceUrl);
10113
10570
  if (!apiKey) {
10114
- const existing2 = readActiveStudioSession();
10115
- if (existing2) {
10116
- const reauth = await requestSessionReauth({
10117
- serviceUrl: existing2.serviceUrl,
10118
- sessionId: existing2.sessionId,
10119
- redirectPath: path15
10120
- });
10121
- if (!reauth.ok) {
10122
- throw new StudioNavigationError("push-failed", void 0, existing2.sessionId);
10123
- }
10124
- return loginViaExistingWindow({
10125
- serviceUrl: existing2.serviceUrl,
10126
- sessionId: existing2.sessionId,
10127
- startCursor: reauth.pushedEventId,
10128
- signInUrl: reauth.signInUrl,
10129
- restoreFocus,
10130
- opts
10131
- });
10132
- }
10133
- const sessionId2 = crypto4.randomUUID();
10134
- const freshPath = opts.freshWindowPath ?? path15;
10135
- const separator = freshPath.includes("?") ? "&" : "?";
10136
- const redirectPath = `${freshPath}${separator}session=${encodeURIComponent(sessionId2)}&pluginLogin=true`;
10137
- const signInPath = `/studio/sign-in?redirect_url=${encodeURIComponent(redirectPath)}`;
10138
- const signInUrl = `${serviceUrl}${signInPath}&session=${encodeURIComponent(sessionId2)}`;
10139
- const windowPid = openChromelessWindow(signInUrl);
10140
- writeActiveStudioSession({
10141
- sessionId: sessionId2,
10142
- serviceUrl,
10143
- windowPid: windowPid ?? void 0
10571
+ const sessionId2 = crypto7.randomUUID();
10572
+ const destination = opts.freshWindowPath ?? path18;
10573
+ const destSeparator = destination.includes("?") ? "&" : "?";
10574
+ const redirectPath = `${destination}${destSeparator}session=${encodeURIComponent(sessionId2)}&pluginLogin=true`;
10575
+ const signInInitialPath = `/studio/sign-in?redirect_url=${encodeURIComponent(redirectPath)}&session=${encodeURIComponent(sessionId2)}`;
10576
+ await channel.openOrNavigate(signInInitialPath, {
10577
+ clientHeaders: opts.clientHeaders,
10578
+ sessionId: sessionId2
10144
10579
  });
10580
+ const signInUrl = `${serviceUrl}${signInInitialPath}`;
10145
10581
  opts.onLoginRequired?.(sessionId2, signInUrl);
10146
- const abortController = new AbortController();
10147
- const timer = setTimeout(() => abortController.abort(), LOGIN_TIMEOUT_MS);
10148
- const keepalive = setInterval(() => process.stderr.write(""), 3e4);
10149
- let loginApiKey;
10150
- try {
10151
- loginApiKey = await pollLoginEvents({
10152
- serviceUrl,
10153
- sessionId: sessionId2,
10154
- abortSignal: abortController.signal
10582
+ const loginApiKey = await new Promise((resolve, reject) => {
10583
+ const timer = setTimeout(() => {
10584
+ sub.abort();
10585
+ reject(new Error("Login polling aborted"));
10586
+ }, LOGIN_TIMEOUT_MS);
10587
+ const keepalive = setInterval(() => process.stderr.write(""), 3e4);
10588
+ const cleanup = () => {
10589
+ clearTimeout(timer);
10590
+ clearInterval(keepalive);
10591
+ };
10592
+ const sub = channel.subscribe(sessionId2, (event) => {
10593
+ if (event.type === "studio:authenticated" && typeof event.data?.token === "string") {
10594
+ cleanup();
10595
+ sub.abort();
10596
+ resolve(event.data.token);
10597
+ }
10598
+ }, (err) => {
10599
+ cleanup();
10600
+ reject(err);
10155
10601
  });
10156
- } catch (err) {
10157
- clearTimeout(timer);
10158
- clearInterval(keepalive);
10159
- throw err;
10160
- }
10161
- clearTimeout(timer);
10162
- clearInterval(keepalive);
10602
+ });
10163
10603
  saveCredentials(loginApiKey);
10164
10604
  opts.onAuthenticated?.(sessionId2);
10605
+ channel.setApiKey(loginApiKey);
10165
10606
  if (opts.onEvent) {
10166
- await waitForSessionReady({
10167
- serviceUrl,
10168
- apiKey: loginApiKey,
10169
- sessionId: sessionId2
10170
- });
10171
- const poller2 = startEventPoller(serviceUrl, loginApiKey, sessionId2, opts.onEvent, restoreFocus, { claim: opts.monitor !== "wait" });
10607
+ const subscription2 = createEventSubscription(channel, sessionId2, opts.onEvent, restoreFocus);
10172
10608
  return {
10173
10609
  sessionId: sessionId2,
10174
10610
  serviceUrl,
10175
10611
  apiKey: loginApiKey,
10176
10612
  opened: true,
10177
- ...poller2
10613
+ ...subscription2
10178
10614
  };
10179
10615
  }
10180
10616
  restoreFocus();
@@ -10187,299 +10623,20 @@ async function openStudioTo(path15, opts = {}) {
10187
10623
  done: Promise.resolve()
10188
10624
  };
10189
10625
  }
10190
- const existing = readActiveStudioSession();
10191
- if (existing) {
10192
- const client = {
10193
- serviceUrl: existing.serviceUrl,
10194
- apiKey,
10195
- sessionId: existing.sessionId
10196
- };
10197
- const ack = await navigateStudioAndAwaitAck(client, path15);
10198
- if (!ack.acked) {
10199
- throw new StudioNavigationError(ack.reason ?? "unknown", ack.blockedReason, existing.sessionId);
10200
- }
10201
- if (opts.onEvent && opts.monitor === "wait") {
10202
- const poller2 = startEventPoller(existing.serviceUrl, apiKey, existing.sessionId, opts.onEvent, restoreFocus, { claim: false });
10203
- return {
10204
- sessionId: existing.sessionId,
10205
- serviceUrl: existing.serviceUrl,
10206
- apiKey,
10207
- opened: false,
10208
- ...poller2
10209
- };
10210
- }
10211
- if (opts.onEvent && opts.monitor === "start" && tryClaimStudioMonitor()) {
10212
- const poller2 = startEventPoller(existing.serviceUrl, apiKey, existing.sessionId, opts.onEvent, restoreFocus, { claim: true });
10213
- return {
10214
- sessionId: existing.sessionId,
10215
- serviceUrl: existing.serviceUrl,
10216
- apiKey,
10217
- opened: false,
10218
- ...poller2
10219
- };
10220
- }
10221
- restoreFocus();
10222
- return {
10223
- sessionId: existing.sessionId,
10224
- serviceUrl: existing.serviceUrl,
10225
- apiKey,
10226
- opened: false,
10227
- abort: noop,
10228
- done: Promise.resolve()
10229
- };
10230
- }
10231
- const sessionId = crypto4.randomUUID();
10232
- const session = await createStudioSession({
10233
- serviceUrl,
10234
- apiKey,
10235
- sessionId,
10236
- initialPath: path15,
10626
+ const { sessionId, opened } = await channel.openOrNavigate(path18, {
10237
10627
  clientHeaders: opts.clientHeaders
10238
10628
  });
10239
- const poller = opts.onEvent ? startEventPoller(session.serviceUrl, apiKey, session.sessionId, opts.onEvent, restoreFocus, { claim: opts.monitor !== "wait" }) : { abort: noop, done: Promise.resolve() };
10240
- return {
10241
- sessionId: session.sessionId,
10242
- serviceUrl: session.serviceUrl,
10243
- apiKey,
10244
- opened: true,
10245
- ...poller
10246
- };
10247
- }
10248
- async function loginViaExistingWindow(args) {
10249
- const { serviceUrl, sessionId } = args;
10250
- const noop = () => {
10251
- };
10252
- const keepalive = setInterval(() => process.stderr.write(""), 3e4);
10253
- let loginApiKey;
10254
- try {
10255
- loginApiKey = await awaitReauthLogin({
10256
- serviceUrl,
10257
- sessionId,
10258
- startCursor: args.startCursor,
10259
- // Only fired once the sign-in page proved the window alive AND is
10260
- // waiting on the user; a still-valid browser session re-auths silently
10261
- // and the caller never surfaces a sign-in prompt.
10262
- onAck: () => args.opts.onLoginRequired?.(sessionId, args.signInUrl)
10263
- });
10264
- } finally {
10265
- clearInterval(keepalive);
10266
- }
10267
- saveCredentials(loginApiKey);
10268
- args.opts.onAuthenticated?.(sessionId);
10269
- if (args.opts.onEvent && (args.opts.monitor === "wait" || args.opts.monitor === "start" && tryClaimStudioMonitor())) {
10270
- await waitForSessionReady({
10271
- serviceUrl,
10272
- apiKey: loginApiKey,
10273
- sessionId
10274
- });
10275
- const poller = startEventPoller(serviceUrl, loginApiKey, sessionId, args.opts.onEvent, args.restoreFocus, { claim: args.opts.monitor !== "wait" });
10276
- return {
10277
- sessionId,
10278
- serviceUrl,
10279
- apiKey: loginApiKey,
10280
- opened: false,
10281
- ...poller
10282
- };
10283
- }
10284
- args.restoreFocus();
10629
+ const subscription = opts.onEvent ? createEventSubscription(channel, sessionId, opts.onEvent, restoreFocus) : { abort: () => channel.destroy(), done: Promise.resolve() };
10285
10630
  return {
10286
10631
  sessionId,
10287
10632
  serviceUrl,
10288
- apiKey: loginApiKey,
10289
- opened: false,
10290
- abort: noop,
10291
- done: Promise.resolve()
10292
- };
10293
- }
10294
- function awaitReauthLogin(args) {
10295
- return new Promise((resolve, reject) => {
10296
- const abortController = new AbortController();
10297
- let settled = false;
10298
- let acked = false;
10299
- const settle = (fn) => {
10300
- if (settled) {
10301
- return;
10302
- }
10303
- settled = true;
10304
- clearTimeout(ackTimer);
10305
- clearTimeout(loginTimer);
10306
- abortController.abort();
10307
- fn();
10308
- };
10309
- const ackTimer = setTimeout(() => {
10310
- settle(() => reject(new StudioNavigationError("timeout", void 0, args.sessionId)));
10311
- }, REAUTH_ACK_TIMEOUT_MS);
10312
- const loginTimer = setTimeout(() => {
10313
- settle(() => reject(new Error("Login polling aborted")));
10314
- }, LOGIN_TIMEOUT_MS);
10315
- pollSessionAuthEvents({
10316
- serviceUrl: args.serviceUrl,
10317
- sessionId: args.sessionId,
10318
- startCursor: args.startCursor,
10319
- abortSignal: abortController.signal,
10320
- onEvent: (event) => {
10321
- if (event.type === "studio:auth-required" && !acked) {
10322
- acked = true;
10323
- clearTimeout(ackTimer);
10324
- args.onAck();
10325
- return;
10326
- }
10327
- if (event.type === "studio:authenticated") {
10328
- const token = event.data.token;
10329
- if (typeof token === "string") {
10330
- settle(() => resolve(token));
10331
- }
10332
- }
10333
- }
10334
- }).catch((err) => {
10335
- settle(() => reject(err instanceof Error ? err : new Error(String(err))));
10336
- });
10337
- });
10338
- }
10339
- function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus, { claim = true, fromStreamStart = false, graceMs, forceClear = false } = {}) {
10340
- printBlockingNotice(BLOCKING_NOTICE_UNTIL);
10341
- if (claim) {
10342
- claimStudioMonitor();
10343
- }
10344
- const abortController = new AbortController();
10345
- let endedNotified = false;
10346
- const releaseLock = () => {
10347
- if (claim) {
10348
- releaseStudioMonitor();
10349
- }
10350
- };
10351
- const notifyEnded = (baseEvent) => {
10352
- if (!endedNotified) {
10353
- endedNotified = true;
10354
- restoreFocus();
10355
- onEvent({ ...baseEvent, type: "studio:session-ended" });
10356
- }
10357
- releaseLock();
10358
- abortController.abort();
10359
- };
10360
- const arbiter = createWindowCloseArbiter({
10361
- onConfirmed: (event) => {
10362
- if (forceClear) {
10363
- clearActiveStudioSession(sessionId, { force: true });
10364
- } else {
10365
- clearActiveStudioSession(sessionId);
10366
- }
10367
- notifyEnded(event);
10368
- },
10369
- onRefuted: () => {
10370
- console.error("Studio reconnected after page refresh");
10371
- }
10372
- }, graceMs);
10373
- const done = (async () => {
10374
- const startCursor = fromStreamStart ? void 0 : await fetchStreamTip({ serviceUrl, apiKey, sessionId });
10375
- if (abortController.signal.aborted) {
10376
- return;
10377
- }
10378
- await pollAgentSessionEvents({
10379
- serviceUrl,
10380
- apiKey,
10381
- sessionId,
10382
- startCursor,
10383
- abortSignal: abortController.signal,
10384
- onEvent: (event) => {
10385
- if (event.type === "studio:window-closed" || event.type === "studio:session-started") {
10386
- arbiter.handleEvent(event);
10387
- return;
10388
- }
10389
- if (event.type === "studio:session-ended") {
10390
- arbiter.cancel();
10391
- notifyEnded(event);
10392
- return;
10393
- }
10394
- if (event.type === "studio:session-closed") {
10395
- return;
10396
- }
10397
- if (event.type === "studio:not-member") {
10398
- restoreFocus();
10399
- onEvent(event);
10400
- releaseLock();
10401
- abortController.abort();
10402
- return;
10403
- }
10404
- if (event.type === "studio:return-to-agent" || event.type === "studio:edit-with-agent") {
10405
- restoreFocus();
10406
- }
10407
- onEvent(event);
10408
- },
10409
- onError: (err) => {
10410
- console.error(`studio agent-event poll: ${err.message}`);
10411
- }
10412
- });
10413
- })().catch((err) => {
10414
- if (!abortController.signal.aborted) {
10415
- console.error(`studio agent-event poll terminated: ${err.message}`);
10416
- }
10417
- releaseLock();
10418
- });
10419
- return {
10420
- abort: () => {
10421
- releaseLock();
10422
- abortController.abort();
10423
- },
10424
- done
10425
- };
10426
- }
10427
- var LOGIN_MONITOR_DRAIN_MS = 8e3;
10428
- async function runLoginWindowMonitor(sessionId) {
10429
- const config2 = getConfig();
10430
- const apiKey = config2.apiKey;
10431
- if (!apiKey) {
10432
- return;
10433
- }
10434
- const poller = startEventPoller(
10435
- config2.serviceUrl,
10436
10633
  apiKey,
10437
- sessionId,
10438
- () => {
10439
- },
10440
- () => {
10441
- },
10442
- // claim: false — this is a transient cleanup reader with a no-op event
10443
- // handler, NOT the session's real monitor. If it claimed the lock, a
10444
- // subsequent `openStudioTo` reuse with monitor:"start" (e.g. a flow's
10445
- // backgrounded open right after a PARKED login) would lose the atomic
10446
- // claim, assume a live monitor is forwarding events, and fire-and-exit —
10447
- // leaving the session with no real monitor while this one forwards
10448
- // nothing. Reading without claiming lets it clean up the pointer
10449
- // (window-closed → clear) without shadowing the real monitor.
10450
- //
10451
- // forceClear: the clear runs in a detached child while the login/init
10452
- // parent that WROTE the pointer may still be alive (init keeps running with
10453
- // exitOnComplete:false, even spawnSync-ing the editor), so a non-force clear
10454
- // would be refused by the writer-alive guard and the closed window's
10455
- // pointer would leak. Safe — the clear still matches on the unique
10456
- // sessionId.
10457
- { claim: false, fromStreamStart: true, graceMs: 0, forceClear: true }
10458
- );
10459
- let drainTimer;
10460
- const drain = new Promise((resolve) => {
10461
- drainTimer = setTimeout(resolve, LOGIN_MONITOR_DRAIN_MS);
10462
- });
10463
- await Promise.race([poller.done, drain]);
10464
- if (drainTimer) {
10465
- clearTimeout(drainTimer);
10466
- }
10467
- poller.abort();
10634
+ opened,
10635
+ ...subscription
10636
+ };
10468
10637
  }
10469
10638
 
10470
10639
  // ../bitfab-plugin-lib/dist/commands/login.js
10471
- function trySpawnDetachedMonitor(sessionId) {
10472
- try {
10473
- const pid = spawnDetached(process.execPath, [
10474
- ...process.argv.slice(1),
10475
- "--drain",
10476
- sessionId
10477
- ]);
10478
- return pid != null;
10479
- } catch {
10480
- return false;
10481
- }
10482
- }
10483
10640
  async function verifyToken(serviceUrl, token) {
10484
10641
  try {
10485
10642
  const res = await fetch(`${serviceUrl}/api/plugin/whoami`, {
@@ -10495,18 +10652,6 @@ async function verifyToken(serviceUrl, token) {
10495
10652
  }
10496
10653
  async function runLogin(platform2, pluginVersion, options) {
10497
10654
  const exitOnComplete = options?.exitOnComplete ?? true;
10498
- const drainIdx = process.argv.indexOf("--drain");
10499
- if (drainIdx !== -1) {
10500
- const sessionId = process.argv[drainIdx + 1];
10501
- if (!sessionId) {
10502
- throw new Error("--drain requires a sessionId argument");
10503
- }
10504
- await runLoginWindowMonitor(sessionId);
10505
- if (exitOnComplete) {
10506
- process.exit(0);
10507
- }
10508
- return;
10509
- }
10510
10655
  const force = options?.force ?? process.argv.includes("--force");
10511
10656
  const config2 = getConfig();
10512
10657
  if (!force && hasCredentials() && config2.apiKey) {
@@ -10546,9 +10691,7 @@ ${signInUrl}`);
10546
10691
  ${greeting}`);
10547
10692
  console.log("Bitfab MCP tools are now active. (via studio)");
10548
10693
  }
10549
- if (!trySpawnDetachedMonitor(result.sessionId)) {
10550
- await runLoginWindowMonitor(result.sessionId);
10551
- }
10694
+ result.abort();
10552
10695
  if (exitOnComplete) {
10553
10696
  process.exit(0);
10554
10697
  }
@@ -10568,8 +10711,11 @@ ${err.message}`);
10568
10711
 
10569
10712
  // ../bitfab-plugin-lib/dist/commands/logout.js
10570
10713
  function runLogout() {
10714
+ printHelpIfRequested({
10715
+ description: "Log out of Bitfab by removing the stored credentials from this machine. Takes no arguments."
10716
+ });
10571
10717
  if (!hasCredentials()) {
10572
- console.log("Not logged in \u2014 no credentials to remove.");
10718
+ console.log("Not logged in: no credentials to remove.");
10573
10719
  return;
10574
10720
  }
10575
10721
  deleteCredentials();
@@ -11350,10 +11496,10 @@ function mergeDefs(...defs) {
11350
11496
  function cloneDef(schema) {
11351
11497
  return mergeDefs(schema._zod.def);
11352
11498
  }
11353
- function getElementAtPath(obj, path15) {
11354
- if (!path15)
11499
+ function getElementAtPath(obj, path18) {
11500
+ if (!path18)
11355
11501
  return obj;
11356
- return path15.reduce((acc, key) => acc?.[key], obj);
11502
+ return path18.reduce((acc, key) => acc?.[key], obj);
11357
11503
  }
11358
11504
  function promiseAllObject(promisesObj) {
11359
11505
  const keys = Object.keys(promisesObj);
@@ -11736,11 +11882,11 @@ function aborted(x, startIndex = 0) {
11736
11882
  }
11737
11883
  return false;
11738
11884
  }
11739
- function prefixIssues(path15, issues) {
11885
+ function prefixIssues(path18, issues) {
11740
11886
  return issues.map((iss) => {
11741
11887
  var _a2;
11742
11888
  (_a2 = iss).path ?? (_a2.path = []);
11743
- iss.path.unshift(path15);
11889
+ iss.path.unshift(path18);
11744
11890
  return iss;
11745
11891
  });
11746
11892
  }
@@ -11923,7 +12069,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
11923
12069
  }
11924
12070
  function treeifyError(error48, mapper = (issue2) => issue2.message) {
11925
12071
  const result = { errors: [] };
11926
- const processError = (error49, path15 = []) => {
12072
+ const processError = (error49, path18 = []) => {
11927
12073
  var _a2, _b;
11928
12074
  for (const issue2 of error49.issues) {
11929
12075
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -11933,7 +12079,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
11933
12079
  } else if (issue2.code === "invalid_element") {
11934
12080
  processError({ issues: issue2.issues }, issue2.path);
11935
12081
  } else {
11936
- const fullpath = [...path15, ...issue2.path];
12082
+ const fullpath = [...path18, ...issue2.path];
11937
12083
  if (fullpath.length === 0) {
11938
12084
  result.errors.push(mapper(issue2));
11939
12085
  continue;
@@ -11965,8 +12111,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
11965
12111
  }
11966
12112
  function toDotPath(_path) {
11967
12113
  const segs = [];
11968
- const path15 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
11969
- for (const seg of path15) {
12114
+ const path18 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
12115
+ for (const seg of path18) {
11970
12116
  if (typeof seg === "number")
11971
12117
  segs.push(`[${seg}]`);
11972
12118
  else if (typeof seg === "symbol")
@@ -23943,13 +24089,13 @@ function resolveRef(ref, ctx) {
23943
24089
  if (!ref.startsWith("#")) {
23944
24090
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
23945
24091
  }
23946
- const path15 = ref.slice(1).split("/").filter(Boolean);
23947
- if (path15.length === 0) {
24092
+ const path18 = ref.slice(1).split("/").filter(Boolean);
24093
+ if (path18.length === 0) {
23948
24094
  return ctx.rootSchema;
23949
24095
  }
23950
24096
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
23951
- if (path15[0] === defsKey) {
23952
- const key = path15[1];
24097
+ if (path18[0] === defsKey) {
24098
+ const key = path18[1];
23953
24099
  if (!key || !ctx.defs[key]) {
23954
24100
  throw new Error(`Reference not found: ${ref}`);
23955
24101
  }
@@ -24381,18 +24527,18 @@ var inputFileSchema = external_exports.object({
24381
24527
  import { spawn as spawn3 } from "child_process";
24382
24528
 
24383
24529
  // ../bitfab-plugin-lib/dist/activePreviewSession.js
24384
- import fs10 from "fs";
24385
- import os9 from "os";
24386
- import path8 from "path";
24387
- var FILE_PATH = path8.join(os9.homedir(), ".config", "bitfab", "active-preview-session.json");
24530
+ import fs12 from "fs";
24531
+ import os11 from "os";
24532
+ import path11 from "path";
24533
+ var FILE_PATH = path11.join(os11.homedir(), ".config", "bitfab", "active-preview-session.json");
24388
24534
 
24389
24535
  // ../bitfab-plugin-lib/dist/commands/status.js
24390
- import path10 from "path";
24536
+ import path13 from "path";
24391
24537
 
24392
24538
  // ../bitfab-plugin-lib/dist/updates.js
24393
- import fs11 from "fs";
24394
- import os10 from "os";
24395
- import path9 from "path";
24539
+ import fs13 from "fs";
24540
+ import os12 from "os";
24541
+ import path12 from "path";
24396
24542
 
24397
24543
  // ../bitfab-plugin-lib/dist/commands/update.js
24398
24544
  import { execSync as execSync2 } from "child_process";
@@ -24401,15 +24547,15 @@ import { execSync as execSync2 } from "child_process";
24401
24547
  var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
24402
24548
 
24403
24549
  // ../bitfab-plugin-lib/dist/hooks/captureHook.js
24404
- import fs12 from "fs";
24550
+ import fs14 from "fs";
24405
24551
 
24406
24552
  // ../bitfab-plugin-lib/dist/hooks/userPromptSubmit.js
24407
- import fs13 from "fs";
24553
+ import fs15 from "fs";
24408
24554
 
24409
24555
  // ../bitfab-plugin-lib/dist/installScope.js
24410
- import fs14 from "fs";
24411
- import os11 from "os";
24412
- import path11 from "path";
24556
+ import fs16 from "fs";
24557
+ import os13 from "os";
24558
+ import path14 from "path";
24413
24559
 
24414
24560
  // ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/helpers/util.js
24415
24561
  var util;
@@ -24770,8 +24916,8 @@ function getErrorMap2() {
24770
24916
 
24771
24917
  // ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/helpers/parseUtil.js
24772
24918
  var makeIssue = (params) => {
24773
- const { data, path: path15, errorMaps, issueData } = params;
24774
- const fullPath = [...path15, ...issueData.path || []];
24919
+ const { data, path: path18, errorMaps, issueData } = params;
24920
+ const fullPath = [...path18, ...issueData.path || []];
24775
24921
  const fullIssue = {
24776
24922
  ...issueData,
24777
24923
  path: fullPath
@@ -24886,11 +25032,11 @@ var errorUtil;
24886
25032
 
24887
25033
  // ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/types.js
24888
25034
  var ParseInputLazyPath = class {
24889
- constructor(parent, value, path15, key) {
25035
+ constructor(parent, value, path18, key) {
24890
25036
  this._cachedPath = [];
24891
25037
  this.parent = parent;
24892
25038
  this.data = value;
24893
- this._path = path15;
25039
+ this._path = path18;
24894
25040
  this._key = key;
24895
25041
  }
24896
25042
  get path() {
@@ -29784,15 +29930,11 @@ var McpZodTypeKind;
29784
29930
  // ../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
29785
29931
  import process3 from "process";
29786
29932
 
29787
- // src/init.ts
29788
- import { execSync as execSync3 } from "child_process";
29789
- import * as p4 from "@clack/prompts";
29790
-
29791
29933
  // src/claude.ts
29792
29934
  import { spawnSync } from "child_process";
29793
- import fs15 from "fs";
29794
- import os12 from "os";
29795
- import path12 from "path";
29935
+ import fs17 from "fs";
29936
+ import os14 from "os";
29937
+ import path15 from "path";
29796
29938
  import * as p from "@clack/prompts";
29797
29939
  var REPO = "Project-White-Rabbit/bitfab-claude-plugin";
29798
29940
  var MARKETPLACE = "bitfab";
@@ -29812,7 +29954,7 @@ function runClaudeInstall() {
29812
29954
  }
29813
29955
  s.start("Enabling auto-updates");
29814
29956
  const changed = enableAutoUpdate(
29815
- path12.join(os12.homedir(), ".claude", "settings.json")
29957
+ path15.join(os14.homedir(), ".claude", "settings.json")
29816
29958
  );
29817
29959
  if (changed) {
29818
29960
  s.stop("Auto-updates enabled");
@@ -29903,8 +30045,8 @@ function runClaude(args) {
29903
30045
  }
29904
30046
  function enableAutoUpdate(settingsPath) {
29905
30047
  let settings = {};
29906
- if (fs15.existsSync(settingsPath)) {
29907
- const raw = fs15.readFileSync(settingsPath, "utf-8");
30048
+ if (fs17.existsSync(settingsPath)) {
30049
+ const raw = fs17.readFileSync(settingsPath, "utf-8");
29908
30050
  settings = raw.trim() === "" ? {} : JSON.parse(raw);
29909
30051
  }
29910
30052
  const marketplaces = settings.extraKnownMarketplaces ?? {};
@@ -29918,17 +30060,17 @@ function enableAutoUpdate(settingsPath) {
29918
30060
  autoUpdate: true
29919
30061
  };
29920
30062
  settings.extraKnownMarketplaces = marketplaces;
29921
- fs15.mkdirSync(path12.dirname(settingsPath), { recursive: true });
29922
- fs15.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
30063
+ fs17.mkdirSync(path15.dirname(settingsPath), { recursive: true });
30064
+ fs17.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
29923
30065
  `);
29924
30066
  return true;
29925
30067
  }
29926
30068
 
29927
30069
  // src/codex.ts
29928
30070
  import { spawnSync as spawnSync2 } from "child_process";
29929
- import fs16 from "fs";
29930
- import os13 from "os";
29931
- import path13 from "path";
30071
+ import fs18 from "fs";
30072
+ import os15 from "os";
30073
+ import path16 from "path";
29932
30074
  import * as p2 from "@clack/prompts";
29933
30075
  var REPO2 = "Project-White-Rabbit/bitfab-codex-plugin";
29934
30076
  var PLUGIN_KEY2 = "bitfab@bitfab";
@@ -29956,7 +30098,7 @@ function runCodexInstall() {
29956
30098
  }
29957
30099
  s.start("Enabling bitfab plugin in config");
29958
30100
  const changed = enableCodexPlugin(
29959
- path13.join(os13.homedir(), ".codex", "config.toml")
30101
+ path16.join(os15.homedir(), ".codex", "config.toml")
29960
30102
  );
29961
30103
  if (changed) {
29962
30104
  s.stop("Plugin enabled");
@@ -30019,8 +30161,8 @@ function runCodexUpdate(args, skipPermissions) {
30019
30161
  }
30020
30162
  function enableCodexPlugin(configPath) {
30021
30163
  let content = "";
30022
- if (fs16.existsSync(configPath)) {
30023
- content = fs16.readFileSync(configPath, "utf-8");
30164
+ if (fs18.existsSync(configPath)) {
30165
+ content = fs18.readFileSync(configPath, "utf-8");
30024
30166
  }
30025
30167
  const header = `[plugins."${PLUGIN_KEY2}"]`;
30026
30168
  const desiredSection = `${header}
@@ -30043,8 +30185,8 @@ enabled = true
30043
30185
 
30044
30186
  ${desiredSection}`;
30045
30187
  }
30046
- fs16.mkdirSync(path13.dirname(configPath), { recursive: true });
30047
- fs16.writeFileSync(configPath, next);
30188
+ fs18.mkdirSync(path16.dirname(configPath), { recursive: true });
30189
+ fs18.writeFileSync(configPath, next);
30048
30190
  return true;
30049
30191
  }
30050
30192
  function escapeRegex2(s) {
@@ -30053,7 +30195,7 @@ function escapeRegex2(s) {
30053
30195
 
30054
30196
  // src/cursor.ts
30055
30197
  import { spawn as spawn4, spawnSync as spawnSync3 } from "child_process";
30056
- import os14 from "os";
30198
+ import os16 from "os";
30057
30199
  import * as p3 from "@clack/prompts";
30058
30200
  var REPO3 = "Project-White-Rabbit/bitfab-cursor-plugin";
30059
30201
  var ADD_PLUGIN_CMD = `/add-plugin ${REPO3}`;
@@ -30125,13 +30267,13 @@ function copyToClipboard(text) {
30125
30267
  return result.status === 0;
30126
30268
  }
30127
30269
  function clipboardCommand() {
30128
- if (os14.platform() === "darwin") {
30270
+ if (os16.platform() === "darwin") {
30129
30271
  return { bin: "pbcopy", args: [] };
30130
30272
  }
30131
- if (os14.platform() === "linux") {
30273
+ if (os16.platform() === "linux") {
30132
30274
  return { bin: "xclip", args: ["-selection", "clipboard"] };
30133
30275
  }
30134
- if (os14.platform() === "win32") {
30276
+ if (os16.platform() === "win32") {
30135
30277
  return { bin: "clip", args: [] };
30136
30278
  }
30137
30279
  return null;
@@ -30155,12 +30297,12 @@ var platform = {
30155
30297
  };
30156
30298
 
30157
30299
  // src/version.ts
30158
- import fs17 from "fs";
30159
- import path14 from "path";
30160
- import { fileURLToPath as fileURLToPath2 } from "url";
30300
+ import fs19 from "fs";
30301
+ import path17 from "path";
30302
+ import { fileURLToPath as fileURLToPath3 } from "url";
30161
30303
  var CLI_VERSION = JSON.parse(
30162
- fs17.readFileSync(
30163
- path14.join(path14.dirname(fileURLToPath2(import.meta.url)), "../package.json"),
30304
+ fs19.readFileSync(
30305
+ path17.join(path17.dirname(fileURLToPath3(import.meta.url)), "../package.json"),
30164
30306
  "utf-8"
30165
30307
  )
30166
30308
  ).version;
@@ -30405,6 +30547,10 @@ Examples:
30405
30547
  bitfab setup --skip-permissions Setup without permission prompts
30406
30548
  bitfab assistant --skip-permissions Run assistant autonomously
30407
30549
  `;
30550
+ function wantsHelp(argv) {
30551
+ const command = argv[0];
30552
+ return !command || command === "help" || argv.includes("-h") || argv.includes("--help");
30553
+ }
30408
30554
  function parseArgs2(argv) {
30409
30555
  const [command, ...tail] = argv;
30410
30556
  let editor;
@@ -30431,27 +30577,12 @@ function parseArgs2(argv) {
30431
30577
  }
30432
30578
  return { command, editor, skipPermissions, rest };
30433
30579
  }
30434
- async function handleDrainArg(argv) {
30435
- const drainIdx = argv.indexOf("--drain");
30436
- if (drainIdx === -1) {
30437
- return false;
30438
- }
30439
- const sessionId = argv[drainIdx + 1];
30440
- if (!sessionId) {
30441
- throw new Error("--drain requires a sessionId argument");
30442
- }
30443
- await runLoginWindowMonitor(sessionId);
30444
- return true;
30445
- }
30446
30580
  async function main() {
30447
- if (await handleDrainArg(process.argv)) {
30448
- return;
30449
- }
30450
30581
  const { command, editor, skipPermissions, rest } = parseArgs2(
30451
30582
  process.argv.slice(2)
30452
30583
  );
30453
30584
  const abortUpdateCheck = startUpdateCheck();
30454
- if (!command || command === "help" || command === "--help" || command === "-h") {
30585
+ if (wantsHelp(process.argv.slice(2))) {
30455
30586
  process.stdout.write(HELP_TEXT);
30456
30587
  abortUpdateCheck();
30457
30588
  return;
@@ -30503,6 +30634,6 @@ main().catch((err) => {
30503
30634
  process.exit(1);
30504
30635
  });
30505
30636
  export {
30506
- handleDrainArg,
30507
- parseArgs2 as parseArgs
30637
+ parseArgs2 as parseArgs,
30638
+ wantsHelp
30508
30639
  };