bitfab-cli 0.2.84 → 0.2.86

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 +924 -848
  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,24 @@ var NotAuthenticatedError = class extends Error {
8738
8742
  this.name = "NotAuthenticatedError";
8739
8743
  }
8740
8744
  };
8745
+ var DaemonUnavailableError = class extends Error {
8746
+ constructor(message) {
8747
+ super(message);
8748
+ this.name = "DaemonUnavailableError";
8749
+ }
8750
+ };
8751
+ var StudioNavigationError = class extends Error {
8752
+ reason;
8753
+ blockedReason;
8754
+ staleSessionId;
8755
+ constructor(reason, blockedReason, staleSessionId) {
8756
+ super(blockedReason ? `Studio navigation failed (${reason}): ${blockedReason}` : `Studio navigation failed: ${reason}`);
8757
+ this.reason = reason;
8758
+ this.blockedReason = blockedReason;
8759
+ this.staleSessionId = staleSessionId;
8760
+ this.name = "StudioNavigationError";
8761
+ }
8762
+ };
8741
8763
 
8742
8764
  // ../bitfab-plugin-lib/dist/studioRoutes.js
8743
8765
  var STUDIO_ROUTE_PATTERNS = [
@@ -8805,7 +8827,7 @@ async function pollAgentSessionEvents(opts) {
8805
8827
  const interval = opts.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
8806
8828
  const client = {
8807
8829
  serviceUrl: opts.serviceUrl,
8808
- apiKey: opts.apiKey,
8830
+ apiKey: opts.apiKey ?? "",
8809
8831
  sessionId: opts.sessionId
8810
8832
  };
8811
8833
  let since = opts.startCursor ?? null;
@@ -8816,8 +8838,9 @@ async function pollAgentSessionEvents(opts) {
8816
8838
  if (since) {
8817
8839
  url2.searchParams.set("since", since);
8818
8840
  }
8841
+ const authHeaders = opts.apiKey ? { Authorization: `Bearer ${opts.apiKey}` } : { [STUDIO_SESSION_SCOPE_HEADER]: STUDIO_SESSION_SCOPE_VALUE };
8819
8842
  const res = await fetch(url2.toString(), {
8820
- headers: { Authorization: `Bearer ${opts.apiKey}` },
8843
+ headers: authHeaders,
8821
8844
  signal: opts.abortSignal
8822
8845
  });
8823
8846
  if (res.ok) {
@@ -8857,124 +8880,13 @@ async function pollAgentSessionEvents(opts) {
8857
8880
  });
8858
8881
  }
8859
8882
  }
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
8883
  var STUDIO_SESSION_SCOPE_HEADER = "x-bitfab-auth-scope";
8902
8884
  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
8885
  async function fetchStreamTip(client) {
8975
8886
  const PAGE_LIMIT = 500;
8976
8887
  const MAX_PAGES = 10;
8977
8888
  let tip;
8889
+ const authHeaders = client.apiKey ? { Authorization: `Bearer ${client.apiKey}` } : { [STUDIO_SESSION_SCOPE_HEADER]: STUDIO_SESSION_SCOPE_VALUE };
8978
8890
  try {
8979
8891
  for (let page = 0; page < MAX_PAGES; page++) {
8980
8892
  const url2 = new URL(`${client.serviceUrl}/api/studio/events`);
@@ -8984,7 +8896,7 @@ async function fetchStreamTip(client) {
8984
8896
  url2.searchParams.set("since", tip);
8985
8897
  }
8986
8898
  const res = await fetch(url2.toString(), {
8987
- headers: { Authorization: `Bearer ${client.apiKey}` }
8899
+ headers: authHeaders
8988
8900
  });
8989
8901
  if (!res.ok) {
8990
8902
  return tip;
@@ -9004,18 +8916,24 @@ async function fetchStreamTip(client) {
9004
8916
  }
9005
8917
  return tip;
9006
8918
  }
9007
- async function navigateStudio(client, path15) {
9008
- if (!isValidStudioRoute(path15)) {
9009
- throw new Error(`Studio route not allowed: ${path15}`);
8919
+ async function closeStudio(client, message) {
8920
+ return pushAgentSessionEvent(client, {
8921
+ type: "agent:close-studio",
8922
+ data: message ? { message } : {}
8923
+ });
8924
+ }
8925
+ async function navigateStudio(client, path18) {
8926
+ if (!isValidStudioRoute(path18)) {
8927
+ throw new Error(`Studio route not allowed: ${path18}`);
9010
8928
  }
9011
8929
  return pushAgentSessionEvent(client, {
9012
8930
  type: "agent:navigate",
9013
- data: { path: path15 }
8931
+ data: { path: path18 }
9014
8932
  });
9015
8933
  }
9016
8934
  var STUDIO_NAVIGATE_ACK_TIMEOUT_MS = 12e3;
9017
8935
  var PING_PONG_TIMEOUT_MS = 5e3;
9018
- function awaitNavigateAck(client, path15, ackTimeoutMs, startCursor) {
8936
+ function awaitNavigateAck(client, path18, ackTimeoutMs, startCursor) {
9019
8937
  const abortController = new AbortController();
9020
8938
  let timer = null;
9021
8939
  const result = new Promise((resolve) => {
@@ -9023,7 +8941,7 @@ function awaitNavigateAck(client, path15, ackTimeoutMs, startCursor) {
9023
8941
  resolve({ acked: false, reason: "timeout" });
9024
8942
  abortController.abort();
9025
8943
  }, ackTimeoutMs);
9026
- const pathOnly = path15.split("?")[0];
8944
+ const pathOnly = path18.split("?")[0];
9027
8945
  pollAgentSessionEvents({
9028
8946
  serviceUrl: client.serviceUrl,
9029
8947
  apiKey: client.apiKey,
@@ -9109,18 +9027,18 @@ function awaitPong(client, pingTs) {
9109
9027
  });
9110
9028
  });
9111
9029
  }
9112
- async function navigateStudioAndAwaitAck(client, path15, opts = {}) {
9030
+ async function navigateStudioAndAwaitAck(client, path18, opts = {}) {
9113
9031
  const ackTimeoutMs = opts.ackTimeoutMs ?? STUDIO_NAVIGATE_ACK_TIMEOUT_MS;
9114
- if (!isValidStudioRoute(path15)) {
9032
+ if (!isValidStudioRoute(path18)) {
9115
9033
  return { acked: false, reason: "invalid-route" };
9116
9034
  }
9117
9035
  const startCursor = await fetchStreamTip(client);
9118
9036
  try {
9119
- await navigateStudio(client, path15);
9037
+ await navigateStudio(client, path18);
9120
9038
  } catch {
9121
9039
  return { acked: false, reason: "push-failed" };
9122
9040
  }
9123
- const result = await awaitNavigateAck(client, path15, ackTimeoutMs, startCursor);
9041
+ const result = await awaitNavigateAck(client, path18, ackTimeoutMs, startCursor);
9124
9042
  if (result.acked || result.reason !== "timeout") {
9125
9043
  return result;
9126
9044
  }
@@ -9181,9 +9099,9 @@ var GLOBAL_CONFIG_FILE = path3.join(GLOBAL_CONFIG_DIR, "config.json");
9181
9099
  var GLOBAL_CREDENTIALS_FILE = path3.join(GLOBAL_CONFIG_DIR, "credentials.json");
9182
9100
  var PROJECT_CONFIG_RELATIVE = path3.join(".bitfab", "config.local.json");
9183
9101
  var PROJECT_CREDENTIALS_RELATIVE = path3.join(".bitfab", "credentials.local.json");
9184
- function readJsonFile(filePath2) {
9102
+ function readJsonFile(filePath) {
9185
9103
  try {
9186
- const content = fs3.readFileSync(filePath2, "utf-8");
9104
+ const content = fs3.readFileSync(filePath, "utf-8");
9187
9105
  return JSON.parse(content);
9188
9106
  } catch {
9189
9107
  return null;
@@ -9204,18 +9122,18 @@ function findProjectFile(relativePath, startDir = process.cwd()) {
9204
9122
  }
9205
9123
  }
9206
9124
  function getProjectConfigData() {
9207
- const filePath2 = findProjectFile(PROJECT_CONFIG_RELATIVE);
9208
- if (!filePath2) {
9125
+ const filePath = findProjectFile(PROJECT_CONFIG_RELATIVE);
9126
+ if (!filePath) {
9209
9127
  return null;
9210
9128
  }
9211
- return readJsonFile(filePath2);
9129
+ return readJsonFile(filePath);
9212
9130
  }
9213
9131
  function getProjectCredentialsData() {
9214
- const filePath2 = findProjectFile(PROJECT_CREDENTIALS_RELATIVE);
9215
- if (!filePath2) {
9132
+ const filePath = findProjectFile(PROJECT_CREDENTIALS_RELATIVE);
9133
+ if (!filePath) {
9216
9134
  return null;
9217
9135
  }
9218
- return readJsonFile(filePath2) ?? {};
9136
+ return readJsonFile(filePath) ?? {};
9219
9137
  }
9220
9138
  function getConfigData() {
9221
9139
  return readJsonFile(GLOBAL_CONFIG_FILE) ?? {};
@@ -9345,10 +9263,34 @@ function buildBitfabRequestHeaders(apiKey, pluginVersion, platform2) {
9345
9263
 
9346
9264
  // ../bitfab-plugin-lib/dist/activeStudioSession.js
9347
9265
  import { execFileSync as execFileSync2 } from "child_process";
9266
+ import crypto4 from "crypto";
9267
+ import fs7 from "fs";
9268
+ import os7 from "os";
9269
+ import path6 from "path";
9270
+
9271
+ // ../bitfab-plugin-lib/dist/agentSessionKey.js
9348
9272
  import crypto3 from "crypto";
9349
9273
  import fs6 from "fs";
9350
9274
  import os6 from "os";
9351
9275
  import path5 from "path";
9276
+ var FALLBACK_KEY_PATH = path5.join(os6.homedir(), ".config", "bitfab", "agent-session-key");
9277
+ function readOrCreateFallbackKey() {
9278
+ try {
9279
+ const existing = fs6.readFileSync(FALLBACK_KEY_PATH, "utf-8").trim();
9280
+ if (existing) {
9281
+ return existing;
9282
+ }
9283
+ } catch {
9284
+ }
9285
+ const key = crypto3.randomUUID();
9286
+ fs6.mkdirSync(path5.dirname(FALLBACK_KEY_PATH), { recursive: true });
9287
+ fs6.writeFileSync(FALLBACK_KEY_PATH, `${key}
9288
+ `);
9289
+ return key;
9290
+ }
9291
+ function resolveAgentSessionKey() {
9292
+ return process.env.CLAUDE_CODE_SESSION_ID ?? process.env.CURSOR_SESSION_ID ?? process.env.CODEX_THREAD_ID ?? readOrCreateFallbackKey();
9293
+ }
9352
9294
 
9353
9295
  // ../bitfab-plugin-lib/dist/getAgentSessionId.js
9354
9296
  function getAgentSessionId() {
@@ -9356,186 +9298,104 @@ function getAgentSessionId() {
9356
9298
  }
9357
9299
 
9358
9300
  // ../bitfab-plugin-lib/dist/activeStudioSession.js
9301
+ var SESSIONS_DIR = path6.join(os7.homedir(), ".config", "bitfab", "sessions");
9359
9302
  function shortHash(input) {
9360
- return crypto3.createHash("sha256").update(input).digest("hex").slice(0, 16);
9303
+ return crypto4.createHash("sha256").update(input).digest("hex").slice(0, 16);
9361
9304
  }
9362
- function sessionScope() {
9363
- return shortHash(getAgentSessionId() ?? process.cwd());
9305
+ function sessionFilePathForKey(key) {
9306
+ return path6.join(SESSIONS_DIR, `${shortHash(key)}.json`);
9364
9307
  }
9365
- function filePath() {
9366
- return path5.join(os6.homedir(), ".config", "bitfab", `active-studio-session.${sessionScope()}.json`);
9308
+ function sessionFilePath() {
9309
+ return sessionFilePathForKey(resolveAgentSessionKey());
9367
9310
  }
9368
- function writeRecord(record2) {
9369
- const target = filePath();
9370
- fs6.mkdirSync(path5.dirname(target), { recursive: true });
9311
+ function writeStateFile(target, state) {
9312
+ fs7.mkdirSync(path6.dirname(target), { recursive: true });
9371
9313
  const tmp = `${target}.${process.pid}.tmp`;
9372
- fs6.writeFileSync(tmp, `${JSON.stringify(record2, null, 2)}
9314
+ fs7.writeFileSync(tmp, `${JSON.stringify(state, null, 2)}
9373
9315
  `);
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
- });
9382
- }
9383
- function isProcessAlive(pid) {
9384
- 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;
9403
- } catch {
9404
- return null;
9405
- }
9316
+ fs7.renameSync(tmp, target);
9406
9317
  }
9407
- function clearActiveStudioSession(sessionId, opts = {}) {
9318
+ function readStateFile(filePath) {
9408
9319
  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
- }
9421
- }
9422
- function readActiveStudioSessionRaw() {
9423
- try {
9424
- const raw = fs6.readFileSync(filePath(), "utf-8");
9320
+ const raw = fs7.readFileSync(filePath, "utf-8");
9425
9321
  const parsed = JSON.parse(raw);
9426
- if (parsed.closedAt != null || parsed.endedAt != null) {
9322
+ if (typeof parsed.sessionId !== "string" || typeof parsed.serviceUrl !== "string") {
9427
9323
  return null;
9428
9324
  }
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
- };
9437
- }
9438
- return null;
9325
+ return parsed;
9439
9326
  } catch {
9440
9327
  return null;
9441
9328
  }
9442
9329
  }
9443
- function readActiveStudioSession() {
9444
- return readActiveStudioSessionRaw();
9445
- }
9446
- function monitorLockPath() {
9447
- return path5.join(os6.homedir(), ".config", "bitfab", `active-studio-session.${sessionScope()}.monitor.lock`);
9448
- }
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
- }
9475
- }
9476
- stampMonitorPid();
9477
- return true;
9330
+ function legacySessionScope() {
9331
+ return shortHash(getAgentSessionId() ?? process.cwd());
9478
9332
  }
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 {
9485
- }
9486
- stampMonitorPid();
9333
+ function legacyFilePath() {
9334
+ return path6.join(os7.homedir(), ".config", "bitfab", `active-studio-session.${legacySessionScope()}.json`);
9487
9335
  }
9488
- function releaseStudioMonitor() {
9336
+ function readLegacySession() {
9489
9337
  try {
9490
- const lock = monitorLockPath();
9491
- if (readLockOwner(lock)?.pid === process.pid) {
9492
- fs6.unlinkSync(lock);
9338
+ const raw = fs7.readFileSync(legacyFilePath(), "utf-8");
9339
+ const parsed = JSON.parse(raw);
9340
+ if (parsed.closedAt != null || parsed.endedAt != null) {
9341
+ return null;
9493
9342
  }
9494
- } catch {
9495
- }
9496
- }
9497
- function monitorLockBody(pid) {
9498
- return `${pid}
9499
- ${processStartToken(pid) ?? ""}`;
9500
- }
9501
- function isMonitorOwnerLive(owner) {
9502
- if (!isProcessAlive(owner.pid)) {
9503
- return false;
9504
- }
9505
- if (!owner.start) {
9506
- return true;
9507
- }
9508
- const current = processStartToken(owner.pid);
9509
- if (current === null) {
9510
- return true;
9511
- }
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)) {
9343
+ if (typeof parsed.sessionId !== "string" || typeof parsed.serviceUrl !== "string" || typeof parsed.pid !== "number" || typeof parsed.startedAt !== "string") {
9519
9344
  return null;
9520
9345
  }
9521
- return { pid, start: rest.join("\n").trim() };
9346
+ const legacy = parsed;
9347
+ return {
9348
+ sessionId: legacy.sessionId,
9349
+ serviceUrl: legacy.serviceUrl,
9350
+ windowPid: legacy.windowPid ?? null,
9351
+ pollerPid: legacy.pid,
9352
+ source: "direct",
9353
+ openedAt: legacy.startedAt,
9354
+ currentPath: null,
9355
+ authenticated: true,
9356
+ windowState: "open",
9357
+ agentIntent: "open",
9358
+ lastBrowserPingAt: null,
9359
+ browserConnected: false,
9360
+ lastEventAt: null,
9361
+ lastEventCursor: null,
9362
+ agentSessionKey: null
9363
+ };
9522
9364
  } catch {
9523
9365
  return null;
9524
9366
  }
9525
9367
  }
9526
- function stampMonitorPid() {
9527
- const current = readActiveStudioSessionRaw();
9528
- if (current) {
9529
- writeRecord({ ...current, pid: process.pid });
9530
- }
9368
+ function writeActiveStudioSession(init) {
9369
+ const state = {
9370
+ sessionId: init.sessionId,
9371
+ serviceUrl: init.serviceUrl,
9372
+ windowPid: init.windowPid ?? null,
9373
+ pollerPid: process.pid,
9374
+ source: init.source ?? "direct",
9375
+ openedAt: (/* @__PURE__ */ new Date()).toISOString(),
9376
+ currentPath: init.currentPath ?? null,
9377
+ authenticated: init.authenticated ?? true,
9378
+ windowState: "open",
9379
+ agentIntent: "open",
9380
+ lastBrowserPingAt: null,
9381
+ browserConnected: false,
9382
+ lastEventAt: null,
9383
+ lastEventCursor: null,
9384
+ agentSessionKey: null
9385
+ };
9386
+ writeStateFile(sessionFilePath(), state);
9531
9387
  }
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.`;
9388
+ function readActiveStudioSession() {
9389
+ return readStateFile(sessionFilePath()) ?? readLegacySession();
9536
9390
  }
9537
- function printBlockingNotice(until) {
9538
- console.error(blockingNote(until));
9391
+ function updateActiveStudioSession(updates) {
9392
+ const current = readStateFile(sessionFilePath());
9393
+ if (!current) {
9394
+ return null;
9395
+ }
9396
+ const updated = { ...current, ...updates };
9397
+ writeStateFile(sessionFilePath(), updated);
9398
+ return updated;
9539
9399
  }
9540
9400
 
9541
9401
  // ../bitfab-plugin-lib/dist/parseArgs.js
@@ -9604,42 +9464,15 @@ function extractCommandName(argv) {
9604
9464
  return argv[1]?.replace(/.*\//, "").replace(/\.[jt]s$/, "") ?? "command";
9605
9465
  }
9606
9466
 
9607
- // ../bitfab-plugin-lib/dist/studioTeardown.js
9608
- var WINDOW_CLOSE_GRACE_PERIOD_MS = 1e4;
9609
- function createWindowCloseArbiter(handlers, graceMs = WINDOW_CLOSE_GRACE_PERIOD_MS) {
9610
- let graceTimer = null;
9611
- const cancel3 = () => {
9612
- if (graceTimer) {
9613
- clearTimeout(graceTimer);
9614
- graceTimer = null;
9615
- }
9616
- };
9617
- return {
9618
- handleEvent(event) {
9619
- if (event.type === "studio:window-closed") {
9620
- cancel3();
9621
- graceTimer = setTimeout(() => {
9622
- graceTimer = null;
9623
- handlers.onConfirmed(event);
9624
- }, graceMs);
9625
- return;
9626
- }
9627
- if (event.type === "studio:session-started" && graceTimer) {
9628
- cancel3();
9629
- handlers.onRefuted?.();
9630
- }
9631
- },
9632
- hasPendingCandidate: () => graceTimer != null,
9633
- cancel: cancel3
9634
- };
9635
- }
9467
+ // ../bitfab-plugin-lib/dist/studioChannel.js
9468
+ import crypto6 from "crypto";
9636
9469
 
9637
9470
  // ../bitfab-plugin-lib/dist/browser.js
9638
9471
  import { execSync, spawn } from "child_process";
9639
- import fs7 from "fs";
9640
- import os7 from "os";
9472
+ import fs8 from "fs";
9473
+ import os8 from "os";
9641
9474
  function getChromiumBrowsers() {
9642
- const platform2 = os7.platform();
9475
+ const platform2 = os8.platform();
9643
9476
  if (platform2 === "darwin") {
9644
9477
  return [
9645
9478
  {
@@ -9727,7 +9560,7 @@ function getChromiumBrowsers() {
9727
9560
  function findExistingBinary(paths) {
9728
9561
  for (const candidate of paths) {
9729
9562
  if (candidate.includes("/") || candidate.includes("\\")) {
9730
- if (fs7.existsSync(candidate)) {
9563
+ if (fs8.existsSync(candidate)) {
9731
9564
  return candidate;
9732
9565
  }
9733
9566
  } else {
@@ -9741,10 +9574,10 @@ function findExistingBinary(paths) {
9741
9574
  return null;
9742
9575
  }
9743
9576
  function getDefaultBrowserId() {
9744
- const platform2 = os7.platform();
9577
+ const platform2 = os8.platform();
9745
9578
  try {
9746
9579
  if (platform2 === "darwin") {
9747
- const plistPath = `${os7.homedir()}/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist`;
9580
+ const plistPath = `${os8.homedir()}/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist`;
9748
9581
  const json2 = execSync(`plutil -convert json -o - "${plistPath}"`, {
9749
9582
  stdio: ["ignore", "pipe", "ignore"],
9750
9583
  encoding: "utf-8",
@@ -9785,7 +9618,7 @@ function matchChromiumBrowser(defaultId, browsers) {
9785
9618
  var SCREEN_FRACTION = 0.95;
9786
9619
  function getWorkArea() {
9787
9620
  try {
9788
- const platform2 = os7.platform();
9621
+ const platform2 = os8.platform();
9789
9622
  if (platform2 === "darwin") {
9790
9623
  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])'`, {
9791
9624
  stdio: ["ignore", "pipe", "ignore"],
@@ -9845,6 +9678,9 @@ function getWindowSizeArgs() {
9845
9678
  return computeWindowArgs(getWorkArea());
9846
9679
  }
9847
9680
  function spawnDetached(command, args) {
9681
+ if (process.env.VITEST || process.env.NODE_ENV === "test") {
9682
+ throw new Error("spawnDetached called in a test environment without being mocked");
9683
+ }
9848
9684
  const child = spawn(command, [...args], { detached: true, stdio: "ignore" });
9849
9685
  child.on("error", () => {
9850
9686
  });
@@ -9855,14 +9691,14 @@ function openChromiumApp(binaryPath, url2, sizeArgs) {
9855
9691
  return spawnDetached(binaryPath, [`--app=${url2}`, ...sizeArgs]);
9856
9692
  }
9857
9693
  function openOsDefault(url2) {
9858
- const platform2 = os7.platform();
9694
+ const platform2 = os8.platform();
9859
9695
  if (platform2 === "darwin") {
9860
- spawnDetached("open", [url2]);
9861
- } else if (platform2 === "win32") {
9862
- spawnDetached("cmd", ["/c", "start", "", url2]);
9863
- } else {
9864
- spawnDetached("xdg-open", [url2]);
9696
+ return spawnDetached("open", [url2]);
9697
+ }
9698
+ if (platform2 === "win32") {
9699
+ return spawnDetached("cmd", ["/c", "start", "", url2]);
9865
9700
  }
9701
+ return spawnDetached("xdg-open", [url2]);
9866
9702
  }
9867
9703
  function isCodexOnMacos() {
9868
9704
  if (process.platform !== "darwin") {
@@ -9876,7 +9712,22 @@ function shouldDisableChromelessWindows() {
9876
9712
  }
9877
9713
  return isCodexOnMacos();
9878
9714
  }
9715
+ var WINDOW_OPEN_RATE_WINDOW_MS = 3e4;
9716
+ var WINDOW_OPEN_RATE_LIMIT = 3;
9717
+ var recentOpens = [];
9879
9718
  function openChromelessWindow(url2) {
9719
+ if (process.env.VITEST || process.env.NODE_ENV === "test") {
9720
+ throw new Error("openChromelessWindow called in a test environment without being mocked");
9721
+ }
9722
+ const now = Date.now();
9723
+ while (recentOpens.length > 0 && now - recentOpens[0] > WINDOW_OPEN_RATE_WINDOW_MS) {
9724
+ recentOpens.shift();
9725
+ }
9726
+ if (recentOpens.length >= WINDOW_OPEN_RATE_LIMIT) {
9727
+ console.error(`[browser] rate limit: ${recentOpens.length} windows opened in the last ${WINDOW_OPEN_RATE_WINDOW_MS / 1e3}s, refusing to open another`);
9728
+ return null;
9729
+ }
9730
+ recentOpens.push(now);
9880
9731
  if (shouldDisableChromelessWindows()) {
9881
9732
  openOsDefault(url2);
9882
9733
  return null;
@@ -9932,11 +9783,555 @@ async function createStudioSession({ serviceUrl, apiKey, sessionId, initialPath
9932
9783
  writeActiveStudioSession({
9933
9784
  sessionId,
9934
9785
  serviceUrl,
9935
- windowPid: windowPid ?? void 0
9786
+ windowPid,
9787
+ currentPath: initialPath
9936
9788
  });
9937
9789
  return { sessionId, serviceUrl };
9938
9790
  }
9939
9791
 
9792
+ // ../bitfab-plugin-lib/dist/daemon/client.js
9793
+ import net2 from "net";
9794
+
9795
+ // ../bitfab-plugin-lib/dist/daemon/ensure.js
9796
+ import { fork } from "child_process";
9797
+ import crypto5 from "crypto";
9798
+ import fs9 from "fs";
9799
+ import net from "net";
9800
+ import path8 from "path";
9801
+ import { fileURLToPath as fileURLToPath2 } from "url";
9802
+
9803
+ // ../bitfab-plugin-lib/dist/daemon/protocol.js
9804
+ import os9 from "os";
9805
+ import path7 from "path";
9806
+ var BITFAB_CONFIG_DIR = path7.join(os9.homedir(), ".config", "bitfab");
9807
+ var SOCKET_PATH = path7.join(BITFAB_CONFIG_DIR, "studio-daemon.sock");
9808
+ var PID_FILE_PATH = path7.join(BITFAB_CONFIG_DIR, "studio-daemon.pid");
9809
+ var BROWSER_HEALTH_INTERVAL_MS = 10 * 1e3;
9810
+ var ENSURE_POLL_INTERVAL_MS = 200;
9811
+ var ENSURE_TIMEOUT_MS = 5e3;
9812
+ var PING_TIMEOUT_MS = 2e3;
9813
+ function serialize(msg) {
9814
+ return `${JSON.stringify(msg)}
9815
+ `;
9816
+ }
9817
+ function isResponse(msg) {
9818
+ return typeof msg === "object" && msg !== null && "ok" in msg;
9819
+ }
9820
+ function isPush(msg) {
9821
+ return typeof msg === "object" && msg !== null && "event" in msg;
9822
+ }
9823
+
9824
+ // ../bitfab-plugin-lib/dist/daemon/ensure.js
9825
+ var LOCK_STALE_MS = 1e4;
9826
+ function processIsAlive(pid) {
9827
+ try {
9828
+ process.kill(pid, 0);
9829
+ return true;
9830
+ } catch (err) {
9831
+ return err.code !== "ESRCH";
9832
+ }
9833
+ }
9834
+ function readPid(pidPath) {
9835
+ try {
9836
+ const raw = fs9.readFileSync(pidPath, "utf-8").trim();
9837
+ const pid = Number.parseInt(raw, 10);
9838
+ return Number.isNaN(pid) ? null : pid;
9839
+ } catch {
9840
+ return null;
9841
+ }
9842
+ }
9843
+ async function pingSocket(socketPath) {
9844
+ return new Promise((resolve) => {
9845
+ const timeout = setTimeout(() => {
9846
+ socket.destroy();
9847
+ resolve({ alive: false, build: null });
9848
+ }, PING_TIMEOUT_MS);
9849
+ const socket = net.createConnection(socketPath, () => {
9850
+ socket.write('{"cmd":"ping"}\n');
9851
+ });
9852
+ let buffer = "";
9853
+ socket.on("data", (chunk) => {
9854
+ buffer += chunk.toString();
9855
+ if (buffer.includes("\n")) {
9856
+ clearTimeout(timeout);
9857
+ socket.destroy();
9858
+ try {
9859
+ const msg = JSON.parse(buffer.split("\n")[0]);
9860
+ resolve({ alive: msg.ok === true, build: msg.build ?? null });
9861
+ } catch {
9862
+ resolve({ alive: false, build: null });
9863
+ }
9864
+ }
9865
+ });
9866
+ socket.on("error", () => {
9867
+ clearTimeout(timeout);
9868
+ resolve({ alive: false, build: null });
9869
+ });
9870
+ });
9871
+ }
9872
+ async function waitForSocket(socketPath, timeoutMs) {
9873
+ const deadline = Date.now() + timeoutMs;
9874
+ while (Date.now() < deadline) {
9875
+ const { alive } = await pingSocket(socketPath);
9876
+ if (alive) {
9877
+ return;
9878
+ }
9879
+ await new Promise((r) => setTimeout(r, ENSURE_POLL_INTERVAL_MS));
9880
+ }
9881
+ throw new Error(`daemon did not become ready within ${timeoutMs}ms`);
9882
+ }
9883
+ function localBuildFingerprint() {
9884
+ try {
9885
+ const thisDir = path8.dirname(fileURLToPath2(import.meta.url));
9886
+ const serverPath = path8.join(thisDir, "server.js");
9887
+ const content = fs9.readFileSync(serverPath);
9888
+ return crypto5.createHash("sha256").update(content).digest("hex").slice(0, 16);
9889
+ } catch {
9890
+ return "unknown";
9891
+ }
9892
+ }
9893
+ async function killDaemon(pidPath, socketPath) {
9894
+ const pid = readPid(pidPath);
9895
+ if (pid !== null) {
9896
+ try {
9897
+ process.kill(pid, "SIGTERM");
9898
+ } catch {
9899
+ }
9900
+ const deadline = Date.now() + 5e3;
9901
+ while (processIsAlive(pid) && Date.now() < deadline) {
9902
+ await new Promise((r) => setTimeout(r, 50));
9903
+ }
9904
+ if (processIsAlive(pid)) {
9905
+ try {
9906
+ process.kill(pid, "SIGKILL");
9907
+ } catch {
9908
+ }
9909
+ }
9910
+ }
9911
+ cleanupStaleFiles(socketPath, pidPath);
9912
+ }
9913
+ function spawnDaemon(socketPath, pidPath) {
9914
+ const thisDir = path8.dirname(fileURLToPath2(import.meta.url));
9915
+ const mainPath = path8.join(thisDir, "main.js");
9916
+ if (!fs9.existsSync(mainPath)) {
9917
+ throw new DaemonUnavailableError(`daemon entry point not found: ${mainPath}`);
9918
+ }
9919
+ const child = fork(mainPath, [socketPath, pidPath], {
9920
+ detached: true,
9921
+ stdio: "ignore",
9922
+ execArgv: []
9923
+ });
9924
+ child.unref();
9925
+ }
9926
+ function cleanupStaleFiles(socketPath, pidPath) {
9927
+ try {
9928
+ fs9.unlinkSync(socketPath);
9929
+ } catch {
9930
+ }
9931
+ try {
9932
+ fs9.unlinkSync(pidPath);
9933
+ } catch {
9934
+ }
9935
+ }
9936
+ function lockPath(socketPath) {
9937
+ return `${socketPath}.lock`;
9938
+ }
9939
+ function acquireLock(socketPath) {
9940
+ const lock = lockPath(socketPath);
9941
+ try {
9942
+ fs9.mkdirSync(path8.dirname(lock), { recursive: true });
9943
+ fs9.writeFileSync(lock, `${process.pid}
9944
+ `, { flag: "wx" });
9945
+ return true;
9946
+ } catch (err) {
9947
+ if (err.code !== "EEXIST") {
9948
+ return true;
9949
+ }
9950
+ try {
9951
+ const stat = fs9.statSync(lock);
9952
+ if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
9953
+ fs9.unlinkSync(lock);
9954
+ fs9.writeFileSync(lock, `${process.pid}
9955
+ `, { flag: "wx" });
9956
+ return true;
9957
+ }
9958
+ } catch {
9959
+ }
9960
+ return false;
9961
+ }
9962
+ }
9963
+ function releaseLock(socketPath) {
9964
+ try {
9965
+ fs9.unlinkSync(lockPath(socketPath));
9966
+ } catch {
9967
+ }
9968
+ }
9969
+ async function ensureDaemon(socketPath = SOCKET_PATH, pidPath = PID_FILE_PATH) {
9970
+ const pid = readPid(pidPath);
9971
+ if (pid !== null && processIsAlive(pid)) {
9972
+ const ping = await pingSocket(socketPath);
9973
+ if (ping.alive) {
9974
+ const localBuild = localBuildFingerprint();
9975
+ if (ping.build && ping.build !== localBuild && localBuild !== "unknown") {
9976
+ console.error(`[daemon] build mismatch (running: ${ping.build}, local: ${localBuild}), restarting`);
9977
+ await killDaemon(pidPath, socketPath);
9978
+ } else {
9979
+ return;
9980
+ }
9981
+ }
9982
+ }
9983
+ if (!acquireLock(socketPath)) {
9984
+ await waitForSocket(socketPath, ENSURE_TIMEOUT_MS);
9985
+ return;
9986
+ }
9987
+ try {
9988
+ cleanupStaleFiles(socketPath, pidPath);
9989
+ spawnDaemon(socketPath, pidPath);
9990
+ await waitForSocket(socketPath, ENSURE_TIMEOUT_MS);
9991
+ } finally {
9992
+ releaseLock(socketPath);
9993
+ }
9994
+ }
9995
+
9996
+ // ../bitfab-plugin-lib/dist/daemon/client.js
9997
+ var DaemonClient = class {
9998
+ socket = null;
9999
+ buffer = "";
10000
+ responseQueue = [];
10001
+ eventHandler = null;
10002
+ socketPath;
10003
+ constructor(socketPath) {
10004
+ this.socketPath = socketPath ?? SOCKET_PATH;
10005
+ }
10006
+ async connect() {
10007
+ await ensureDaemon(this.socketPath);
10008
+ return new Promise((resolve, reject) => {
10009
+ const socket = net2.createConnection(this.socketPath, () => {
10010
+ this.socket = socket;
10011
+ resolve();
10012
+ });
10013
+ socket.on("data", (chunk) => {
10014
+ this.buffer += chunk.toString();
10015
+ this.drain();
10016
+ });
10017
+ socket.on("error", (err) => {
10018
+ if (!this.socket) {
10019
+ reject(err);
10020
+ return;
10021
+ }
10022
+ this.socket = null;
10023
+ });
10024
+ socket.on("close", () => {
10025
+ this.socket = null;
10026
+ });
10027
+ });
10028
+ }
10029
+ drain() {
10030
+ let idx = this.buffer.indexOf("\n");
10031
+ while (idx !== -1) {
10032
+ const line = this.buffer.slice(0, idx).trim();
10033
+ this.buffer = this.buffer.slice(idx + 1);
10034
+ if (line) {
10035
+ try {
10036
+ const msg = JSON.parse(line);
10037
+ if (isResponse(msg) && this.responseQueue.length > 0) {
10038
+ const resolver = this.responseQueue.shift();
10039
+ resolver(msg);
10040
+ } else if (isPush(msg) && this.eventHandler) {
10041
+ this.eventHandler(msg);
10042
+ }
10043
+ } catch {
10044
+ }
10045
+ }
10046
+ idx = this.buffer.indexOf("\n");
10047
+ }
10048
+ }
10049
+ sendCommand(cmd) {
10050
+ if (!this.socket) {
10051
+ return Promise.reject(new Error("not connected"));
10052
+ }
10053
+ return new Promise((resolve, reject) => {
10054
+ this.responseQueue.push(resolve);
10055
+ try {
10056
+ this.socket.write(serialize(cmd));
10057
+ } catch (err) {
10058
+ this.responseQueue.pop();
10059
+ reject(err);
10060
+ }
10061
+ });
10062
+ }
10063
+ async open(opts) {
10064
+ const cmd = { cmd: "open", ...opts };
10065
+ const res = await this.sendCommand(cmd);
10066
+ if (!res.ok) {
10067
+ throw new Error(res.error);
10068
+ }
10069
+ return res;
10070
+ }
10071
+ async navigate(key, navPath) {
10072
+ const res = await this.sendCommand({ cmd: "navigate", key, path: navPath });
10073
+ if (!res.ok) {
10074
+ throw new Error(res.error);
10075
+ }
10076
+ return res;
10077
+ }
10078
+ async close(key) {
10079
+ const res = await this.sendCommand({ cmd: "close", key });
10080
+ if (!res.ok) {
10081
+ throw new Error(res.error);
10082
+ }
10083
+ }
10084
+ async detach(key) {
10085
+ const res = await this.sendCommand({ cmd: "detach", key });
10086
+ if (!res.ok) {
10087
+ throw new Error(res.error);
10088
+ }
10089
+ }
10090
+ async status() {
10091
+ const res = await this.sendCommand({ cmd: "status" });
10092
+ if (!res.ok) {
10093
+ throw new Error(res.error);
10094
+ }
10095
+ return res;
10096
+ }
10097
+ async ping() {
10098
+ const res = await this.sendCommand({ cmd: "ping" });
10099
+ if (!res.ok) {
10100
+ throw new Error(res.error);
10101
+ }
10102
+ }
10103
+ onEvent(handler) {
10104
+ this.eventHandler = handler;
10105
+ }
10106
+ destroy() {
10107
+ this.eventHandler = null;
10108
+ this.responseQueue = [];
10109
+ if (this.socket) {
10110
+ this.socket.destroy();
10111
+ this.socket = null;
10112
+ }
10113
+ }
10114
+ };
10115
+
10116
+ // ../bitfab-plugin-lib/dist/studioChannel.js
10117
+ var DirectChannel = class {
10118
+ apiKey;
10119
+ serviceUrl;
10120
+ constructor(apiKey, serviceUrl) {
10121
+ this.apiKey = apiKey;
10122
+ this.serviceUrl = serviceUrl;
10123
+ }
10124
+ setApiKey(apiKey) {
10125
+ this.apiKey = apiKey;
10126
+ }
10127
+ async openOrNavigate(path18, opts) {
10128
+ if (!this.apiKey) {
10129
+ const sessionId2 = opts?.sessionId ?? crypto6.randomUUID();
10130
+ const initialPath = path18.startsWith("/studio") ? path18 : "/studio";
10131
+ if (!isValidStudioRoute(initialPath)) {
10132
+ throw new Error(`Studio route not allowed: ${initialPath}`);
10133
+ }
10134
+ const url2 = initialPath.includes("session=") ? `${this.serviceUrl}${initialPath}` : `${this.serviceUrl}${initialPath}${initialPath.includes("?") ? "&" : "?"}session=${encodeURIComponent(sessionId2)}`;
10135
+ const windowPid = openChromelessWindow(url2);
10136
+ writeActiveStudioSession({
10137
+ sessionId: sessionId2,
10138
+ serviceUrl: this.serviceUrl,
10139
+ windowPid,
10140
+ currentPath: initialPath
10141
+ });
10142
+ return { sessionId: sessionId2, opened: true };
10143
+ }
10144
+ const existing = readActiveStudioSession();
10145
+ if (existing && existing.windowState !== "closed") {
10146
+ const ack = await navigateStudioAndAwaitAck({
10147
+ serviceUrl: existing.serviceUrl,
10148
+ apiKey: this.apiKey,
10149
+ sessionId: existing.sessionId
10150
+ }, path18);
10151
+ if (!ack.acked) {
10152
+ throw new StudioNavigationError(ack.reason ?? "unknown", ack.blockedReason, existing.sessionId);
10153
+ }
10154
+ return { sessionId: existing.sessionId, opened: false };
10155
+ }
10156
+ const sessionId = opts?.sessionId ?? crypto6.randomUUID();
10157
+ const session = await createStudioSession({
10158
+ serviceUrl: this.serviceUrl,
10159
+ apiKey: this.apiKey,
10160
+ sessionId,
10161
+ initialPath: path18,
10162
+ clientHeaders: opts?.clientHeaders
10163
+ });
10164
+ return { sessionId: session.sessionId, opened: true };
10165
+ }
10166
+ async close(sessionId, message) {
10167
+ await closeStudio({ serviceUrl: this.serviceUrl, apiKey: this.apiKey, sessionId }, message);
10168
+ }
10169
+ subscribe(sessionId, onEvent, onError) {
10170
+ const abortController = new AbortController();
10171
+ const done = (async () => {
10172
+ const startCursor = await fetchStreamTip({
10173
+ serviceUrl: this.serviceUrl,
10174
+ apiKey: this.apiKey,
10175
+ sessionId
10176
+ });
10177
+ if (abortController.signal.aborted) {
10178
+ return;
10179
+ }
10180
+ await pollAgentSessionEvents({
10181
+ serviceUrl: this.serviceUrl,
10182
+ apiKey: this.apiKey,
10183
+ sessionId,
10184
+ startCursor,
10185
+ abortSignal: abortController.signal,
10186
+ onEvent,
10187
+ onError
10188
+ });
10189
+ })().catch((err) => {
10190
+ if (!abortController.signal.aborted) {
10191
+ onError(err instanceof Error ? err : new Error(String(err)));
10192
+ }
10193
+ });
10194
+ return { abort: () => abortController.abort(), done };
10195
+ }
10196
+ destroy() {
10197
+ }
10198
+ };
10199
+ var DaemonChannel = class _DaemonChannel {
10200
+ client;
10201
+ key;
10202
+ apiKey;
10203
+ serviceUrl;
10204
+ doneResolve = null;
10205
+ constructor(client, key, apiKey, serviceUrl) {
10206
+ this.client = client;
10207
+ this.key = key;
10208
+ this.apiKey = apiKey;
10209
+ this.serviceUrl = serviceUrl;
10210
+ }
10211
+ static async connect(apiKey, serviceUrl) {
10212
+ const client = new DaemonClient();
10213
+ await client.connect();
10214
+ const key = resolveAgentSessionKey();
10215
+ return new _DaemonChannel(client, key, apiKey, serviceUrl);
10216
+ }
10217
+ setApiKey(apiKey) {
10218
+ this.apiKey = apiKey;
10219
+ }
10220
+ async openOrNavigate(path18, opts) {
10221
+ const openRes = await this.client.open({
10222
+ key: this.key,
10223
+ serviceUrl: this.serviceUrl,
10224
+ apiKey: this.apiKey,
10225
+ sessionId: opts?.sessionId,
10226
+ initialPath: path18,
10227
+ clientHeaders: opts?.clientHeaders
10228
+ });
10229
+ if (openRes.reconnected) {
10230
+ const navRes = await this.client.navigate(this.key, path18);
10231
+ if (!navRes.acked) {
10232
+ this.client.destroy();
10233
+ throw new StudioNavigationError(navRes.reason ?? "unknown", void 0, openRes.sessionId);
10234
+ }
10235
+ writeActiveStudioSession({
10236
+ sessionId: openRes.sessionId,
10237
+ serviceUrl: this.serviceUrl,
10238
+ source: "daemon",
10239
+ currentPath: path18
10240
+ });
10241
+ return { sessionId: openRes.sessionId, opened: false };
10242
+ }
10243
+ writeActiveStudioSession({
10244
+ sessionId: openRes.sessionId,
10245
+ serviceUrl: this.serviceUrl,
10246
+ source: "daemon",
10247
+ currentPath: path18
10248
+ });
10249
+ return { sessionId: openRes.sessionId, opened: true };
10250
+ }
10251
+ async close(sessionId, message) {
10252
+ await Promise.all([
10253
+ closeStudio({ serviceUrl: this.serviceUrl, apiKey: this.apiKey, sessionId }, message),
10254
+ this.client.close(this.key).catch(() => {
10255
+ })
10256
+ ]);
10257
+ }
10258
+ subscribe(_sessionId, onEvent, _onError) {
10259
+ const done = new Promise((resolve) => {
10260
+ this.doneResolve = resolve;
10261
+ });
10262
+ this.client.onEvent((push) => {
10263
+ const event = {
10264
+ id: "",
10265
+ type: push.event,
10266
+ data: push.data
10267
+ };
10268
+ onEvent(event);
10269
+ });
10270
+ return {
10271
+ abort: () => this.destroy(),
10272
+ done
10273
+ };
10274
+ }
10275
+ destroy() {
10276
+ this.client.detach(this.key).catch(() => {
10277
+ });
10278
+ this.client.destroy();
10279
+ if (this.doneResolve) {
10280
+ this.doneResolve();
10281
+ this.doneResolve = null;
10282
+ }
10283
+ }
10284
+ };
10285
+ function isDaemonEnabled() {
10286
+ return process.env.BITFAB_STUDIO_DAEMON !== "0";
10287
+ }
10288
+ function withDirectFallback(primary, apiKey, serviceUrl) {
10289
+ let active = primary;
10290
+ return {
10291
+ async openOrNavigate(path18, opts) {
10292
+ try {
10293
+ return await active.openOrNavigate(path18, opts);
10294
+ } catch (err) {
10295
+ if (err instanceof StudioNavigationError || active !== primary) {
10296
+ throw err;
10297
+ }
10298
+ console.error(`daemon failed, falling back to direct: ${err instanceof Error ? err.message : String(err)}`);
10299
+ active.destroy();
10300
+ active = new DirectChannel(apiKey, serviceUrl);
10301
+ return active.openOrNavigate(path18, opts);
10302
+ }
10303
+ },
10304
+ async close(sessionId, message) {
10305
+ await active.close(sessionId, message);
10306
+ },
10307
+ subscribe(sessionId, onEvent, onError) {
10308
+ return active.subscribe(sessionId, onEvent, onError);
10309
+ },
10310
+ setApiKey(key) {
10311
+ active.setApiKey(key);
10312
+ },
10313
+ destroy() {
10314
+ active.destroy();
10315
+ }
10316
+ };
10317
+ }
10318
+ async function resolveChannel(apiKey, serviceUrl) {
10319
+ if (isDaemonEnabled()) {
10320
+ try {
10321
+ const daemon = await DaemonChannel.connect(apiKey, serviceUrl);
10322
+ return withDirectFallback(daemon, apiKey, serviceUrl);
10323
+ } catch (err) {
10324
+ if (err instanceof StudioNavigationError) {
10325
+ throw err;
10326
+ }
10327
+ if (!(err instanceof DaemonUnavailableError)) {
10328
+ console.error(`daemon unavailable, falling back to direct: ${err instanceof Error ? err.message : String(err)}`);
10329
+ }
10330
+ }
10331
+ }
10332
+ return new DirectChannel(apiKey, serviceUrl);
10333
+ }
10334
+
9940
10335
  // ../bitfab-plugin-lib/dist/replayCapabilities.js
9941
10336
  var semver = __toESM(require_semver2(), 1);
9942
10337
 
@@ -9945,12 +10340,12 @@ var semver3 = __toESM(require_semver2(), 1);
9945
10340
 
9946
10341
  // ../bitfab-plugin-lib/dist/installedSdk.js
9947
10342
  var semver2 = __toESM(require_semver2(), 1);
9948
- import fs9 from "fs";
9949
- import path7 from "path";
10343
+ import fs11 from "fs";
10344
+ import path10 from "path";
9950
10345
 
9951
10346
  // ../bitfab-plugin-lib/dist/workspaces.js
9952
- import fs8 from "fs";
9953
- import path6 from "path";
10347
+ import fs10 from "fs";
10348
+ import path9 from "path";
9954
10349
 
9955
10350
  // ../bitfab-plugin-lib/dist/analytics.js
9956
10351
  async function reportHandoff(apiKey, pluginVersion, platform2, body) {
@@ -9974,11 +10369,11 @@ async function reportHandoff(apiKey, pluginVersion, platform2, body) {
9974
10369
  }
9975
10370
 
9976
10371
  // ../bitfab-plugin-lib/dist/commands/openStudioTo.js
9977
- import crypto4 from "crypto";
10372
+ import crypto7 from "crypto";
9978
10373
 
9979
10374
  // ../bitfab-plugin-lib/dist/frontmostApp.js
9980
10375
  import { execFileSync as execFileSync3, spawn as spawn2 } from "child_process";
9981
- import os8 from "os";
10376
+ import os10 from "os";
9982
10377
  function findAncestorApp() {
9983
10378
  try {
9984
10379
  let pid = process.ppid;
@@ -10039,7 +10434,7 @@ function focusItermSession(sessionId) {
10039
10434
  spawnQuiet("osascript", ["-e", script]);
10040
10435
  }
10041
10436
  function getFrontmostApp() {
10042
- const platform2 = os8.platform();
10437
+ const platform2 = os10.platform();
10043
10438
  try {
10044
10439
  if (platform2 === "darwin") {
10045
10440
  return execFileSync3("osascript", [
@@ -10073,7 +10468,7 @@ function focusApp(identifier) {
10073
10468
  if (!identifier) {
10074
10469
  return;
10075
10470
  }
10076
- const platform2 = os8.platform();
10471
+ const platform2 = os10.platform();
10077
10472
  if (platform2 === "darwin") {
10078
10473
  spawnQuiet("osascript", [
10079
10474
  "-e",
@@ -10102,145 +10497,144 @@ function focusApp(identifier) {
10102
10497
  spawnQuiet("powershell.exe", ["-NoProfile", "-Command", script]);
10103
10498
  }
10104
10499
  }
10105
- function recordFocus() {
10106
- const platform2 = os8.platform();
10107
- if (platform2 === "linux") {
10108
- const windowId = process.env.WINDOWID;
10109
- if (windowId) {
10110
- return () => spawnQuiet("xdotool", ["windowactivate", windowId]);
10111
- }
10112
- }
10113
- if (platform2 === "darwin") {
10114
- const app = findAncestorApp();
10115
- if (app) {
10116
- const itermSessionId = process.env.ITERM_SESSION_ID ?? null;
10117
- return () => focusAncestorApp(app, itermSessionId);
10118
- }
10119
- }
10120
- const frontmost = getFrontmostApp();
10121
- return () => focusApp(frontmost);
10122
- }
10123
-
10124
- // ../bitfab-plugin-lib/dist/commands/sessionReady.js
10125
- var SESSION_READY_TIMEOUT_MS = 3e4;
10126
- var SESSION_READY_POLL_MS = 500;
10127
- async function waitForSessionReady(opts) {
10128
- const deadline = Date.now() + SESSION_READY_TIMEOUT_MS;
10129
- let since = null;
10130
- while (Date.now() < deadline) {
10131
- try {
10132
- const url2 = new URL(`${opts.serviceUrl}/api/studio/events`);
10133
- url2.searchParams.set("session", opts.sessionId);
10134
- if (since) {
10135
- url2.searchParams.set("since", since);
10136
- }
10137
- const res = await fetch(url2.toString(), {
10138
- headers: { Authorization: `Bearer ${opts.apiKey}` }
10139
- });
10140
- if (res.ok) {
10141
- const body = await res.json();
10142
- for (const event of body.events) {
10143
- since = event.id;
10144
- if (event.type === "studio:session-started") {
10145
- return;
10146
- }
10147
- }
10148
- }
10149
- } catch {
10500
+ function recordFocus() {
10501
+ const platform2 = os10.platform();
10502
+ if (platform2 === "linux") {
10503
+ const windowId = process.env.WINDOWID;
10504
+ if (windowId) {
10505
+ return () => spawnQuiet("xdotool", ["windowactivate", windowId]);
10506
+ }
10507
+ }
10508
+ if (platform2 === "darwin") {
10509
+ const app = findAncestorApp();
10510
+ if (app) {
10511
+ const itermSessionId = process.env.ITERM_SESSION_ID ?? null;
10512
+ return () => focusAncestorApp(app, itermSessionId);
10150
10513
  }
10151
- await new Promise((resolve) => setTimeout(resolve, SESSION_READY_POLL_MS));
10152
10514
  }
10153
- console.error("Warning: Studio session did not confirm readiness within 30s. Proceeding anyway.");
10515
+ const frontmost = getFrontmostApp();
10516
+ return () => focusApp(frontmost);
10154
10517
  }
10155
10518
 
10156
10519
  // ../bitfab-plugin-lib/dist/commands/openStudioTo.js
10520
+ var WINDOW_CLOSE_GRACE_PERIOD_MS = 1e4;
10157
10521
  var LOGIN_TIMEOUT_MS = 10 * 60 * 1e3;
10158
- var BLOCKING_NOTICE_UNTIL = "the Studio interaction completes or the session ends";
10159
- var REAUTH_ACK_TIMEOUT_MS = 2e4;
10160
- var StudioNavigationError = class extends Error {
10161
- reason;
10162
- blockedReason;
10163
- staleSessionId;
10164
- constructor(reason, blockedReason, staleSessionId) {
10165
- super(blockedReason ? `Studio navigation failed (${reason}): ${blockedReason}` : `Studio navigation failed: ${reason}`);
10166
- this.reason = reason;
10167
- this.blockedReason = blockedReason;
10168
- this.staleSessionId = staleSessionId;
10169
- this.name = "StudioNavigationError";
10170
- }
10171
- };
10172
- async function openStudioTo(path15, opts = {}) {
10522
+ function createEventSubscription(channel, sessionId, onEvent, restoreFocus) {
10523
+ let graceTimer = null;
10524
+ let endedNotified = false;
10525
+ const clearGraceTimer = () => {
10526
+ if (graceTimer) {
10527
+ clearTimeout(graceTimer);
10528
+ graceTimer = null;
10529
+ }
10530
+ };
10531
+ const notifyEnded = (baseEvent) => {
10532
+ if (!endedNotified) {
10533
+ endedNotified = true;
10534
+ restoreFocus();
10535
+ onEvent({ ...baseEvent, type: "studio:session-ended" });
10536
+ }
10537
+ clearGraceTimer();
10538
+ sub.abort();
10539
+ };
10540
+ const sub = channel.subscribe(sessionId, (event) => {
10541
+ if (event.type === "studio:window-closed") {
10542
+ clearGraceTimer();
10543
+ graceTimer = setTimeout(() => {
10544
+ graceTimer = null;
10545
+ updateActiveStudioSession({ windowState: "closed" });
10546
+ notifyEnded(event);
10547
+ }, WINDOW_CLOSE_GRACE_PERIOD_MS);
10548
+ return;
10549
+ }
10550
+ if (event.type === "studio:session-started") {
10551
+ if (graceTimer) {
10552
+ clearGraceTimer();
10553
+ console.error("Studio reconnected after page refresh");
10554
+ }
10555
+ return;
10556
+ }
10557
+ if (event.type === "studio:session-ended") {
10558
+ clearGraceTimer();
10559
+ notifyEnded(event);
10560
+ return;
10561
+ }
10562
+ if (event.type === "studio:session-closed") {
10563
+ return;
10564
+ }
10565
+ if (event.type === "studio:not-member") {
10566
+ restoreFocus();
10567
+ onEvent(event);
10568
+ sub.abort();
10569
+ return;
10570
+ }
10571
+ if (event.type === "studio:return-to-agent" || event.type === "studio:edit-with-agent") {
10572
+ restoreFocus();
10573
+ }
10574
+ onEvent(event);
10575
+ }, (err) => {
10576
+ console.error(`studio event stream: ${err.message}`);
10577
+ });
10578
+ return {
10579
+ abort: () => {
10580
+ clearGraceTimer();
10581
+ sub.abort();
10582
+ },
10583
+ done: sub.done
10584
+ };
10585
+ }
10586
+ async function openStudioTo(path18, opts = {}) {
10173
10587
  const config2 = getConfig();
10174
10588
  const serviceUrl = config2.serviceUrl;
10175
10589
  const apiKey = !opts.forceLogin && hasCredentials() ? config2.apiKey : null;
10176
10590
  const noop = () => {
10177
10591
  };
10178
10592
  const restoreFocus = recordFocus();
10593
+ const channel = await resolveChannel(apiKey ?? "", serviceUrl);
10179
10594
  if (!apiKey) {
10180
- const existing2 = readActiveStudioSession();
10181
- if (existing2) {
10182
- const reauth = await requestSessionReauth({
10183
- serviceUrl: existing2.serviceUrl,
10184
- sessionId: existing2.sessionId,
10185
- redirectPath: path15
10186
- });
10187
- if (!reauth.ok) {
10188
- throw new StudioNavigationError("push-failed", void 0, existing2.sessionId);
10189
- }
10190
- return loginViaExistingWindow({
10191
- serviceUrl: existing2.serviceUrl,
10192
- sessionId: existing2.sessionId,
10193
- startCursor: reauth.pushedEventId,
10194
- signInUrl: reauth.signInUrl,
10195
- restoreFocus,
10196
- opts
10197
- });
10198
- }
10199
- const sessionId2 = crypto4.randomUUID();
10200
- const freshPath = opts.freshWindowPath ?? path15;
10201
- const separator = freshPath.includes("?") ? "&" : "?";
10202
- const redirectPath = `${freshPath}${separator}session=${encodeURIComponent(sessionId2)}&pluginLogin=true`;
10203
- const signInPath = `/studio/sign-in?redirect_url=${encodeURIComponent(redirectPath)}`;
10204
- const signInUrl = `${serviceUrl}${signInPath}&session=${encodeURIComponent(sessionId2)}`;
10205
- const windowPid = openChromelessWindow(signInUrl);
10206
- writeActiveStudioSession({
10207
- sessionId: sessionId2,
10208
- serviceUrl,
10209
- windowPid: windowPid ?? void 0
10595
+ const sessionId2 = crypto7.randomUUID();
10596
+ const destination = opts.freshWindowPath ?? path18;
10597
+ const destSeparator = destination.includes("?") ? "&" : "?";
10598
+ const redirectPath = `${destination}${destSeparator}session=${encodeURIComponent(sessionId2)}&pluginLogin=true`;
10599
+ const signInInitialPath = `/studio/sign-in?redirect_url=${encodeURIComponent(redirectPath)}&session=${encodeURIComponent(sessionId2)}`;
10600
+ await channel.openOrNavigate(signInInitialPath, {
10601
+ clientHeaders: opts.clientHeaders,
10602
+ sessionId: sessionId2
10210
10603
  });
10604
+ const signInUrl = `${serviceUrl}${signInInitialPath}`;
10211
10605
  opts.onLoginRequired?.(sessionId2, signInUrl);
10212
- const abortController = new AbortController();
10213
- const timer = setTimeout(() => abortController.abort(), LOGIN_TIMEOUT_MS);
10214
- const keepalive = setInterval(() => process.stderr.write(""), 3e4);
10215
- let loginApiKey;
10216
- try {
10217
- loginApiKey = await pollLoginEvents({
10218
- serviceUrl,
10219
- sessionId: sessionId2,
10220
- abortSignal: abortController.signal
10606
+ const loginApiKey = await new Promise((resolve, reject) => {
10607
+ const timer = setTimeout(() => {
10608
+ sub.abort();
10609
+ reject(new Error("Login polling aborted"));
10610
+ }, LOGIN_TIMEOUT_MS);
10611
+ const keepalive = setInterval(() => process.stderr.write(""), 3e4);
10612
+ const cleanup = () => {
10613
+ clearTimeout(timer);
10614
+ clearInterval(keepalive);
10615
+ };
10616
+ const sub = channel.subscribe(sessionId2, (event) => {
10617
+ if (event.type === "studio:authenticated" && typeof event.data?.token === "string") {
10618
+ cleanup();
10619
+ sub.abort();
10620
+ resolve(event.data.token);
10621
+ }
10622
+ }, (err) => {
10623
+ cleanup();
10624
+ reject(err);
10221
10625
  });
10222
- } catch (err) {
10223
- clearTimeout(timer);
10224
- clearInterval(keepalive);
10225
- throw err;
10226
- }
10227
- clearTimeout(timer);
10228
- clearInterval(keepalive);
10626
+ });
10229
10627
  saveCredentials(loginApiKey);
10230
10628
  opts.onAuthenticated?.(sessionId2);
10629
+ channel.setApiKey(loginApiKey);
10231
10630
  if (opts.onEvent) {
10232
- await waitForSessionReady({
10233
- serviceUrl,
10234
- apiKey: loginApiKey,
10235
- sessionId: sessionId2
10236
- });
10237
- const poller2 = startEventPoller(serviceUrl, loginApiKey, sessionId2, opts.onEvent, restoreFocus, { claim: opts.monitor !== "wait" });
10631
+ const subscription2 = createEventSubscription(channel, sessionId2, opts.onEvent, restoreFocus);
10238
10632
  return {
10239
10633
  sessionId: sessionId2,
10240
10634
  serviceUrl,
10241
10635
  apiKey: loginApiKey,
10242
10636
  opened: true,
10243
- ...poller2
10637
+ ...subscription2
10244
10638
  };
10245
10639
  }
10246
10640
  restoreFocus();
@@ -10253,299 +10647,20 @@ async function openStudioTo(path15, opts = {}) {
10253
10647
  done: Promise.resolve()
10254
10648
  };
10255
10649
  }
10256
- const existing = readActiveStudioSession();
10257
- if (existing) {
10258
- const client = {
10259
- serviceUrl: existing.serviceUrl,
10260
- apiKey,
10261
- sessionId: existing.sessionId
10262
- };
10263
- const ack = await navigateStudioAndAwaitAck(client, path15);
10264
- if (!ack.acked) {
10265
- throw new StudioNavigationError(ack.reason ?? "unknown", ack.blockedReason, existing.sessionId);
10266
- }
10267
- if (opts.onEvent && opts.monitor === "wait") {
10268
- const poller2 = startEventPoller(existing.serviceUrl, apiKey, existing.sessionId, opts.onEvent, restoreFocus, { claim: false });
10269
- return {
10270
- sessionId: existing.sessionId,
10271
- serviceUrl: existing.serviceUrl,
10272
- apiKey,
10273
- opened: false,
10274
- ...poller2
10275
- };
10276
- }
10277
- if (opts.onEvent && opts.monitor === "start" && tryClaimStudioMonitor()) {
10278
- const poller2 = startEventPoller(existing.serviceUrl, apiKey, existing.sessionId, opts.onEvent, restoreFocus, { claim: true });
10279
- return {
10280
- sessionId: existing.sessionId,
10281
- serviceUrl: existing.serviceUrl,
10282
- apiKey,
10283
- opened: false,
10284
- ...poller2
10285
- };
10286
- }
10287
- restoreFocus();
10288
- return {
10289
- sessionId: existing.sessionId,
10290
- serviceUrl: existing.serviceUrl,
10291
- apiKey,
10292
- opened: false,
10293
- abort: noop,
10294
- done: Promise.resolve()
10295
- };
10296
- }
10297
- const sessionId = crypto4.randomUUID();
10298
- const session = await createStudioSession({
10299
- serviceUrl,
10300
- apiKey,
10301
- sessionId,
10302
- initialPath: path15,
10650
+ const { sessionId, opened } = await channel.openOrNavigate(path18, {
10303
10651
  clientHeaders: opts.clientHeaders
10304
10652
  });
10305
- const poller = opts.onEvent ? startEventPoller(session.serviceUrl, apiKey, session.sessionId, opts.onEvent, restoreFocus, { claim: opts.monitor !== "wait" }) : { abort: noop, done: Promise.resolve() };
10306
- return {
10307
- sessionId: session.sessionId,
10308
- serviceUrl: session.serviceUrl,
10309
- apiKey,
10310
- opened: true,
10311
- ...poller
10312
- };
10313
- }
10314
- async function loginViaExistingWindow(args) {
10315
- const { serviceUrl, sessionId } = args;
10316
- const noop = () => {
10317
- };
10318
- const keepalive = setInterval(() => process.stderr.write(""), 3e4);
10319
- let loginApiKey;
10320
- try {
10321
- loginApiKey = await awaitReauthLogin({
10322
- serviceUrl,
10323
- sessionId,
10324
- startCursor: args.startCursor,
10325
- // Only fired once the sign-in page proved the window alive AND is
10326
- // waiting on the user; a still-valid browser session re-auths silently
10327
- // and the caller never surfaces a sign-in prompt.
10328
- onAck: () => args.opts.onLoginRequired?.(sessionId, args.signInUrl)
10329
- });
10330
- } finally {
10331
- clearInterval(keepalive);
10332
- }
10333
- saveCredentials(loginApiKey);
10334
- args.opts.onAuthenticated?.(sessionId);
10335
- if (args.opts.onEvent && (args.opts.monitor === "wait" || args.opts.monitor === "start" && tryClaimStudioMonitor())) {
10336
- await waitForSessionReady({
10337
- serviceUrl,
10338
- apiKey: loginApiKey,
10339
- sessionId
10340
- });
10341
- const poller = startEventPoller(serviceUrl, loginApiKey, sessionId, args.opts.onEvent, args.restoreFocus, { claim: args.opts.monitor !== "wait" });
10342
- return {
10343
- sessionId,
10344
- serviceUrl,
10345
- apiKey: loginApiKey,
10346
- opened: false,
10347
- ...poller
10348
- };
10349
- }
10350
- args.restoreFocus();
10653
+ const subscription = opts.onEvent ? createEventSubscription(channel, sessionId, opts.onEvent, restoreFocus) : { abort: () => channel.destroy(), done: Promise.resolve() };
10351
10654
  return {
10352
10655
  sessionId,
10353
10656
  serviceUrl,
10354
- apiKey: loginApiKey,
10355
- opened: false,
10356
- abort: noop,
10357
- done: Promise.resolve()
10358
- };
10359
- }
10360
- function awaitReauthLogin(args) {
10361
- return new Promise((resolve, reject) => {
10362
- const abortController = new AbortController();
10363
- let settled = false;
10364
- let acked = false;
10365
- const settle = (fn) => {
10366
- if (settled) {
10367
- return;
10368
- }
10369
- settled = true;
10370
- clearTimeout(ackTimer);
10371
- clearTimeout(loginTimer);
10372
- abortController.abort();
10373
- fn();
10374
- };
10375
- const ackTimer = setTimeout(() => {
10376
- settle(() => reject(new StudioNavigationError("timeout", void 0, args.sessionId)));
10377
- }, REAUTH_ACK_TIMEOUT_MS);
10378
- const loginTimer = setTimeout(() => {
10379
- settle(() => reject(new Error("Login polling aborted")));
10380
- }, LOGIN_TIMEOUT_MS);
10381
- pollSessionAuthEvents({
10382
- serviceUrl: args.serviceUrl,
10383
- sessionId: args.sessionId,
10384
- startCursor: args.startCursor,
10385
- abortSignal: abortController.signal,
10386
- onEvent: (event) => {
10387
- if (event.type === "studio:auth-required" && !acked) {
10388
- acked = true;
10389
- clearTimeout(ackTimer);
10390
- args.onAck();
10391
- return;
10392
- }
10393
- if (event.type === "studio:authenticated") {
10394
- const token = event.data.token;
10395
- if (typeof token === "string") {
10396
- settle(() => resolve(token));
10397
- }
10398
- }
10399
- }
10400
- }).catch((err) => {
10401
- settle(() => reject(err instanceof Error ? err : new Error(String(err))));
10402
- });
10403
- });
10404
- }
10405
- function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus, { claim = true, fromStreamStart = false, graceMs, forceClear = false } = {}) {
10406
- printBlockingNotice(BLOCKING_NOTICE_UNTIL);
10407
- if (claim) {
10408
- claimStudioMonitor();
10409
- }
10410
- const abortController = new AbortController();
10411
- let endedNotified = false;
10412
- const releaseLock = () => {
10413
- if (claim) {
10414
- releaseStudioMonitor();
10415
- }
10416
- };
10417
- const notifyEnded = (baseEvent) => {
10418
- if (!endedNotified) {
10419
- endedNotified = true;
10420
- restoreFocus();
10421
- onEvent({ ...baseEvent, type: "studio:session-ended" });
10422
- }
10423
- releaseLock();
10424
- abortController.abort();
10425
- };
10426
- const arbiter = createWindowCloseArbiter({
10427
- onConfirmed: (event) => {
10428
- if (forceClear) {
10429
- clearActiveStudioSession(sessionId, { force: true });
10430
- } else {
10431
- clearActiveStudioSession(sessionId);
10432
- }
10433
- notifyEnded(event);
10434
- },
10435
- onRefuted: () => {
10436
- console.error("Studio reconnected after page refresh");
10437
- }
10438
- }, graceMs);
10439
- const done = (async () => {
10440
- const startCursor = fromStreamStart ? void 0 : await fetchStreamTip({ serviceUrl, apiKey, sessionId });
10441
- if (abortController.signal.aborted) {
10442
- return;
10443
- }
10444
- await pollAgentSessionEvents({
10445
- serviceUrl,
10446
- apiKey,
10447
- sessionId,
10448
- startCursor,
10449
- abortSignal: abortController.signal,
10450
- onEvent: (event) => {
10451
- if (event.type === "studio:window-closed" || event.type === "studio:session-started") {
10452
- arbiter.handleEvent(event);
10453
- return;
10454
- }
10455
- if (event.type === "studio:session-ended") {
10456
- arbiter.cancel();
10457
- notifyEnded(event);
10458
- return;
10459
- }
10460
- if (event.type === "studio:session-closed") {
10461
- return;
10462
- }
10463
- if (event.type === "studio:not-member") {
10464
- restoreFocus();
10465
- onEvent(event);
10466
- releaseLock();
10467
- abortController.abort();
10468
- return;
10469
- }
10470
- if (event.type === "studio:return-to-agent" || event.type === "studio:edit-with-agent") {
10471
- restoreFocus();
10472
- }
10473
- onEvent(event);
10474
- },
10475
- onError: (err) => {
10476
- console.error(`studio agent-event poll: ${err.message}`);
10477
- }
10478
- });
10479
- })().catch((err) => {
10480
- if (!abortController.signal.aborted) {
10481
- console.error(`studio agent-event poll terminated: ${err.message}`);
10482
- }
10483
- releaseLock();
10484
- });
10485
- return {
10486
- abort: () => {
10487
- releaseLock();
10488
- abortController.abort();
10489
- },
10490
- done
10491
- };
10492
- }
10493
- var LOGIN_MONITOR_DRAIN_MS = 8e3;
10494
- async function runLoginWindowMonitor(sessionId) {
10495
- const config2 = getConfig();
10496
- const apiKey = config2.apiKey;
10497
- if (!apiKey) {
10498
- return;
10499
- }
10500
- const poller = startEventPoller(
10501
- config2.serviceUrl,
10502
10657
  apiKey,
10503
- sessionId,
10504
- () => {
10505
- },
10506
- () => {
10507
- },
10508
- // claim: false — this is a transient cleanup reader with a no-op event
10509
- // handler, NOT the session's real monitor. If it claimed the lock, a
10510
- // subsequent `openStudioTo` reuse with monitor:"start" (e.g. a flow's
10511
- // backgrounded open right after a PARKED login) would lose the atomic
10512
- // claim, assume a live monitor is forwarding events, and fire-and-exit —
10513
- // leaving the session with no real monitor while this one forwards
10514
- // nothing. Reading without claiming lets it clean up the pointer
10515
- // (window-closed → clear) without shadowing the real monitor.
10516
- //
10517
- // forceClear: the clear runs in a detached child while the login/init
10518
- // parent that WROTE the pointer may still be alive (init keeps running with
10519
- // exitOnComplete:false, even spawnSync-ing the editor), so a non-force clear
10520
- // would be refused by the writer-alive guard and the closed window's
10521
- // pointer would leak. Safe — the clear still matches on the unique
10522
- // sessionId.
10523
- { claim: false, fromStreamStart: true, graceMs: 0, forceClear: true }
10524
- );
10525
- let drainTimer;
10526
- const drain = new Promise((resolve) => {
10527
- drainTimer = setTimeout(resolve, LOGIN_MONITOR_DRAIN_MS);
10528
- });
10529
- await Promise.race([poller.done, drain]);
10530
- if (drainTimer) {
10531
- clearTimeout(drainTimer);
10532
- }
10533
- poller.abort();
10658
+ opened,
10659
+ ...subscription
10660
+ };
10534
10661
  }
10535
10662
 
10536
10663
  // ../bitfab-plugin-lib/dist/commands/login.js
10537
- function trySpawnDetachedMonitor(sessionId) {
10538
- try {
10539
- const pid = spawnDetached(process.execPath, [
10540
- ...process.argv.slice(1),
10541
- "--drain",
10542
- sessionId
10543
- ]);
10544
- return pid != null;
10545
- } catch {
10546
- return false;
10547
- }
10548
- }
10549
10664
  async function verifyToken(serviceUrl, token) {
10550
10665
  try {
10551
10666
  const res = await fetch(`${serviceUrl}/api/plugin/whoami`, {
@@ -10561,23 +10676,6 @@ async function verifyToken(serviceUrl, token) {
10561
10676
  }
10562
10677
  async function runLogin(platform2, pluginVersion, options) {
10563
10678
  const exitOnComplete = options?.exitOnComplete ?? true;
10564
- const drainIdx = process.argv.indexOf("--drain");
10565
- if (drainIdx !== -1) {
10566
- const sessionId = process.argv[drainIdx + 1];
10567
- if (!sessionId) {
10568
- throw new Error("--drain requires a sessionId argument");
10569
- }
10570
- await runLoginWindowMonitor(sessionId);
10571
- if (exitOnComplete) {
10572
- process.exit(0);
10573
- }
10574
- return;
10575
- }
10576
- if (exitOnComplete) {
10577
- printHelpIfRequested({
10578
- description: "Sign in to Bitfab. Opens Studio in the browser for authentication and saves the API key locally. Takes no arguments."
10579
- });
10580
- }
10581
10679
  const force = options?.force ?? process.argv.includes("--force");
10582
10680
  const config2 = getConfig();
10583
10681
  if (!force && hasCredentials() && config2.apiKey) {
@@ -10617,9 +10715,7 @@ ${signInUrl}`);
10617
10715
  ${greeting}`);
10618
10716
  console.log("Bitfab MCP tools are now active. (via studio)");
10619
10717
  }
10620
- if (!trySpawnDetachedMonitor(result.sessionId)) {
10621
- await runLoginWindowMonitor(result.sessionId);
10622
- }
10718
+ result.abort();
10623
10719
  if (exitOnComplete) {
10624
10720
  process.exit(0);
10625
10721
  }
@@ -11424,10 +11520,10 @@ function mergeDefs(...defs) {
11424
11520
  function cloneDef(schema) {
11425
11521
  return mergeDefs(schema._zod.def);
11426
11522
  }
11427
- function getElementAtPath(obj, path15) {
11428
- if (!path15)
11523
+ function getElementAtPath(obj, path18) {
11524
+ if (!path18)
11429
11525
  return obj;
11430
- return path15.reduce((acc, key) => acc?.[key], obj);
11526
+ return path18.reduce((acc, key) => acc?.[key], obj);
11431
11527
  }
11432
11528
  function promiseAllObject(promisesObj) {
11433
11529
  const keys = Object.keys(promisesObj);
@@ -11810,11 +11906,11 @@ function aborted(x, startIndex = 0) {
11810
11906
  }
11811
11907
  return false;
11812
11908
  }
11813
- function prefixIssues(path15, issues) {
11909
+ function prefixIssues(path18, issues) {
11814
11910
  return issues.map((iss) => {
11815
11911
  var _a2;
11816
11912
  (_a2 = iss).path ?? (_a2.path = []);
11817
- iss.path.unshift(path15);
11913
+ iss.path.unshift(path18);
11818
11914
  return iss;
11819
11915
  });
11820
11916
  }
@@ -11997,7 +12093,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
11997
12093
  }
11998
12094
  function treeifyError(error48, mapper = (issue2) => issue2.message) {
11999
12095
  const result = { errors: [] };
12000
- const processError = (error49, path15 = []) => {
12096
+ const processError = (error49, path18 = []) => {
12001
12097
  var _a2, _b;
12002
12098
  for (const issue2 of error49.issues) {
12003
12099
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -12007,7 +12103,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
12007
12103
  } else if (issue2.code === "invalid_element") {
12008
12104
  processError({ issues: issue2.issues }, issue2.path);
12009
12105
  } else {
12010
- const fullpath = [...path15, ...issue2.path];
12106
+ const fullpath = [...path18, ...issue2.path];
12011
12107
  if (fullpath.length === 0) {
12012
12108
  result.errors.push(mapper(issue2));
12013
12109
  continue;
@@ -12039,8 +12135,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
12039
12135
  }
12040
12136
  function toDotPath(_path) {
12041
12137
  const segs = [];
12042
- const path15 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
12043
- for (const seg of path15) {
12138
+ const path18 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
12139
+ for (const seg of path18) {
12044
12140
  if (typeof seg === "number")
12045
12141
  segs.push(`[${seg}]`);
12046
12142
  else if (typeof seg === "symbol")
@@ -24017,13 +24113,13 @@ function resolveRef(ref, ctx) {
24017
24113
  if (!ref.startsWith("#")) {
24018
24114
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
24019
24115
  }
24020
- const path15 = ref.slice(1).split("/").filter(Boolean);
24021
- if (path15.length === 0) {
24116
+ const path18 = ref.slice(1).split("/").filter(Boolean);
24117
+ if (path18.length === 0) {
24022
24118
  return ctx.rootSchema;
24023
24119
  }
24024
24120
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
24025
- if (path15[0] === defsKey) {
24026
- const key = path15[1];
24121
+ if (path18[0] === defsKey) {
24122
+ const key = path18[1];
24027
24123
  if (!key || !ctx.defs[key]) {
24028
24124
  throw new Error(`Reference not found: ${ref}`);
24029
24125
  }
@@ -24455,18 +24551,18 @@ var inputFileSchema = external_exports.object({
24455
24551
  import { spawn as spawn3 } from "child_process";
24456
24552
 
24457
24553
  // ../bitfab-plugin-lib/dist/activePreviewSession.js
24458
- import fs10 from "fs";
24459
- import os9 from "os";
24460
- import path8 from "path";
24461
- var FILE_PATH = path8.join(os9.homedir(), ".config", "bitfab", "active-preview-session.json");
24554
+ import fs12 from "fs";
24555
+ import os11 from "os";
24556
+ import path11 from "path";
24557
+ var FILE_PATH = path11.join(os11.homedir(), ".config", "bitfab", "active-preview-session.json");
24462
24558
 
24463
24559
  // ../bitfab-plugin-lib/dist/commands/status.js
24464
- import path10 from "path";
24560
+ import path13 from "path";
24465
24561
 
24466
24562
  // ../bitfab-plugin-lib/dist/updates.js
24467
- import fs11 from "fs";
24468
- import os10 from "os";
24469
- import path9 from "path";
24563
+ import fs13 from "fs";
24564
+ import os12 from "os";
24565
+ import path12 from "path";
24470
24566
 
24471
24567
  // ../bitfab-plugin-lib/dist/commands/update.js
24472
24568
  import { execSync as execSync2 } from "child_process";
@@ -24475,15 +24571,15 @@ import { execSync as execSync2 } from "child_process";
24475
24571
  var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
24476
24572
 
24477
24573
  // ../bitfab-plugin-lib/dist/hooks/captureHook.js
24478
- import fs12 from "fs";
24574
+ import fs14 from "fs";
24479
24575
 
24480
24576
  // ../bitfab-plugin-lib/dist/hooks/userPromptSubmit.js
24481
- import fs13 from "fs";
24577
+ import fs15 from "fs";
24482
24578
 
24483
24579
  // ../bitfab-plugin-lib/dist/installScope.js
24484
- import fs14 from "fs";
24485
- import os11 from "os";
24486
- import path11 from "path";
24580
+ import fs16 from "fs";
24581
+ import os13 from "os";
24582
+ import path14 from "path";
24487
24583
 
24488
24584
  // ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/helpers/util.js
24489
24585
  var util;
@@ -24844,8 +24940,8 @@ function getErrorMap2() {
24844
24940
 
24845
24941
  // ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/helpers/parseUtil.js
24846
24942
  var makeIssue = (params) => {
24847
- const { data, path: path15, errorMaps, issueData } = params;
24848
- const fullPath = [...path15, ...issueData.path || []];
24943
+ const { data, path: path18, errorMaps, issueData } = params;
24944
+ const fullPath = [...path18, ...issueData.path || []];
24849
24945
  const fullIssue = {
24850
24946
  ...issueData,
24851
24947
  path: fullPath
@@ -24960,11 +25056,11 @@ var errorUtil;
24960
25056
 
24961
25057
  // ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/types.js
24962
25058
  var ParseInputLazyPath = class {
24963
- constructor(parent, value, path15, key) {
25059
+ constructor(parent, value, path18, key) {
24964
25060
  this._cachedPath = [];
24965
25061
  this.parent = parent;
24966
25062
  this.data = value;
24967
- this._path = path15;
25063
+ this._path = path18;
24968
25064
  this._key = key;
24969
25065
  }
24970
25066
  get path() {
@@ -29858,15 +29954,11 @@ var McpZodTypeKind;
29858
29954
  // ../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
29859
29955
  import process3 from "process";
29860
29956
 
29861
- // src/init.ts
29862
- import { execSync as execSync3 } from "child_process";
29863
- import * as p4 from "@clack/prompts";
29864
-
29865
29957
  // src/claude.ts
29866
29958
  import { spawnSync } from "child_process";
29867
- import fs15 from "fs";
29868
- import os12 from "os";
29869
- import path12 from "path";
29959
+ import fs17 from "fs";
29960
+ import os14 from "os";
29961
+ import path15 from "path";
29870
29962
  import * as p from "@clack/prompts";
29871
29963
  var REPO = "Project-White-Rabbit/bitfab-claude-plugin";
29872
29964
  var MARKETPLACE = "bitfab";
@@ -29886,7 +29978,7 @@ function runClaudeInstall() {
29886
29978
  }
29887
29979
  s.start("Enabling auto-updates");
29888
29980
  const changed = enableAutoUpdate(
29889
- path12.join(os12.homedir(), ".claude", "settings.json")
29981
+ path15.join(os14.homedir(), ".claude", "settings.json")
29890
29982
  );
29891
29983
  if (changed) {
29892
29984
  s.stop("Auto-updates enabled");
@@ -29977,8 +30069,8 @@ function runClaude(args) {
29977
30069
  }
29978
30070
  function enableAutoUpdate(settingsPath) {
29979
30071
  let settings = {};
29980
- if (fs15.existsSync(settingsPath)) {
29981
- const raw = fs15.readFileSync(settingsPath, "utf-8");
30072
+ if (fs17.existsSync(settingsPath)) {
30073
+ const raw = fs17.readFileSync(settingsPath, "utf-8");
29982
30074
  settings = raw.trim() === "" ? {} : JSON.parse(raw);
29983
30075
  }
29984
30076
  const marketplaces = settings.extraKnownMarketplaces ?? {};
@@ -29992,17 +30084,17 @@ function enableAutoUpdate(settingsPath) {
29992
30084
  autoUpdate: true
29993
30085
  };
29994
30086
  settings.extraKnownMarketplaces = marketplaces;
29995
- fs15.mkdirSync(path12.dirname(settingsPath), { recursive: true });
29996
- fs15.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
30087
+ fs17.mkdirSync(path15.dirname(settingsPath), { recursive: true });
30088
+ fs17.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
29997
30089
  `);
29998
30090
  return true;
29999
30091
  }
30000
30092
 
30001
30093
  // src/codex.ts
30002
30094
  import { spawnSync as spawnSync2 } from "child_process";
30003
- import fs16 from "fs";
30004
- import os13 from "os";
30005
- import path13 from "path";
30095
+ import fs18 from "fs";
30096
+ import os15 from "os";
30097
+ import path16 from "path";
30006
30098
  import * as p2 from "@clack/prompts";
30007
30099
  var REPO2 = "Project-White-Rabbit/bitfab-codex-plugin";
30008
30100
  var PLUGIN_KEY2 = "bitfab@bitfab";
@@ -30030,7 +30122,7 @@ function runCodexInstall() {
30030
30122
  }
30031
30123
  s.start("Enabling bitfab plugin in config");
30032
30124
  const changed = enableCodexPlugin(
30033
- path13.join(os13.homedir(), ".codex", "config.toml")
30125
+ path16.join(os15.homedir(), ".codex", "config.toml")
30034
30126
  );
30035
30127
  if (changed) {
30036
30128
  s.stop("Plugin enabled");
@@ -30093,8 +30185,8 @@ function runCodexUpdate(args, skipPermissions) {
30093
30185
  }
30094
30186
  function enableCodexPlugin(configPath) {
30095
30187
  let content = "";
30096
- if (fs16.existsSync(configPath)) {
30097
- content = fs16.readFileSync(configPath, "utf-8");
30188
+ if (fs18.existsSync(configPath)) {
30189
+ content = fs18.readFileSync(configPath, "utf-8");
30098
30190
  }
30099
30191
  const header = `[plugins."${PLUGIN_KEY2}"]`;
30100
30192
  const desiredSection = `${header}
@@ -30117,8 +30209,8 @@ enabled = true
30117
30209
 
30118
30210
  ${desiredSection}`;
30119
30211
  }
30120
- fs16.mkdirSync(path13.dirname(configPath), { recursive: true });
30121
- fs16.writeFileSync(configPath, next);
30212
+ fs18.mkdirSync(path16.dirname(configPath), { recursive: true });
30213
+ fs18.writeFileSync(configPath, next);
30122
30214
  return true;
30123
30215
  }
30124
30216
  function escapeRegex2(s) {
@@ -30127,7 +30219,7 @@ function escapeRegex2(s) {
30127
30219
 
30128
30220
  // src/cursor.ts
30129
30221
  import { spawn as spawn4, spawnSync as spawnSync3 } from "child_process";
30130
- import os14 from "os";
30222
+ import os16 from "os";
30131
30223
  import * as p3 from "@clack/prompts";
30132
30224
  var REPO3 = "Project-White-Rabbit/bitfab-cursor-plugin";
30133
30225
  var ADD_PLUGIN_CMD = `/add-plugin ${REPO3}`;
@@ -30199,13 +30291,13 @@ function copyToClipboard(text) {
30199
30291
  return result.status === 0;
30200
30292
  }
30201
30293
  function clipboardCommand() {
30202
- if (os14.platform() === "darwin") {
30294
+ if (os16.platform() === "darwin") {
30203
30295
  return { bin: "pbcopy", args: [] };
30204
30296
  }
30205
- if (os14.platform() === "linux") {
30297
+ if (os16.platform() === "linux") {
30206
30298
  return { bin: "xclip", args: ["-selection", "clipboard"] };
30207
30299
  }
30208
- if (os14.platform() === "win32") {
30300
+ if (os16.platform() === "win32") {
30209
30301
  return { bin: "clip", args: [] };
30210
30302
  }
30211
30303
  return null;
@@ -30229,12 +30321,12 @@ var platform = {
30229
30321
  };
30230
30322
 
30231
30323
  // src/version.ts
30232
- import fs17 from "fs";
30233
- import path14 from "path";
30234
- import { fileURLToPath as fileURLToPath2 } from "url";
30324
+ import fs19 from "fs";
30325
+ import path17 from "path";
30326
+ import { fileURLToPath as fileURLToPath3 } from "url";
30235
30327
  var CLI_VERSION = JSON.parse(
30236
- fs17.readFileSync(
30237
- path14.join(path14.dirname(fileURLToPath2(import.meta.url)), "../package.json"),
30328
+ fs19.readFileSync(
30329
+ path17.join(path17.dirname(fileURLToPath3(import.meta.url)), "../package.json"),
30238
30330
  "utf-8"
30239
30331
  )
30240
30332
  ).version;
@@ -30509,22 +30601,7 @@ function parseArgs2(argv) {
30509
30601
  }
30510
30602
  return { command, editor, skipPermissions, rest };
30511
30603
  }
30512
- async function handleDrainArg(argv) {
30513
- const drainIdx = argv.indexOf("--drain");
30514
- if (drainIdx === -1) {
30515
- return false;
30516
- }
30517
- const sessionId = argv[drainIdx + 1];
30518
- if (!sessionId) {
30519
- throw new Error("--drain requires a sessionId argument");
30520
- }
30521
- await runLoginWindowMonitor(sessionId);
30522
- return true;
30523
- }
30524
30604
  async function main() {
30525
- if (await handleDrainArg(process.argv)) {
30526
- return;
30527
- }
30528
30605
  const { command, editor, skipPermissions, rest } = parseArgs2(
30529
30606
  process.argv.slice(2)
30530
30607
  );
@@ -30581,7 +30658,6 @@ main().catch((err) => {
30581
30658
  process.exit(1);
30582
30659
  });
30583
30660
  export {
30584
- handleDrainArg,
30585
30661
  parseArgs2 as parseArgs,
30586
30662
  wantsHelp
30587
30663
  };