bitfab-cli 0.2.178 → 0.2.180

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 +625 -92
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5155,8 +5155,8 @@ var require_utils = __commonJS({
5155
5155
  }
5156
5156
  return ind;
5157
5157
  }
5158
- function removeDotSegments(path20) {
5159
- let input = path20;
5158
+ function removeDotSegments(path21) {
5159
+ let input = path21;
5160
5160
  const output = [];
5161
5161
  let nextSlash = -1;
5162
5162
  let len = 0;
@@ -5408,8 +5408,8 @@ var require_schemes = __commonJS({
5408
5408
  wsComponent.secure = void 0;
5409
5409
  }
5410
5410
  if (wsComponent.resourceName) {
5411
- const [path20, query] = wsComponent.resourceName.split("?");
5412
- wsComponent.path = path20 && path20 !== "/" ? path20 : void 0;
5411
+ const [path21, query] = wsComponent.resourceName.split("?");
5412
+ wsComponent.path = path21 && path21 !== "/" ? path21 : void 0;
5413
5413
  wsComponent.query = query;
5414
5414
  wsComponent.resourceName = void 0;
5415
5415
  }
@@ -8802,12 +8802,12 @@ var require_dist = __commonJS({
8802
8802
  throw new Error(`Unknown format "${name}"`);
8803
8803
  return f;
8804
8804
  };
8805
- function addFormats(ajv, list, fs23, exportName) {
8805
+ function addFormats(ajv, list, fs24, exportName) {
8806
8806
  var _a2;
8807
8807
  var _b;
8808
8808
  (_a2 = (_b = ajv.opts.code).formats) !== null && _a2 !== void 0 ? _a2 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
8809
8809
  for (const f of list)
8810
- ajv.addFormat(f, fs23[f]);
8810
+ ajv.addFormat(f, fs24[f]);
8811
8811
  }
8812
8812
  module.exports = exports = formatsPlugin;
8813
8813
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -9013,18 +9013,18 @@ async function closeStudio(client, message) {
9013
9013
  data: message ? { message } : {}
9014
9014
  });
9015
9015
  }
9016
- async function navigateStudio(client, path20) {
9017
- if (!isValidStudioRoute(path20)) {
9018
- throw new Error(`Studio route not allowed: ${path20}`);
9016
+ async function navigateStudio(client, path21) {
9017
+ if (!isValidStudioRoute(path21)) {
9018
+ throw new Error(`Studio route not allowed: ${path21}`);
9019
9019
  }
9020
9020
  return pushAgentSessionEvent(client, {
9021
9021
  type: "agent:navigate",
9022
- data: { path: path20 }
9022
+ data: { path: path21 }
9023
9023
  });
9024
9024
  }
9025
9025
  var STUDIO_NAVIGATE_ACK_TIMEOUT_MS = 12e3;
9026
9026
  var PING_PONG_TIMEOUT_MS = 5e3;
9027
- function awaitNavigateAck(client, path20, ackTimeoutMs, startCursor) {
9027
+ function awaitNavigateAck(client, path21, ackTimeoutMs, startCursor) {
9028
9028
  const abortController = new AbortController();
9029
9029
  let timer = null;
9030
9030
  const result = new Promise((resolve2) => {
@@ -9032,7 +9032,7 @@ function awaitNavigateAck(client, path20, ackTimeoutMs, startCursor) {
9032
9032
  resolve2({ acked: false, reason: "timeout" });
9033
9033
  abortController.abort();
9034
9034
  }, ackTimeoutMs);
9035
- const pathOnly = path20.split("?")[0];
9035
+ const pathOnly = path21.split("?")[0];
9036
9036
  pollAgentSessionEvents({
9037
9037
  serviceUrl: client.serviceUrl,
9038
9038
  apiKey: client.apiKey,
@@ -9118,18 +9118,18 @@ function awaitPong(client, pingTs) {
9118
9118
  });
9119
9119
  });
9120
9120
  }
9121
- async function navigateStudioAndAwaitAck(client, path20, opts = {}) {
9121
+ async function navigateStudioAndAwaitAck(client, path21, opts = {}) {
9122
9122
  const ackTimeoutMs = opts.ackTimeoutMs ?? STUDIO_NAVIGATE_ACK_TIMEOUT_MS;
9123
- if (!isValidStudioRoute(path20)) {
9123
+ if (!isValidStudioRoute(path21)) {
9124
9124
  return { acked: false, reason: "invalid-route" };
9125
9125
  }
9126
9126
  const startCursor = await fetchStreamTip(client);
9127
9127
  try {
9128
- await navigateStudio(client, path20);
9128
+ await navigateStudio(client, path21);
9129
9129
  } catch {
9130
9130
  return { acked: false, reason: "push-failed" };
9131
9131
  }
9132
- const result = await awaitNavigateAck(client, path20, ackTimeoutMs, startCursor);
9132
+ const result = await awaitNavigateAck(client, path21, ackTimeoutMs, startCursor);
9133
9133
  if (result.acked || result.reason !== "timeout") {
9134
9134
  return result;
9135
9135
  }
@@ -10077,20 +10077,20 @@ function resolveStudioInitialPath(input) {
10077
10077
  return DEFAULT_STUDIO_PATH;
10078
10078
  }
10079
10079
  function buildStudioSessionUrl(args) {
10080
- const { serviceUrl, path: path20, sessionId } = args;
10081
- if (path20.includes("session=")) {
10082
- return `${serviceUrl}${path20}`;
10080
+ const { serviceUrl, path: path21, sessionId } = args;
10081
+ if (path21.includes("session=")) {
10082
+ return `${serviceUrl}${path21}`;
10083
10083
  }
10084
- const separator = path20.includes("?") ? "&" : "?";
10085
- return `${serviceUrl}${path20}${separator}session=${encodeURIComponent(sessionId)}`;
10084
+ const separator = path21.includes("?") ? "&" : "?";
10085
+ return `${serviceUrl}${path21}${separator}session=${encodeURIComponent(sessionId)}`;
10086
10086
  }
10087
10087
  function resolveStudioSessionTarget(args) {
10088
- const path20 = resolveStudioInitialPath(args.path);
10088
+ const path21 = resolveStudioInitialPath(args.path);
10089
10089
  return {
10090
- path: path20,
10090
+ path: path21,
10091
10091
  url: buildStudioSessionUrl({
10092
10092
  serviceUrl: args.serviceUrl,
10093
- path: path20,
10093
+ path: path21,
10094
10094
  sessionId: args.sessionId
10095
10095
  })
10096
10096
  };
@@ -10751,8 +10751,8 @@ function tailStudioEvents(key, onLine, opts) {
10751
10751
  }
10752
10752
 
10753
10753
  // ../bitfab-plugin-lib/dist/studioChannel.js
10754
- function stripSessionId(path20, sessionId) {
10755
- return sessionId ? path20.split(sessionId).join("__SID__") : path20;
10754
+ function stripSessionId(path21, sessionId) {
10755
+ return sessionId ? path21.split(sessionId).join("__SID__") : path21;
10756
10756
  }
10757
10757
  var DirectChannel = class {
10758
10758
  apiKey;
@@ -10767,10 +10767,10 @@ var DirectChannel = class {
10767
10767
  isPersistent() {
10768
10768
  return false;
10769
10769
  }
10770
- async openOrNavigate(path20, opts) {
10770
+ async openOrNavigate(path21, opts) {
10771
10771
  if (!this.apiKey) {
10772
10772
  const sessionId2 = opts?.sessionId ?? crypto8.randomUUID();
10773
- const initialPath = resolveStudioInitialPath(path20);
10773
+ const initialPath = resolveStudioInitialPath(path21);
10774
10774
  const live = readActiveStudioSession();
10775
10775
  if (live && live.source === "direct" && !live.authenticated && live.windowState !== "closed" && live.serviceUrl === this.serviceUrl && live.currentPath != null && stripSessionId(live.currentPath, live.sessionId) === stripSessionId(initialPath, sessionId2)) {
10776
10776
  return { sessionId: live.sessionId, opened: false };
@@ -10801,11 +10801,11 @@ var DirectChannel = class {
10801
10801
  serviceUrl: existing.serviceUrl,
10802
10802
  apiKey: this.apiKey,
10803
10803
  sessionId: existing.sessionId
10804
- }, path20);
10804
+ }, path21);
10805
10805
  if (!ack.acked) {
10806
10806
  throw new StudioNavigationError(ack.reason ?? "unknown", ack.blockedReason, existing.sessionId);
10807
10807
  }
10808
- updateActiveStudioSession({ currentPath: path20 });
10808
+ updateActiveStudioSession({ currentPath: path21 });
10809
10809
  return { sessionId: existing.sessionId, opened: false };
10810
10810
  }
10811
10811
  truncateStudioEventLog(resolveAgentSessionKey());
@@ -10814,7 +10814,7 @@ var DirectChannel = class {
10814
10814
  serviceUrl: this.serviceUrl,
10815
10815
  apiKey: this.apiKey,
10816
10816
  sessionId,
10817
- initialPath: path20,
10817
+ initialPath: path21,
10818
10818
  clientHeaders: opts?.clientHeaders
10819
10819
  });
10820
10820
  return { sessionId: session.sessionId, opened: true };
@@ -10881,18 +10881,18 @@ var DaemonChannel = class _DaemonChannel {
10881
10881
  isPersistent() {
10882
10882
  return true;
10883
10883
  }
10884
- async openOrNavigate(path20, opts) {
10884
+ async openOrNavigate(path21, opts) {
10885
10885
  const openRes = await this.client.open({
10886
10886
  key: this.key,
10887
10887
  serviceUrl: this.serviceUrl,
10888
10888
  apiKey: this.apiKey,
10889
10889
  sessionId: opts?.sessionId,
10890
- initialPath: path20,
10890
+ initialPath: path21,
10891
10891
  clientHeaders: opts?.clientHeaders,
10892
10892
  focusTarget: opts?.focusTarget
10893
10893
  });
10894
10894
  if (openRes.reconnected) {
10895
- const navRes = await this.client.navigate(this.key, path20);
10895
+ const navRes = await this.client.navigate(this.key, path21);
10896
10896
  if (!navRes.acked) {
10897
10897
  this.client.destroy();
10898
10898
  throw new StudioNavigationError(navRes.reason ?? "unknown", void 0, openRes.sessionId);
@@ -10950,9 +10950,9 @@ function isDaemonEnabled() {
10950
10950
  function withDirectFallback(primary, apiKey, serviceUrl) {
10951
10951
  let active = primary;
10952
10952
  return {
10953
- async openOrNavigate(path20, opts) {
10953
+ async openOrNavigate(path21, opts) {
10954
10954
  try {
10955
- return await active.openOrNavigate(path20, opts);
10955
+ return await active.openOrNavigate(path21, opts);
10956
10956
  } catch (err) {
10957
10957
  if (err instanceof StudioNavigationError || active !== primary) {
10958
10958
  throw err;
@@ -10960,7 +10960,7 @@ function withDirectFallback(primary, apiKey, serviceUrl) {
10960
10960
  console.error(`daemon failed, falling back to direct: ${err instanceof Error ? err.message : String(err)}`);
10961
10961
  active.destroy();
10962
10962
  active = new DirectChannel(apiKey, serviceUrl);
10963
- return active.openOrNavigate(path20, opts);
10963
+ return active.openOrNavigate(path21, opts);
10964
10964
  }
10965
10965
  },
10966
10966
  async close(sessionId, message) {
@@ -11285,7 +11285,7 @@ function createEventSubscription(channel, sessionId, onEvent, restoreFocus) {
11285
11285
  done: sub.done
11286
11286
  };
11287
11287
  }
11288
- async function openStudioTo(path20, opts = {}) {
11288
+ async function openStudioTo(path21, opts = {}) {
11289
11289
  const config4 = getConfig();
11290
11290
  const serviceUrl = config4.serviceUrl;
11291
11291
  const apiKey = !opts.forceLogin && hasCredentials() ? config4.apiKey : null;
@@ -11363,7 +11363,7 @@ async function openStudioTo(path20, opts = {}) {
11363
11363
  };
11364
11364
  };
11365
11365
  if (!apiKey) {
11366
- const destination = opts.freshWindowPath ?? path20;
11366
+ const destination = opts.freshWindowPath ?? path21;
11367
11367
  const destSeparator = destination.includes("?") ? "&" : "?";
11368
11368
  const buildSignInPath = (sid) => {
11369
11369
  const redirectPath = `${destination}${destSeparator}session=${encodeURIComponent(sid)}&pluginLogin=true`;
@@ -11408,11 +11408,11 @@ async function openStudioTo(path20, opts = {}) {
11408
11408
  channel.setApiKey(loginApiKey);
11409
11409
  return finish(sessionId2, openResult.opened, loginApiKey, openResult.opened ? signInUrl : void 0);
11410
11410
  }
11411
- const { sessionId, opened } = await channel.openOrNavigate(path20, {
11411
+ const { sessionId, opened } = await channel.openOrNavigate(path21, {
11412
11412
  clientHeaders: opts.clientHeaders,
11413
11413
  focusTarget: focusTarget ?? void 0
11414
11414
  });
11415
- const url2 = opened ? resolveStudioSessionTarget({ serviceUrl, path: path20, sessionId }).url : void 0;
11415
+ const url2 = opened ? resolveStudioSessionTarget({ serviceUrl, path: path21, sessionId }).url : void 0;
11416
11416
  if (url2) {
11417
11417
  opts.onWindowOpened?.(url2);
11418
11418
  }
@@ -12293,10 +12293,10 @@ function mergeDefs(...defs) {
12293
12293
  function cloneDef(schema) {
12294
12294
  return mergeDefs(schema._zod.def);
12295
12295
  }
12296
- function getElementAtPath(obj, path20) {
12297
- if (!path20)
12296
+ function getElementAtPath(obj, path21) {
12297
+ if (!path21)
12298
12298
  return obj;
12299
- return path20.reduce((acc, key) => acc?.[key], obj);
12299
+ return path21.reduce((acc, key) => acc?.[key], obj);
12300
12300
  }
12301
12301
  function promiseAllObject(promisesObj) {
12302
12302
  const keys = Object.keys(promisesObj);
@@ -12679,11 +12679,11 @@ function aborted(x, startIndex = 0) {
12679
12679
  }
12680
12680
  return false;
12681
12681
  }
12682
- function prefixIssues(path20, issues) {
12682
+ function prefixIssues(path21, issues) {
12683
12683
  return issues.map((iss) => {
12684
12684
  var _a2;
12685
12685
  (_a2 = iss).path ?? (_a2.path = []);
12686
- iss.path.unshift(path20);
12686
+ iss.path.unshift(path21);
12687
12687
  return iss;
12688
12688
  });
12689
12689
  }
@@ -12866,7 +12866,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
12866
12866
  }
12867
12867
  function treeifyError(error48, mapper = (issue2) => issue2.message) {
12868
12868
  const result = { errors: [] };
12869
- const processError = (error49, path20 = []) => {
12869
+ const processError = (error49, path21 = []) => {
12870
12870
  var _a2, _b;
12871
12871
  for (const issue2 of error49.issues) {
12872
12872
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -12876,7 +12876,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
12876
12876
  } else if (issue2.code === "invalid_element") {
12877
12877
  processError({ issues: issue2.issues }, issue2.path);
12878
12878
  } else {
12879
- const fullpath = [...path20, ...issue2.path];
12879
+ const fullpath = [...path21, ...issue2.path];
12880
12880
  if (fullpath.length === 0) {
12881
12881
  result.errors.push(mapper(issue2));
12882
12882
  continue;
@@ -12908,8 +12908,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
12908
12908
  }
12909
12909
  function toDotPath(_path) {
12910
12910
  const segs = [];
12911
- const path20 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
12912
- for (const seg of path20) {
12911
+ const path21 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
12912
+ for (const seg of path21) {
12913
12913
  if (typeof seg === "number")
12914
12914
  segs.push(`[${seg}]`);
12915
12915
  else if (typeof seg === "symbol")
@@ -24886,13 +24886,13 @@ function resolveRef(ref, ctx) {
24886
24886
  if (!ref.startsWith("#")) {
24887
24887
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
24888
24888
  }
24889
- const path20 = ref.slice(1).split("/").filter(Boolean);
24890
- if (path20.length === 0) {
24889
+ const path21 = ref.slice(1).split("/").filter(Boolean);
24890
+ if (path21.length === 0) {
24891
24891
  return ctx.rootSchema;
24892
24892
  }
24893
24893
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
24894
- if (path20[0] === defsKey) {
24895
- const key = path20[1];
24894
+ if (path21[0] === defsKey) {
24895
+ const key = path21[1];
24896
24896
  if (!key || !ctx.defs[key]) {
24897
24897
  throw new Error(`Reference not found: ${ref}`);
24898
24898
  }
@@ -25730,8 +25730,8 @@ function getErrorMap2() {
25730
25730
 
25731
25731
  // ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/helpers/parseUtil.js
25732
25732
  var makeIssue = (params) => {
25733
- const { data, path: path20, errorMaps, issueData } = params;
25734
- const fullPath = [...path20, ...issueData.path || []];
25733
+ const { data, path: path21, errorMaps, issueData } = params;
25734
+ const fullPath = [...path21, ...issueData.path || []];
25735
25735
  const fullIssue = {
25736
25736
  ...issueData,
25737
25737
  path: fullPath
@@ -25846,11 +25846,11 @@ var errorUtil;
25846
25846
 
25847
25847
  // ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/types.js
25848
25848
  var ParseInputLazyPath = class {
25849
- constructor(parent, value, path20, key) {
25849
+ constructor(parent, value, path21, key) {
25850
25850
  this._cachedPath = [];
25851
25851
  this.parent = parent;
25852
25852
  this.data = value;
25853
- this._path = path20;
25853
+ this._path = path21;
25854
25854
  this._key = key;
25855
25855
  }
25856
25856
  get path() {
@@ -30804,6 +30804,7 @@ var tracePlanTreeShape = external_exports.object({
30804
30804
 
30805
30805
  // src/claude.ts
30806
30806
  import { spawn as spawn5 } from "child_process";
30807
+ import crypto10 from "crypto";
30807
30808
  import fs20 from "fs";
30808
30809
  import os15 from "os";
30809
30810
  import path17 from "path";
@@ -30956,6 +30957,27 @@ function cliUpdate(cfg, args, skipPermissions) {
30956
30957
  }
30957
30958
  }
30958
30959
 
30960
+ // src/redaction.ts
30961
+ var SECRET_PATTERN = /bf_[A-Za-z0-9]+/g;
30962
+ var STREAMING_SECRET_TAIL_LENGTH = 512;
30963
+ function redactSecrets(text) {
30964
+ return text.replace(SECRET_PATTERN, "bf_[redacted]");
30965
+ }
30966
+ var StreamingSecretRedactor = class {
30967
+ pending = "";
30968
+ redact(chunk2) {
30969
+ const text = this.pending + chunk2;
30970
+ const flushLength = Math.max(0, text.length - STREAMING_SECRET_TAIL_LENGTH);
30971
+ this.pending = text.slice(flushLength);
30972
+ return redactSecrets(text.slice(0, flushLength));
30973
+ }
30974
+ flush() {
30975
+ const redacted = redactSecrets(this.pending);
30976
+ this.pending = "";
30977
+ return redacted;
30978
+ }
30979
+ };
30980
+
30959
30981
  // src/claude.ts
30960
30982
  var CLI = "claude";
30961
30983
  var SKIP_FLAG = "--dangerously-skip-permissions";
@@ -31026,15 +31048,35 @@ async function runClaudeAnalyzeRepo(captureOverride, limit) {
31026
31048
  throw new Error(auth.message);
31027
31049
  }
31028
31050
  const logPath = analyzeRepoLogPath();
31051
+ const agentRunId = crypto10.randomUUID();
31029
31052
  const env = { ...process.env };
31053
+ env.BITFAB_AGENT_RUN_ID = agentRunId;
31030
31054
  if (captureOverride !== void 0) {
31031
31055
  env.BITFAB_CAPTURE_SESSIONS = captureOverride ? "1" : "0";
31032
31056
  }
31033
31057
  const command = limit === void 0 ? ANALYZE_REPO_COMMAND : `${ANALYZE_REPO_COMMAND} limit=${limit}`;
31034
31058
  const out = fs20.createWriteStream(logPath);
31035
- const spinner5 = p3.spinner();
31036
- spinner5.start("Analyzing repo and drafting trace plans");
31059
+ const spinner6 = p3.spinner();
31060
+ spinner6.start("Analyzing repo and drafting trace plans");
31037
31061
  const stdoutChunks = [];
31062
+ const stdoutRedactor = new StreamingSecretRedactor();
31063
+ const stderrRedactor = new StreamingSecretRedactor();
31064
+ let redactorsFlushed = false;
31065
+ const flushRedactors = () => {
31066
+ if (redactorsFlushed) {
31067
+ return;
31068
+ }
31069
+ redactorsFlushed = true;
31070
+ const stdoutTail = stdoutRedactor.flush();
31071
+ if (stdoutTail !== "") {
31072
+ out.write(stdoutTail);
31073
+ stdoutChunks.push(stdoutTail);
31074
+ }
31075
+ const stderrTail = stderrRedactor.flush();
31076
+ if (stderrTail !== "") {
31077
+ out.write(stderrTail);
31078
+ }
31079
+ };
31038
31080
  try {
31039
31081
  const exitCode = await new Promise((resolve2, reject) => {
31040
31082
  const child = spawn5(
@@ -31050,31 +31092,49 @@ async function runClaudeAnalyzeRepo(captureOverride, limit) {
31050
31092
  { stdio: ["ignore", "pipe", "pipe"], env, ...SHELL_OPTS }
31051
31093
  );
31052
31094
  child.stdout.on("data", (chunk2) => {
31053
- out.write(chunk2);
31054
31095
  const text = chunk2.toString();
31055
- stdoutChunks.push(text);
31096
+ const redacted = stdoutRedactor.redact(text);
31097
+ if (redacted !== "") {
31098
+ out.write(redacted);
31099
+ stdoutChunks.push(redacted);
31100
+ }
31056
31101
  const label = latestActivityLabel(text);
31057
31102
  if (label) {
31058
- spinner5.message(label);
31103
+ spinner6.message(label);
31059
31104
  }
31060
31105
  });
31061
- child.stderr.on("data", (chunk2) => out.write(chunk2));
31106
+ child.stderr.on(
31107
+ "data",
31108
+ (chunk2) => out.write(stderrRedactor.redact(chunk2.toString()))
31109
+ );
31062
31110
  child.on("error", reject);
31063
- child.on("close", (code) => resolve2(code ?? 0));
31111
+ child.on("close", (code, signal) => {
31112
+ if (code === null) {
31113
+ reject(
31114
+ new Error(
31115
+ `analyze-repo terminated by signal ${signal ?? "unknown"}. See ${logPath}`
31116
+ )
31117
+ );
31118
+ return;
31119
+ }
31120
+ resolve2(code);
31121
+ });
31064
31122
  });
31123
+ flushRedactors();
31065
31124
  if (exitCode !== 0) {
31066
31125
  throw new Error(
31067
31126
  `analyze-repo exited with status ${exitCode}. See ${logPath}`
31068
31127
  );
31069
31128
  }
31070
31129
  const uploaded = countUploadedTracePlans(stdoutChunks.join(""));
31071
- spinner5.stop(
31130
+ spinner6.stop(
31072
31131
  uploaded === 0 ? "No AI workflows found - nothing to upload" : `Uploaded ${uploaded} draft trace plan${uploaded === 1 ? "" : "s"} to Bitfab`
31073
31132
  );
31074
31133
  } catch (err) {
31075
- spinner5.stop("analyze-repo failed");
31134
+ spinner6.stop("analyze-repo failed");
31076
31135
  throw err;
31077
31136
  } finally {
31137
+ flushRedactors();
31078
31138
  out.end();
31079
31139
  }
31080
31140
  p3.log.info(`Full run log: ${logPath}`);
@@ -31239,6 +31299,8 @@ var claudeAdapter = {
31239
31299
  };
31240
31300
 
31241
31301
  // src/codex.ts
31302
+ import { spawn as spawn6 } from "child_process";
31303
+ import crypto11 from "crypto";
31242
31304
  import fs21 from "fs";
31243
31305
  import os16 from "os";
31244
31306
  import path18 from "path";
@@ -31248,7 +31310,9 @@ var SKIP_FLAG2 = "--full-auto";
31248
31310
  var REPO2 = "Project-White-Rabbit/bitfab-codex-plugin";
31249
31311
  var MARKETPLACE2 = "bitfab";
31250
31312
  var PLUGIN_KEY2 = "bitfab@bitfab";
31313
+ var INTERNAL_PLUGIN_KEY = "bitfab@bitfab-internal";
31251
31314
  var SETUP_COMMAND2 = "$bitfab:setup";
31315
+ var ANALYZE_REPO_COMMAND2 = "$bitfab:setup analyze-repo";
31252
31316
  var ASSISTANT_COMMAND2 = "$bitfab:assistant";
31253
31317
  var UPDATE_COMMAND2 = "$bitfab:update";
31254
31318
  function isCodexLoginStatusLoggedIn(output) {
@@ -31268,6 +31332,12 @@ function ensureLatestCodexPlugin() {
31268
31332
  ensureLatestPlugin(pullLatestCodexPlugin);
31269
31333
  }
31270
31334
  function runCodexInstall() {
31335
+ const configPath = path18.join(os16.homedir(), ".codex", "config.toml");
31336
+ if (isCodexPluginEnabled(configPath, INTERNAL_PLUGIN_KEY)) {
31337
+ p4.log.step("Codex dev plugin already active");
31338
+ p4.log.success("Bitfab plugin ready in Codex");
31339
+ return;
31340
+ }
31271
31341
  const s = p4.spinner();
31272
31342
  s.start("Adding bitfab marketplace");
31273
31343
  const out = runCli(CLI2, ["plugin", "marketplace", "add", REPO2]);
@@ -31278,9 +31348,7 @@ function runCodexInstall() {
31278
31348
  s.stop("Marketplace added");
31279
31349
  }
31280
31350
  s.start("Enabling bitfab plugin in config");
31281
- const changed = enableCodexPlugin(
31282
- path18.join(os16.homedir(), ".codex", "config.toml")
31283
- );
31351
+ const changed = enableCodexPlugin(configPath);
31284
31352
  if (changed) {
31285
31353
  s.stop("Plugin enabled");
31286
31354
  } else {
@@ -31291,13 +31359,229 @@ function runCodexInstall() {
31291
31359
  }
31292
31360
  p4.log.success("Bitfab plugin ready in Codex");
31293
31361
  }
31294
- function runCodexAnalyzeRepo() {
31295
- p4.log.warn(
31296
- "Headless analyze-repo is currently supported for Claude Code only."
31297
- );
31298
- p4.log.info(
31299
- `To run it in Codex, launch ${SETUP_COMMAND2} analyze-repo interactively.`
31362
+ async function runCodexAnalyzeRepo(captureOverride, limit) {
31363
+ const auth = checkCodexAuth();
31364
+ if (!auth.ok) {
31365
+ throw new Error(auth.message);
31366
+ }
31367
+ const logPath = analyzeRepoLogPath2();
31368
+ const agentRunId = crypto11.randomUUID();
31369
+ const env = { ...process.env };
31370
+ env.BITFAB_AGENT_RUN_ID = agentRunId;
31371
+ if (captureOverride !== void 0) {
31372
+ env.BITFAB_CAPTURE_SESSIONS = captureOverride ? "1" : "0";
31373
+ }
31374
+ const command = limit === void 0 ? ANALYZE_REPO_COMMAND2 : `${ANALYZE_REPO_COMMAND2} limit=${limit}`;
31375
+ const out = fs21.createWriteStream(logPath);
31376
+ const spinner6 = p4.spinner();
31377
+ spinner6.start("Analyzing repo and drafting trace plans");
31378
+ const stdoutChunks = [];
31379
+ const stdoutRedactor = new StreamingSecretRedactor();
31380
+ const stderrRedactor = new StreamingSecretRedactor();
31381
+ let redactorsFlushed = false;
31382
+ const flushRedactors = () => {
31383
+ if (redactorsFlushed) {
31384
+ return;
31385
+ }
31386
+ redactorsFlushed = true;
31387
+ const stdoutTail = stdoutRedactor.flush();
31388
+ if (stdoutTail !== "") {
31389
+ out.write(stdoutTail);
31390
+ stdoutChunks.push(stdoutTail);
31391
+ }
31392
+ const stderrTail = stderrRedactor.flush();
31393
+ if (stderrTail !== "") {
31394
+ out.write(stderrTail);
31395
+ }
31396
+ };
31397
+ try {
31398
+ const exitCode = await new Promise((resolve2, reject) => {
31399
+ const child = spawn6(
31400
+ CLI2,
31401
+ [
31402
+ "--ask-for-approval",
31403
+ "on-request",
31404
+ "--config",
31405
+ 'approvals_reviewer="auto_review"',
31406
+ "exec",
31407
+ "--json",
31408
+ "--sandbox",
31409
+ "danger-full-access",
31410
+ command
31411
+ ],
31412
+ { stdio: ["ignore", "pipe", "pipe"], env, ...SHELL_OPTS }
31413
+ );
31414
+ child.stdout.on("data", (chunk2) => {
31415
+ const text = chunk2.toString();
31416
+ const redacted = stdoutRedactor.redact(text);
31417
+ if (redacted !== "") {
31418
+ out.write(redacted);
31419
+ stdoutChunks.push(redacted);
31420
+ }
31421
+ const label = latestCodexActivityLabel(text);
31422
+ if (label) {
31423
+ spinner6.message(label);
31424
+ }
31425
+ });
31426
+ child.stderr.on(
31427
+ "data",
31428
+ (chunk2) => out.write(stderrRedactor.redact(chunk2.toString()))
31429
+ );
31430
+ child.on("error", reject);
31431
+ child.on("close", (code, signal) => {
31432
+ if (code === null) {
31433
+ reject(
31434
+ new Error(
31435
+ `analyze-repo terminated by signal ${signal ?? "unknown"}. See ${logPath}`
31436
+ )
31437
+ );
31438
+ return;
31439
+ }
31440
+ resolve2(code);
31441
+ });
31442
+ });
31443
+ flushRedactors();
31444
+ if (exitCode !== 0) {
31445
+ throw new Error(
31446
+ `analyze-repo exited with status ${exitCode}. See ${logPath}`
31447
+ );
31448
+ }
31449
+ const uploaded = countUploadedTracePlans2(stdoutChunks.join(""));
31450
+ spinner6.stop(
31451
+ uploaded === 0 ? "No AI workflows found - nothing to upload" : `Uploaded ${uploaded} draft trace plan${uploaded === 1 ? "" : "s"} to Bitfab`
31452
+ );
31453
+ } catch (err) {
31454
+ spinner6.stop("analyze-repo failed");
31455
+ throw err;
31456
+ } finally {
31457
+ flushRedactors();
31458
+ out.end();
31459
+ }
31460
+ p4.log.info(`Full run log: ${logPath}`);
31461
+ }
31462
+ function latestCodexActivityLabel(chunk2) {
31463
+ const lines = chunk2.split("\n").filter((l) => l.trim() !== "");
31464
+ for (let i = lines.length - 1; i >= 0; i--) {
31465
+ let event;
31466
+ try {
31467
+ event = JSON.parse(lines[i]);
31468
+ } catch {
31469
+ continue;
31470
+ }
31471
+ const label = codexActivityLabelFromEvent(event);
31472
+ if (label) {
31473
+ return label;
31474
+ }
31475
+ }
31476
+ return null;
31477
+ }
31478
+ function codexActivityLabelFromEvent(event) {
31479
+ const item = eventItem(event);
31480
+ if (!item) {
31481
+ return null;
31482
+ }
31483
+ if (isMcpToolItem(item, "create_trace_plan")) {
31484
+ return "Uploading a draft trace plan";
31485
+ }
31486
+ if (isMcpToolItem(item, "get_bitfab_api_key")) {
31487
+ return "Checking authentication";
31488
+ }
31489
+ const command = typeof item.command === "string" ? item.command : "";
31490
+ if (command.includes("rg ") || command.includes("rg --files")) {
31491
+ return "Searching the codebase";
31492
+ }
31493
+ if (command.includes("sed ") || command.includes("cat ") || command.includes("nl ")) {
31494
+ return "Reading files";
31495
+ }
31496
+ if (typeof item.type === "string" && item.type.includes("web")) {
31497
+ return "Reading SDK docs";
31498
+ }
31499
+ return null;
31500
+ }
31501
+ function countUploadedTracePlans2(logText) {
31502
+ const planToolUseIds = /* @__PURE__ */ new Set();
31503
+ const nonErrorResultIds = /* @__PURE__ */ new Set();
31504
+ let completedPlanCalls = 0;
31505
+ for (const line of logText.split("\n")) {
31506
+ if (line.trim() === "") {
31507
+ continue;
31508
+ }
31509
+ let event;
31510
+ try {
31511
+ event = JSON.parse(line);
31512
+ } catch {
31513
+ continue;
31514
+ }
31515
+ const item = eventItem(event);
31516
+ if (item && isMcpToolItem(item, "create_trace_plan")) {
31517
+ const status = typeof item.status === "string" ? item.status : "";
31518
+ if (status === "completed" || status === "" && eventType(event) === "item.completed") {
31519
+ completedPlanCalls++;
31520
+ }
31521
+ }
31522
+ for (const block of eventMessageContent2(event) ?? []) {
31523
+ if (typeof block !== "object" || block === null) {
31524
+ continue;
31525
+ }
31526
+ const b = block;
31527
+ if (b.type === "tool_use" && typeof b.id === "string" && typeof b.name === "string" && b.name.includes("create_trace_plan")) {
31528
+ planToolUseIds.add(b.id);
31529
+ }
31530
+ if (b.type === "tool_result" && typeof b.tool_use_id === "string" && b.is_error !== true) {
31531
+ nonErrorResultIds.add(b.tool_use_id);
31532
+ }
31533
+ }
31534
+ }
31535
+ let pairedCount = 0;
31536
+ for (const id of planToolUseIds) {
31537
+ if (nonErrorResultIds.has(id)) {
31538
+ pairedCount++;
31539
+ }
31540
+ }
31541
+ return pairedCount + completedPlanCalls;
31542
+ }
31543
+ function isMcpToolItem(item, toolName) {
31544
+ if (typeof item.type === "string" && !item.type.includes("mcp")) {
31545
+ return false;
31546
+ }
31547
+ const directNames = [item.tool, item.name].filter(
31548
+ (value) => typeof value === "string"
31300
31549
  );
31550
+ if (directNames.some((name) => name.includes(toolName))) {
31551
+ return true;
31552
+ }
31553
+ const toolCall = item.tool_call;
31554
+ if (typeof toolCall === "object" && toolCall !== null) {
31555
+ const nested = toolCall;
31556
+ return [nested.tool, nested.name].some(
31557
+ (value) => typeof value === "string" && value.includes(toolName)
31558
+ );
31559
+ }
31560
+ return false;
31561
+ }
31562
+ function eventType(event) {
31563
+ return typeof event === "object" && event !== null && typeof event.type === "string" ? event.type : "";
31564
+ }
31565
+ function eventItem(event) {
31566
+ if (typeof event !== "object" || event === null) {
31567
+ return null;
31568
+ }
31569
+ const item = event.item;
31570
+ return typeof item === "object" && item !== null ? item : null;
31571
+ }
31572
+ function eventMessageContent2(event) {
31573
+ if (typeof event !== "object" || event === null) {
31574
+ return null;
31575
+ }
31576
+ const message = event.message;
31577
+ const content = typeof message === "object" && message !== null ? message.content : void 0;
31578
+ return Array.isArray(content) ? content : null;
31579
+ }
31580
+ function analyzeRepoLogPath2() {
31581
+ const dir = path18.join(process.cwd(), ".bitfab", "logs");
31582
+ fs21.mkdirSync(dir, { recursive: true });
31583
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
31584
+ return path18.join(dir, `analyze-repo-${stamp}.jsonl`);
31301
31585
  }
31302
31586
  function enableCodexPlugin(configPath) {
31303
31587
  let content = "";
@@ -31313,7 +31597,7 @@ enabled = true
31313
31597
  "m"
31314
31598
  );
31315
31599
  const match = sectionRegex.exec(content);
31316
- if (match?.[0].includes("enabled = true")) {
31600
+ if (match && sectionHasEnabledTrue(match[0])) {
31317
31601
  return false;
31318
31602
  }
31319
31603
  let next;
@@ -31329,9 +31613,24 @@ ${desiredSection}`;
31329
31613
  fs21.writeFileSync(configPath, next);
31330
31614
  return true;
31331
31615
  }
31616
+ function isCodexPluginEnabled(configPath, pluginKey) {
31617
+ if (!fs21.existsSync(configPath)) {
31618
+ return false;
31619
+ }
31620
+ const content = fs21.readFileSync(configPath, "utf-8");
31621
+ const sectionRegex = new RegExp(
31622
+ `\\[plugins\\."${escapeRegex2(pluginKey)}"\\][^\\[]*`,
31623
+ "m"
31624
+ );
31625
+ const match = sectionRegex.exec(content);
31626
+ return match ? sectionHasEnabledTrue(match[0]) : false;
31627
+ }
31332
31628
  function escapeRegex2(s) {
31333
31629
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
31334
31630
  }
31631
+ function sectionHasEnabledTrue(section) {
31632
+ return section.split(/\r?\n/).some((line) => /^\s*enabled\s*=\s*true\s*(?:#.*)?$/.test(line));
31633
+ }
31335
31634
  var config3 = {
31336
31635
  cli: CLI2,
31337
31636
  skipFlag: SKIP_FLAG2,
@@ -31352,12 +31651,16 @@ var codexAdapter = {
31352
31651
  };
31353
31652
 
31354
31653
  // src/cursor.ts
31355
- import { spawn as spawn6, spawnSync as spawnSync2 } from "child_process";
31654
+ import { spawn as spawn7, spawnSync as spawnSync2 } from "child_process";
31655
+ import crypto12 from "crypto";
31656
+ import fs22 from "fs";
31356
31657
  import os17 from "os";
31658
+ import path19 from "path";
31357
31659
  import * as p5 from "@clack/prompts";
31358
31660
  var REPO3 = "Project-White-Rabbit/bitfab-cursor-plugin";
31359
31661
  var ADD_PLUGIN_CMD = `/add-plugin ${REPO3}`;
31360
31662
  var SETUP_COMMAND3 = "/bitfab-setup";
31663
+ var ANALYZE_REPO_COMMAND3 = "/bitfab-setup analyze-repo";
31361
31664
  var ASSISTANT_COMMAND3 = "/bitfab-assistant";
31362
31665
  var UPDATE_COMMAND3 = "/bitfab-update";
31363
31666
  function isCursorAgentStatusAuthenticated(output) {
@@ -31396,7 +31699,7 @@ function runCursorInstall() {
31396
31699
  "Finish setup in Cursor"
31397
31700
  );
31398
31701
  p5.log.info("Opening Cursor...");
31399
- const child = spawn6("cursor", ["."], {
31702
+ const child = spawn7("cursor", ["."], {
31400
31703
  stdio: "ignore",
31401
31704
  detached: true,
31402
31705
  windowsHide: true,
@@ -31415,11 +31718,241 @@ function runCursorSetup(skipPermissions) {
31415
31718
  }
31416
31719
  p5.log.info(`To complete setup, run ${SETUP_COMMAND3} in Cursor.`);
31417
31720
  }
31418
- function runCursorAnalyzeRepo() {
31419
- p5.log.warn(
31420
- "Headless analyze-repo is currently supported for Claude Code only."
31421
- );
31422
- p5.log.info(`To run it in Cursor, run ${SETUP_COMMAND3} analyze-repo manually.`);
31721
+ async function runCursorAnalyzeRepo(captureOverride, limit) {
31722
+ ensureCursorAuthenticated();
31723
+ const pluginDir = resolveCursorPluginDir();
31724
+ if (!pluginDir) {
31725
+ throw new Error(
31726
+ [
31727
+ "Could not find a Cursor Bitfab plugin directory to load headlessly.",
31728
+ `Install the plugin in Cursor with ${ADD_PLUGIN_CMD}, or run from the monorepo checkout so bitfab-cursor-plugin is available.`
31729
+ ].join("\n")
31730
+ );
31731
+ }
31732
+ const logPath = analyzeRepoLogPath3();
31733
+ const agentRunId = crypto12.randomUUID();
31734
+ const env = { ...process.env };
31735
+ env.BITFAB_AGENT_RUN_ID = agentRunId;
31736
+ if (captureOverride !== void 0) {
31737
+ env.BITFAB_CAPTURE_SESSIONS = captureOverride ? "1" : "0";
31738
+ }
31739
+ const command = limit === void 0 ? ANALYZE_REPO_COMMAND3 : `${ANALYZE_REPO_COMMAND3} limit=${limit}`;
31740
+ const out = fs22.createWriteStream(logPath);
31741
+ const spinner6 = p5.spinner();
31742
+ spinner6.start("Analyzing repo and drafting trace plans");
31743
+ const stdoutChunks = [];
31744
+ const stdoutRedactor = new StreamingSecretRedactor();
31745
+ const stderrRedactor = new StreamingSecretRedactor();
31746
+ let redactorsFlushed = false;
31747
+ const flushRedactors = () => {
31748
+ if (redactorsFlushed) {
31749
+ return;
31750
+ }
31751
+ redactorsFlushed = true;
31752
+ const stdoutTail = stdoutRedactor.flush();
31753
+ if (stdoutTail !== "") {
31754
+ out.write(stdoutTail);
31755
+ stdoutChunks.push(stdoutTail);
31756
+ }
31757
+ const stderrTail = stderrRedactor.flush();
31758
+ if (stderrTail !== "") {
31759
+ out.write(stderrTail);
31760
+ }
31761
+ };
31762
+ try {
31763
+ const exitCode = await new Promise((resolve2, reject) => {
31764
+ const child = spawn7(
31765
+ "cursor",
31766
+ [
31767
+ "agent",
31768
+ "--print",
31769
+ "--output-format",
31770
+ "stream-json",
31771
+ "--trust",
31772
+ "--approve-mcps",
31773
+ "--force",
31774
+ "--plugin-dir",
31775
+ pluginDir,
31776
+ "--workspace",
31777
+ process.cwd(),
31778
+ command
31779
+ ],
31780
+ { stdio: ["ignore", "pipe", "pipe"], env, ...SHELL_OPTS }
31781
+ );
31782
+ child.stdout.on("data", (chunk2) => {
31783
+ const text = chunk2.toString();
31784
+ const redacted = stdoutRedactor.redact(text);
31785
+ if (redacted !== "") {
31786
+ out.write(redacted);
31787
+ stdoutChunks.push(redacted);
31788
+ }
31789
+ const label = latestCursorActivityLabel(text);
31790
+ if (label) {
31791
+ spinner6.message(label);
31792
+ }
31793
+ });
31794
+ child.stderr.on(
31795
+ "data",
31796
+ (chunk2) => out.write(stderrRedactor.redact(chunk2.toString()))
31797
+ );
31798
+ child.on("error", reject);
31799
+ child.on("close", (code, signal) => {
31800
+ if (code === null) {
31801
+ reject(
31802
+ new Error(
31803
+ `analyze-repo terminated by signal ${signal ?? "unknown"}. See ${logPath}`
31804
+ )
31805
+ );
31806
+ return;
31807
+ }
31808
+ resolve2(code);
31809
+ });
31810
+ });
31811
+ flushRedactors();
31812
+ if (exitCode !== 0) {
31813
+ throw new Error(
31814
+ `analyze-repo exited with status ${exitCode}. See ${logPath}`
31815
+ );
31816
+ }
31817
+ const uploaded = countUploadedTracePlans3(stdoutChunks.join(""));
31818
+ spinner6.stop(
31819
+ uploaded === 0 ? "No AI workflows found - nothing to upload" : `Uploaded ${uploaded} draft trace plan${uploaded === 1 ? "" : "s"} to Bitfab`
31820
+ );
31821
+ } catch (err) {
31822
+ spinner6.stop("analyze-repo failed");
31823
+ throw err;
31824
+ } finally {
31825
+ flushRedactors();
31826
+ out.end();
31827
+ }
31828
+ p5.log.info(`Full run log: ${logPath}`);
31829
+ }
31830
+ function latestCursorActivityLabel(chunk2) {
31831
+ const lines = chunk2.split("\n").filter((l) => l.trim() !== "");
31832
+ for (let i = lines.length - 1; i >= 0; i--) {
31833
+ let event;
31834
+ try {
31835
+ event = JSON.parse(lines[i]);
31836
+ } catch {
31837
+ continue;
31838
+ }
31839
+ const label = cursorActivityLabelFromEvent(event);
31840
+ if (label) {
31841
+ return label;
31842
+ }
31843
+ }
31844
+ return null;
31845
+ }
31846
+ function cursorActivityLabelFromEvent(event) {
31847
+ const toolCall = eventToolCall(event);
31848
+ if (!toolCall) {
31849
+ return null;
31850
+ }
31851
+ const mcpToolName = mcpToolCallName(toolCall);
31852
+ if (mcpToolName?.includes("create_trace_plan")) {
31853
+ return "Uploading a draft trace plan";
31854
+ }
31855
+ if (mcpToolName?.includes("get_bitfab_api_key")) {
31856
+ return "Checking authentication";
31857
+ }
31858
+ if ("grepToolCall" in toolCall) {
31859
+ return "Searching the codebase";
31860
+ }
31861
+ if ("readToolCall" in toolCall) {
31862
+ return "Reading files";
31863
+ }
31864
+ return null;
31865
+ }
31866
+ function eventToolCall(event) {
31867
+ if (typeof event !== "object" || event === null) {
31868
+ return null;
31869
+ }
31870
+ const toolCall = event.tool_call;
31871
+ return typeof toolCall === "object" && toolCall !== null ? toolCall : null;
31872
+ }
31873
+ function mcpToolCallName(toolCall) {
31874
+ const mcpToolCall = toolCall.mcpToolCall;
31875
+ if (typeof mcpToolCall !== "object" || mcpToolCall === null) {
31876
+ return null;
31877
+ }
31878
+ const call = mcpToolCall;
31879
+ const args = call.args;
31880
+ const names = [
31881
+ call.toolName,
31882
+ call.name,
31883
+ typeof args === "object" && args !== null ? args.name : void 0
31884
+ ];
31885
+ return names.find((name) => typeof name === "string") ?? null;
31886
+ }
31887
+ function countUploadedTracePlans3(logText) {
31888
+ let completedPlanCalls = 0;
31889
+ for (const line of logText.split("\n")) {
31890
+ if (line.trim() === "") {
31891
+ continue;
31892
+ }
31893
+ let event;
31894
+ try {
31895
+ event = JSON.parse(line);
31896
+ } catch {
31897
+ continue;
31898
+ }
31899
+ if (isSuccessfulCreateTracePlanEvent(event)) {
31900
+ completedPlanCalls++;
31901
+ }
31902
+ }
31903
+ if (completedPlanCalls > 0) {
31904
+ return completedPlanCalls;
31905
+ }
31906
+ return 0;
31907
+ }
31908
+ function isSuccessfulCreateTracePlanEvent(event) {
31909
+ if (typeof event !== "object" || event === null) {
31910
+ return false;
31911
+ }
31912
+ const e = event;
31913
+ if (e.type !== "tool_call" || e.subtype !== "completed") {
31914
+ return false;
31915
+ }
31916
+ const toolCall = e.tool_call;
31917
+ if (typeof toolCall !== "object" || toolCall === null) {
31918
+ return false;
31919
+ }
31920
+ const mcpToolCall = toolCall.mcpToolCall;
31921
+ if (typeof mcpToolCall !== "object" || mcpToolCall === null) {
31922
+ return false;
31923
+ }
31924
+ const call = mcpToolCall;
31925
+ const args = call.args;
31926
+ const names = [
31927
+ call.toolName,
31928
+ call.name,
31929
+ typeof args === "object" && args !== null ? args.name : void 0
31930
+ ];
31931
+ return names.some(
31932
+ (name) => typeof name === "string" && name.includes("create_trace_plan")
31933
+ ) && hasSuccessfulToolResult(call.result);
31934
+ }
31935
+ function hasSuccessfulToolResult(result) {
31936
+ return typeof result === "object" && result !== null && "success" in result && !("error" in result);
31937
+ }
31938
+ function resolveCursorPluginDir() {
31939
+ const candidates = [
31940
+ path19.join(process.cwd(), "bitfab-cursor-plugin"),
31941
+ path19.join(os17.homedir(), ".cursor", "plugins", "local", "bitfab-dev"),
31942
+ path19.join(os17.homedir(), ".cursor", "plugins", "local", "bitfab")
31943
+ ];
31944
+ for (const candidate of candidates) {
31945
+ if (fs22.existsSync(path19.join(candidate, ".cursor-plugin", "plugin.json")) && fs22.existsSync(path19.join(candidate, "mcp.json"))) {
31946
+ return candidate;
31947
+ }
31948
+ }
31949
+ return null;
31950
+ }
31951
+ function analyzeRepoLogPath3() {
31952
+ const dir = path19.join(process.cwd(), ".bitfab", "logs");
31953
+ fs22.mkdirSync(dir, { recursive: true });
31954
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
31955
+ return path19.join(dir, `analyze-repo-${stamp}.jsonl`);
31423
31956
  }
31424
31957
  function runCursorAssistant(args, skipPermissions) {
31425
31958
  ensureCursorAuthenticated();
@@ -31495,12 +32028,12 @@ var platform = {
31495
32028
  };
31496
32029
 
31497
32030
  // src/version.ts
31498
- import fs22 from "fs";
31499
- import path19 from "path";
32031
+ import fs23 from "fs";
32032
+ import path20 from "path";
31500
32033
  import { fileURLToPath as fileURLToPath4 } from "url";
31501
32034
  var CLI_VERSION = JSON.parse(
31502
- fs22.readFileSync(
31503
- path19.join(path19.dirname(fileURLToPath4(import.meta.url)), "../package.json"),
32035
+ fs23.readFileSync(
32036
+ path20.join(path20.dirname(fileURLToPath4(import.meta.url)), "../package.json"),
31504
32037
  "utf-8"
31505
32038
  )
31506
32039
  ).version;
@@ -31666,10 +32199,10 @@ async function runAnalyzeRepo(opts) {
31666
32199
  p6.intro("Bitfab");
31667
32200
  const chosen = await resolveEditor(opts);
31668
32201
  p6.log.step(`Preparing ${ADAPTERS[chosen].label} plugin`);
31669
- ADAPTERS[chosen].install();
31670
- if (chosen !== "claude") {
31671
- ADAPTERS[chosen].analyzeRepo();
31672
- process.exit(1);
32202
+ if (chosen === "cursor") {
32203
+ ADAPTERS[chosen].ensureLatest();
32204
+ } else {
32205
+ ADAPTERS[chosen].install();
31673
32206
  }
31674
32207
  await ensureAnalyzeRepoAuth();
31675
32208
  const captureOverride = await resolveCaptureOverride(opts.uploadLogs);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitfab-cli",
3
- "version": "0.2.178",
3
+ "version": "0.2.180",
4
4
  "description": "Install and configure the Bitfab plugin in Claude Code, Codex, or Cursor.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",