bitfab-cli 0.2.162 → 0.2.164

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 +285 -11
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9315,6 +9315,16 @@ function getSessionLogConsentValue() {
9315
9315
  }
9316
9316
  return null;
9317
9317
  }
9318
+ function getSessionLogConsent() {
9319
+ return getSessionLogConsentValue();
9320
+ }
9321
+ function setSessionLogConsent(value) {
9322
+ fs3.mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
9323
+ const existing = readJsonFile(GLOBAL_CONFIG_FILE) ?? {};
9324
+ existing.sessionLogConsent = value;
9325
+ fs3.writeFileSync(GLOBAL_CONFIG_FILE, `${JSON.stringify(existing, null, 2)}
9326
+ `);
9327
+ }
9318
9328
  function getConfig() {
9319
9329
  return {
9320
9330
  serviceUrl: getServiceUrl(),
@@ -30739,7 +30749,7 @@ var tracePlanTreeShape = external_exports.object({
30739
30749
  });
30740
30750
 
30741
30751
  // src/claude.ts
30742
- import { spawnSync } from "child_process";
30752
+ import { spawn as spawn5, spawnSync } from "child_process";
30743
30753
  import fs20 from "fs";
30744
30754
  import os15 from "os";
30745
30755
  import path17 from "path";
@@ -30748,6 +30758,7 @@ var REPO = "Project-White-Rabbit/bitfab-claude-plugin";
30748
30758
  var MARKETPLACE = "bitfab";
30749
30759
  var PLUGIN_KEY = "bitfab@bitfab";
30750
30760
  var SETUP_COMMAND = "/bitfab:setup";
30761
+ var ANALYZE_REPO_COMMAND = "/bitfab:setup analyze-repo";
30751
30762
  var ASSISTANT_COMMAND = "/bitfab:assistant";
30752
30763
  var UPDATE_COMMAND = "/bitfab:update";
30753
30764
  var SHELL_OPTS = process.platform === "win32" ? { shell: true } : {};
@@ -30797,6 +30808,171 @@ function runClaudeSetup(skipPermissions) {
30797
30808
  ...SHELL_OPTS
30798
30809
  });
30799
30810
  }
30811
+ async function runClaudeAnalyzeRepo(captureOverride, limit) {
30812
+ const logPath = analyzeRepoLogPath();
30813
+ const env = { ...process.env };
30814
+ if (captureOverride !== void 0) {
30815
+ env.BITFAB_CAPTURE_SESSIONS = captureOverride ? "1" : "0";
30816
+ }
30817
+ const command = limit === void 0 ? ANALYZE_REPO_COMMAND : `${ANALYZE_REPO_COMMAND} limit=${limit}`;
30818
+ const out = fs20.createWriteStream(logPath);
30819
+ const spinner3 = p.spinner();
30820
+ spinner3.start("Analyzing repo and drafting trace plans");
30821
+ const stdoutChunks = [];
30822
+ try {
30823
+ const exitCode = await new Promise((resolve2, reject) => {
30824
+ const child = spawn5(
30825
+ "claude",
30826
+ [
30827
+ "-p",
30828
+ command,
30829
+ "--output-format",
30830
+ "stream-json",
30831
+ "--verbose",
30832
+ "--dangerously-skip-permissions"
30833
+ ],
30834
+ { stdio: ["ignore", "pipe", "pipe"], env, ...SHELL_OPTS }
30835
+ );
30836
+ child.stdout.on("data", (chunk2) => {
30837
+ out.write(chunk2);
30838
+ const text = chunk2.toString();
30839
+ stdoutChunks.push(text);
30840
+ const label = latestActivityLabel(text);
30841
+ if (label) {
30842
+ spinner3.message(label);
30843
+ }
30844
+ });
30845
+ child.stderr.on("data", (chunk2) => out.write(chunk2));
30846
+ child.on("error", reject);
30847
+ child.on("close", (code) => resolve2(code ?? 0));
30848
+ });
30849
+ if (exitCode !== 0) {
30850
+ throw new Error(
30851
+ `analyze-repo exited with status ${exitCode}. See ${logPath}`
30852
+ );
30853
+ }
30854
+ const uploaded = countUploadedTracePlans(stdoutChunks.join(""));
30855
+ spinner3.stop(
30856
+ uploaded === 0 ? "No AI workflows found - nothing to upload" : `Uploaded ${uploaded} draft trace plan${uploaded === 1 ? "" : "s"} to Bitfab`
30857
+ );
30858
+ } catch (err) {
30859
+ spinner3.stop("analyze-repo failed");
30860
+ throw err;
30861
+ } finally {
30862
+ out.end();
30863
+ }
30864
+ p.log.info(`Full run log: ${logPath}`);
30865
+ }
30866
+ function latestActivityLabel(chunk2) {
30867
+ const lines = chunk2.split("\n").filter((l) => l.trim() !== "");
30868
+ for (let i = lines.length - 1; i >= 0; i--) {
30869
+ let event;
30870
+ try {
30871
+ event = JSON.parse(lines[i]);
30872
+ } catch {
30873
+ continue;
30874
+ }
30875
+ const label = activityLabelFromEvent(event);
30876
+ if (label) {
30877
+ return label;
30878
+ }
30879
+ }
30880
+ return null;
30881
+ }
30882
+ function activityLabelFromEvent(event) {
30883
+ const content = eventMessageContent(event);
30884
+ if (!content) {
30885
+ return null;
30886
+ }
30887
+ for (const block of content) {
30888
+ if (typeof block !== "object" || block === null) {
30889
+ continue;
30890
+ }
30891
+ if (block.type !== "tool_use") {
30892
+ continue;
30893
+ }
30894
+ const label = friendlyToolLabel(block);
30895
+ if (label) {
30896
+ return label;
30897
+ }
30898
+ }
30899
+ return null;
30900
+ }
30901
+ function friendlyToolLabel(block) {
30902
+ const name = typeof block.name === "string" ? block.name : "";
30903
+ if (name.includes("create_trace_plan")) {
30904
+ return "Uploading a draft trace plan";
30905
+ }
30906
+ if (name.includes("get_bitfab_api_key")) {
30907
+ return "Checking authentication";
30908
+ }
30909
+ const input = typeof block.input === "object" && block.input !== null ? block.input : {};
30910
+ switch (name) {
30911
+ case "Read": {
30912
+ const file2 = typeof input.file_path === "string" ? path17.basename(input.file_path) : "";
30913
+ return file2 ? `Reading ${file2}` : "Reading files";
30914
+ }
30915
+ case "Grep":
30916
+ case "Glob":
30917
+ return "Searching the codebase";
30918
+ case "WebFetch":
30919
+ return "Reading SDK docs";
30920
+ default:
30921
+ return null;
30922
+ }
30923
+ }
30924
+ function eventMessageContent(event) {
30925
+ if (typeof event !== "object" || event === null) {
30926
+ return null;
30927
+ }
30928
+ const message = event.message;
30929
+ const content = typeof message === "object" && message !== null ? message.content : void 0;
30930
+ return Array.isArray(content) ? content : null;
30931
+ }
30932
+ function countUploadedTracePlans(logText) {
30933
+ const planToolUseIds = /* @__PURE__ */ new Set();
30934
+ const nonErrorResultIds = /* @__PURE__ */ new Set();
30935
+ for (const line of logText.split("\n")) {
30936
+ if (line.trim() === "") {
30937
+ continue;
30938
+ }
30939
+ let event;
30940
+ try {
30941
+ event = JSON.parse(line);
30942
+ } catch {
30943
+ continue;
30944
+ }
30945
+ const content = eventMessageContent(event);
30946
+ if (!content) {
30947
+ continue;
30948
+ }
30949
+ for (const block of content) {
30950
+ if (typeof block !== "object" || block === null) {
30951
+ continue;
30952
+ }
30953
+ const b = block;
30954
+ if (b.type === "tool_use" && typeof b.id === "string" && typeof b.name === "string" && b.name.includes("create_trace_plan")) {
30955
+ planToolUseIds.add(b.id);
30956
+ }
30957
+ if (b.type === "tool_result" && typeof b.tool_use_id === "string" && b.is_error !== true) {
30958
+ nonErrorResultIds.add(b.tool_use_id);
30959
+ }
30960
+ }
30961
+ }
30962
+ let count = 0;
30963
+ for (const id of planToolUseIds) {
30964
+ if (nonErrorResultIds.has(id)) {
30965
+ count++;
30966
+ }
30967
+ }
30968
+ return count;
30969
+ }
30970
+ function analyzeRepoLogPath() {
30971
+ const dir = path17.join(process.cwd(), ".bitfab", "logs");
30972
+ fs20.mkdirSync(dir, { recursive: true });
30973
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
30974
+ return path17.join(dir, `analyze-repo-${stamp}.jsonl`);
30975
+ }
30800
30976
  function runClaudeAssistant(args, skipPermissions) {
30801
30977
  const cmd = [ASSISTANT_COMMAND, ...args].join(" ");
30802
30978
  p.log.info(`Launching ${cmd}...
@@ -30924,6 +31100,14 @@ function runCodexSetup(skipPermissions) {
30924
31100
  ...SHELL_OPTS2
30925
31101
  });
30926
31102
  }
31103
+ function runCodexAnalyzeRepo() {
31104
+ p2.log.warn(
31105
+ "Headless analyze-repo is currently supported for Claude Code only."
31106
+ );
31107
+ p2.log.info(
31108
+ `To run it in Codex, launch ${SETUP_COMMAND2} analyze-repo interactively.`
31109
+ );
31110
+ }
30927
31111
  function runCodexAssistant(args, skipPermissions) {
30928
31112
  const cmd = [ASSISTANT_COMMAND2, ...args].join(" ");
30929
31113
  p2.log.info(`Launching ${cmd}...
@@ -31002,7 +31186,7 @@ function escapeRegex2(s) {
31002
31186
  }
31003
31187
 
31004
31188
  // src/cursor.ts
31005
- import { spawn as spawn5, spawnSync as spawnSync3 } from "child_process";
31189
+ import { spawn as spawn6, spawnSync as spawnSync3 } from "child_process";
31006
31190
  import os17 from "os";
31007
31191
  import * as p3 from "@clack/prompts";
31008
31192
  var REPO3 = "Project-White-Rabbit/bitfab-cursor-plugin";
@@ -31026,7 +31210,7 @@ function runCursorInstall() {
31026
31210
  "Finish setup in Cursor"
31027
31211
  );
31028
31212
  p3.log.info("Opening Cursor...");
31029
- const child = spawn5("cursor", ["."], {
31213
+ const child = spawn6("cursor", ["."], {
31030
31214
  stdio: "ignore",
31031
31215
  detached: true,
31032
31216
  windowsHide: true,
@@ -31042,6 +31226,12 @@ function runCursorSetup(skipPermissions) {
31042
31226
  }
31043
31227
  p3.log.info(`To complete setup, run ${SETUP_COMMAND3} in Cursor.`);
31044
31228
  }
31229
+ function runCursorAnalyzeRepo() {
31230
+ p3.log.warn(
31231
+ "Headless analyze-repo is currently supported for Claude Code only."
31232
+ );
31233
+ p3.log.info(`To run it in Cursor, run ${SETUP_COMMAND3} analyze-repo manually.`);
31234
+ }
31045
31235
  function runCursorAssistant(args, skipPermissions) {
31046
31236
  if (skipPermissions) {
31047
31237
  p3.log.warn("--skip-permissions is not supported for Cursor");
@@ -31140,6 +31330,11 @@ var SETUP_FN = {
31140
31330
  codex: runCodexSetup,
31141
31331
  cursor: runCursorSetup
31142
31332
  };
31333
+ var ANALYZE_REPO_FN = {
31334
+ claude: runClaudeAnalyzeRepo,
31335
+ codex: runCodexAnalyzeRepo,
31336
+ cursor: runCursorAnalyzeRepo
31337
+ };
31143
31338
  var ASSISTANT_FN = {
31144
31339
  claude: runClaudeAssistant,
31145
31340
  codex: runCodexAssistant,
@@ -31194,6 +31389,31 @@ async function resolveSkipPermissions(value) {
31194
31389
  }
31195
31390
  return answer;
31196
31391
  }
31392
+ async function resolveCaptureOverride(flag) {
31393
+ if (flag !== void 0) {
31394
+ return flag;
31395
+ }
31396
+ const existing = getSessionLogConsent();
31397
+ if (existing !== null) {
31398
+ p4.log.step(
31399
+ existing ? "Session logs: enabled (saved preference)." : "Session logs: disabled (saved preference)."
31400
+ );
31401
+ return void 0;
31402
+ }
31403
+ if (!process.stdin.isTTY) {
31404
+ return false;
31405
+ }
31406
+ const answer = await p4.confirm({
31407
+ message: "Allow Bitfab to collect session logs? (used to diagnose issues and improve the product)",
31408
+ initialValue: true
31409
+ });
31410
+ if (p4.isCancel(answer)) {
31411
+ p4.cancel("Cancelled.");
31412
+ process.exit(0);
31413
+ }
31414
+ setSessionLogConsent(answer);
31415
+ return void 0;
31416
+ }
31197
31417
  async function resolveEditor({ editor }) {
31198
31418
  const detected = detectInstalledEditors();
31199
31419
  if (detected.length === 0) {
@@ -31265,6 +31485,27 @@ async function runInit(opts) {
31265
31485
  }
31266
31486
  p4.outro("Done!");
31267
31487
  }
31488
+ async function runAnalyzeRepo(opts) {
31489
+ p4.intro("bitfab");
31490
+ const chosen = await resolveEditor(opts);
31491
+ p4.log.step(`Setting up Bitfab in ${EDITOR_LABEL[chosen]}`);
31492
+ INSTALL_FN[chosen]();
31493
+ if (chosen !== "claude") {
31494
+ ANALYZE_REPO_FN[chosen]();
31495
+ process.exit(1);
31496
+ }
31497
+ const loggedInEmail = await checkAuth();
31498
+ if (!loggedInEmail) {
31499
+ p4.cancel(
31500
+ "Not authenticated. Run `bitfab login` (or set BITFAB_API_KEY) before `bitfab analyze-repo` - it is non-interactive and cannot open a browser."
31501
+ );
31502
+ process.exit(1);
31503
+ }
31504
+ p4.log.success(`Logged in as ${loggedInEmail}`);
31505
+ const captureOverride = await resolveCaptureOverride(opts.uploadLogs);
31506
+ await ANALYZE_REPO_FN[chosen](captureOverride, opts.limit);
31507
+ p4.outro("Done!");
31508
+ }
31268
31509
  async function checkAuth() {
31269
31510
  const { apiKey, serviceUrl } = getConfig();
31270
31511
  if (!apiKey) {
@@ -31308,18 +31549,17 @@ function startUpdateCheck() {
31308
31549
  const controller = new AbortController();
31309
31550
  const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
31310
31551
  const current = getVersion();
31552
+ let notice = null;
31311
31553
  const done = fetch(NPM_REGISTRY_URL, { signal: controller.signal }).then(
31312
31554
  (res) => res.ok ? res.json() : null
31313
31555
  ).then((data) => {
31314
31556
  const latest = data?.version;
31315
31557
  if (latest && isNewer(latest, current)) {
31316
- process.stderr.write(
31317
- `
31558
+ notice = `
31318
31559
  Update available: ${current} -> ${latest}
31319
31560
  Run \`npx bitfab-cli@latest\` to get the newest version.
31320
31561
 
31321
- `
31322
- );
31562
+ `;
31323
31563
  }
31324
31564
  }).catch(() => {
31325
31565
  }).finally(() => clearTimeout(timer));
@@ -31327,6 +31567,9 @@ function startUpdateCheck() {
31327
31567
  controller.abort();
31328
31568
  done.catch(() => {
31329
31569
  });
31570
+ if (notice) {
31571
+ process.stderr.write(notice);
31572
+ }
31330
31573
  };
31331
31574
  }
31332
31575
 
@@ -31339,6 +31582,7 @@ Commands:
31339
31582
  login Authenticate with Bitfab (opens browser)
31340
31583
  logout Remove stored credentials
31341
31584
  setup [--editor <name>] Launch /bitfab:setup in the editor
31585
+ analyze-repo [--editor <name>] Headless scan: draft + upload trace plans (no prompts, no code edits)
31342
31586
  assistant [--editor <name>] [args] Launch /bitfab:assistant in the editor
31343
31587
  update [--editor <name>] [mode] Update plugin, then launch SDK update (mode: all|plugin|sdk)
31344
31588
  help Show this help
@@ -31347,14 +31591,26 @@ Options:
31347
31591
  --editor, -e <name> Target editor: claude, codex, cursor
31348
31592
  --skip-permissions Skip permission prompts (runs the agent autonomously)
31349
31593
  --no-skip-permissions Keep permission prompts (default, skips the interactive question)
31594
+ --upload-logs analyze-repo: upload the run's session logs to Bitfab (skips the prompt)
31595
+ --no-upload-logs analyze-repo: keep session logs local (skips the prompt)
31596
+ --limit <n> analyze-repo: cap how many draft trace plans to upload (default 5)
31350
31597
 
31351
31598
  Examples:
31352
31599
  bitfab init Full setup (detect editor, install, login, setup)
31353
31600
  bitfab init --editor claude Full setup for Claude Code
31601
+ bitfab analyze-repo Scan the repo and upload draft trace plans (headless)
31602
+ bitfab analyze-repo --limit 3 Scan the repo and upload at most 3 draft trace plans
31354
31603
  bitfab assistant investigate Investigate traces
31355
31604
  bitfab setup --skip-permissions Setup without permission prompts
31356
31605
  bitfab assistant --skip-permissions Run assistant autonomously
31357
31606
  `;
31607
+ function parseLimit(raw) {
31608
+ const n = Number(raw);
31609
+ if (!Number.isInteger(n) || n < 1) {
31610
+ throw new Error(`--limit must be a positive integer, got "${raw}"`);
31611
+ }
31612
+ return n;
31613
+ }
31358
31614
  function wantsHelp(argv) {
31359
31615
  const command = argv[0];
31360
31616
  return !command || command === "help" || argv.includes("-h") || argv.includes("--help");
@@ -31363,6 +31619,8 @@ function parseArgs2(argv) {
31363
31619
  const [command, ...tail] = argv;
31364
31620
  let editor;
31365
31621
  let skipPermissions;
31622
+ let uploadLogs;
31623
+ let limit;
31366
31624
  const rest = [];
31367
31625
  for (let i = 0; i < tail.length; i++) {
31368
31626
  const arg = tail[i];
@@ -31379,16 +31637,27 @@ function parseArgs2(argv) {
31379
31637
  skipPermissions = true;
31380
31638
  } else if (arg === "--no-skip-permissions") {
31381
31639
  skipPermissions = false;
31640
+ } else if (arg === "--upload-logs") {
31641
+ uploadLogs = true;
31642
+ } else if (arg === "--no-upload-logs") {
31643
+ uploadLogs = false;
31644
+ } else if (arg === "--limit") {
31645
+ if (i + 1 < tail.length) {
31646
+ limit = parseLimit(tail[i + 1]);
31647
+ i++;
31648
+ } else {
31649
+ throw new Error("--limit flag requires a value");
31650
+ }
31651
+ } else if (arg?.startsWith("--limit=")) {
31652
+ limit = parseLimit(arg.slice("--limit=".length));
31382
31653
  } else if (arg !== void 0) {
31383
31654
  rest.push(arg);
31384
31655
  }
31385
31656
  }
31386
- return { command, editor, skipPermissions, rest };
31657
+ return { command, editor, skipPermissions, uploadLogs, limit, rest };
31387
31658
  }
31388
31659
  async function main() {
31389
- const { command, editor, skipPermissions, rest } = parseArgs2(
31390
- process.argv.slice(2)
31391
- );
31660
+ const { command, editor, skipPermissions, uploadLogs, limit, rest } = parseArgs2(process.argv.slice(2));
31392
31661
  const abortUpdateCheck = startUpdateCheck();
31393
31662
  if (wantsHelp(process.argv.slice(2))) {
31394
31663
  process.stdout.write(HELP_TEXT);
@@ -31420,6 +31689,11 @@ async function main() {
31420
31689
  abortUpdateCheck();
31421
31690
  return;
31422
31691
  }
31692
+ if (command === "analyze-repo") {
31693
+ await runAnalyzeRepo({ editor, skipPermissions, uploadLogs, limit });
31694
+ abortUpdateCheck();
31695
+ return;
31696
+ }
31423
31697
  if (command === "assistant") {
31424
31698
  await runAssistant({ editor, skipPermissions }, rest);
31425
31699
  abortUpdateCheck();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitfab-cli",
3
- "version": "0.2.162",
3
+ "version": "0.2.164",
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",