bitfab-cli 0.2.185 → 0.2.187
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.
- package/dist/index.js +734 -10
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9566,6 +9566,40 @@ function updateActiveStudioSession(updates) {
|
|
|
9566
9566
|
writeStateFile(sessionFilePath(), updated);
|
|
9567
9567
|
return updated;
|
|
9568
9568
|
}
|
|
9569
|
+
function clearActiveStudioSession(sessionId, opts = {}) {
|
|
9570
|
+
try {
|
|
9571
|
+
const current = readStateFile(sessionFilePath());
|
|
9572
|
+
if (!current) {
|
|
9573
|
+
return clearLegacySession(sessionId, opts);
|
|
9574
|
+
}
|
|
9575
|
+
if (current.sessionId !== sessionId) {
|
|
9576
|
+
return false;
|
|
9577
|
+
}
|
|
9578
|
+
if (!opts.force && current.pollerPid != null && current.pollerPid !== process.pid && isProcessAlive(current.pollerPid)) {
|
|
9579
|
+
return false;
|
|
9580
|
+
}
|
|
9581
|
+
fs7.unlinkSync(sessionFilePath());
|
|
9582
|
+
return true;
|
|
9583
|
+
} catch {
|
|
9584
|
+
return false;
|
|
9585
|
+
}
|
|
9586
|
+
}
|
|
9587
|
+
function clearLegacySession(sessionId, opts = {}) {
|
|
9588
|
+
try {
|
|
9589
|
+
const raw = fs7.readFileSync(legacyFilePath(), "utf-8");
|
|
9590
|
+
const parsed = JSON.parse(raw);
|
|
9591
|
+
if (parsed.sessionId !== sessionId) {
|
|
9592
|
+
return false;
|
|
9593
|
+
}
|
|
9594
|
+
if (!opts.force && typeof parsed.pid === "number" && parsed.pid !== process.pid && isProcessAlive(parsed.pid)) {
|
|
9595
|
+
return false;
|
|
9596
|
+
}
|
|
9597
|
+
fs7.unlinkSync(legacyFilePath());
|
|
9598
|
+
return true;
|
|
9599
|
+
} catch {
|
|
9600
|
+
return false;
|
|
9601
|
+
}
|
|
9602
|
+
}
|
|
9569
9603
|
function isProcessAlive(pid) {
|
|
9570
9604
|
try {
|
|
9571
9605
|
process.kill(pid, 0);
|
|
@@ -11003,6 +11037,48 @@ async function resolveChannel(apiKey, serviceUrl) {
|
|
|
11003
11037
|
return new DirectChannel(apiKey, serviceUrl);
|
|
11004
11038
|
}
|
|
11005
11039
|
|
|
11040
|
+
// ../bitfab-plugin-lib/dist/studioTeardown.js
|
|
11041
|
+
function killStudioWindow(sessionId) {
|
|
11042
|
+
const record2 = readActiveStudioSession();
|
|
11043
|
+
if (record2?.sessionId !== sessionId || record2.windowPid == null) {
|
|
11044
|
+
return;
|
|
11045
|
+
}
|
|
11046
|
+
try {
|
|
11047
|
+
process.kill(record2.windowPid, "SIGTERM");
|
|
11048
|
+
} catch {
|
|
11049
|
+
}
|
|
11050
|
+
}
|
|
11051
|
+
function killPollerProcess(sessionId) {
|
|
11052
|
+
const record2 = readActiveStudioSession();
|
|
11053
|
+
if (record2?.sessionId !== sessionId || record2.pollerPid == null || record2.pollerPid === process.pid) {
|
|
11054
|
+
return;
|
|
11055
|
+
}
|
|
11056
|
+
try {
|
|
11057
|
+
process.kill(record2.pollerPid, "SIGTERM");
|
|
11058
|
+
} catch {
|
|
11059
|
+
}
|
|
11060
|
+
}
|
|
11061
|
+
|
|
11062
|
+
// ../bitfab-plugin-lib/dist/commands/clearStudioSession.js
|
|
11063
|
+
async function clearStudioSessionById(sessionId, record2) {
|
|
11064
|
+
killStudioWindow(sessionId);
|
|
11065
|
+
const alreadyClosed = record2?.sessionId === sessionId && record2.windowState === "closed";
|
|
11066
|
+
if (!alreadyClosed) {
|
|
11067
|
+
closeStudioWindowsBySession(sessionId);
|
|
11068
|
+
}
|
|
11069
|
+
killPollerProcess(sessionId);
|
|
11070
|
+
try {
|
|
11071
|
+
const channel = await resolveChannel("", record2?.serviceUrl ?? "");
|
|
11072
|
+
try {
|
|
11073
|
+
await channel.clearSession(sessionId);
|
|
11074
|
+
} finally {
|
|
11075
|
+
channel.destroy();
|
|
11076
|
+
}
|
|
11077
|
+
} catch {
|
|
11078
|
+
}
|
|
11079
|
+
return clearActiveStudioSession(sessionId, { force: true });
|
|
11080
|
+
}
|
|
11081
|
+
|
|
11006
11082
|
// ../bitfab-plugin-lib/dist/replayCapabilities.js
|
|
11007
11083
|
var semver = __toESM(require_semver2(), 1);
|
|
11008
11084
|
|
|
@@ -11451,6 +11527,13 @@ Already logged in as ${identity}${endpoint}. Run the login command with --force
|
|
|
11451
11527
|
return;
|
|
11452
11528
|
}
|
|
11453
11529
|
}
|
|
11530
|
+
if (force) {
|
|
11531
|
+
const record2 = readActiveStudioSession();
|
|
11532
|
+
if (record2) {
|
|
11533
|
+
await clearStudioSessionById(record2.sessionId, record2).catch(() => {
|
|
11534
|
+
});
|
|
11535
|
+
}
|
|
11536
|
+
}
|
|
11454
11537
|
try {
|
|
11455
11538
|
let printedSignInUrl = false;
|
|
11456
11539
|
const result = await openStudioTo("/studio", {
|
|
@@ -11495,7 +11578,7 @@ ${greeting}`);
|
|
|
11495
11578
|
} catch (err) {
|
|
11496
11579
|
if (exitOnComplete) {
|
|
11497
11580
|
if (err instanceof StudioNavigationError && err.staleSessionId) {
|
|
11498
|
-
console.error("\nA Studio window is recorded as open but is not responding. Close
|
|
11581
|
+
console.error("\nA Studio window is recorded as open but is not responding. Close the Studio window and try again. To force-clear a stale session, a user can re-run with `bitfab login --force` (manual recovery - automated agents should not run it).");
|
|
11499
11582
|
} else {
|
|
11500
11583
|
console.error(`
|
|
11501
11584
|
${err.message}`);
|
|
@@ -30999,6 +31082,16 @@ var SETUP_COMMAND = "/bitfab:setup";
|
|
|
30999
31082
|
var ANALYZE_REPO_COMMAND = "/bitfab:setup analyze-repo";
|
|
31000
31083
|
var ASSISTANT_COMMAND = "/bitfab:assistant";
|
|
31001
31084
|
var UPDATE_COMMAND = "/bitfab:update";
|
|
31085
|
+
var ANSI = {
|
|
31086
|
+
reset: "\x1B[0m",
|
|
31087
|
+
bold: "\x1B[1m",
|
|
31088
|
+
dim: "\x1B[2m",
|
|
31089
|
+
green: "\x1B[32m",
|
|
31090
|
+
yellow: "\x1B[33m",
|
|
31091
|
+
cyan: "\x1B[36m",
|
|
31092
|
+
magenta: "\x1B[35m",
|
|
31093
|
+
red: "\x1B[31m"
|
|
31094
|
+
};
|
|
31002
31095
|
function isClaudeAuthStatusLoggedIn(output) {
|
|
31003
31096
|
try {
|
|
31004
31097
|
const status = JSON.parse(output);
|
|
@@ -31016,6 +31109,12 @@ var checkClaudeAuth = makeCliAuthCheck({
|
|
|
31016
31109
|
});
|
|
31017
31110
|
function runClaudeInstall() {
|
|
31018
31111
|
const s = p3.spinner();
|
|
31112
|
+
const localPluginKey = activeLocalClaudeBitfabPlugin();
|
|
31113
|
+
if (localPluginKey !== null) {
|
|
31114
|
+
p3.log.step(`Local Bitfab plugin already enabled (${localPluginKey})`);
|
|
31115
|
+
p3.log.success("Bitfab plugin ready in Claude Code");
|
|
31116
|
+
return;
|
|
31117
|
+
}
|
|
31019
31118
|
s.start("Adding bitfab marketplace");
|
|
31020
31119
|
const marketplaceOut = runCli(CLI, ["plugin", "marketplace", "add", REPO]);
|
|
31021
31120
|
if (marketplaceOut.includes("already")) {
|
|
@@ -31046,6 +31145,37 @@ function runClaudeInstall() {
|
|
|
31046
31145
|
}
|
|
31047
31146
|
p3.log.success("Bitfab plugin ready in Claude Code");
|
|
31048
31147
|
}
|
|
31148
|
+
function activeLocalClaudeBitfabPlugin() {
|
|
31149
|
+
const settingsPath = path17.join(
|
|
31150
|
+
process.cwd(),
|
|
31151
|
+
".claude",
|
|
31152
|
+
"settings.local.json"
|
|
31153
|
+
);
|
|
31154
|
+
if (!fs20.existsSync(settingsPath)) {
|
|
31155
|
+
return null;
|
|
31156
|
+
}
|
|
31157
|
+
let settings;
|
|
31158
|
+
try {
|
|
31159
|
+
settings = JSON.parse(fs20.readFileSync(settingsPath, "utf-8"));
|
|
31160
|
+
} catch {
|
|
31161
|
+
return null;
|
|
31162
|
+
}
|
|
31163
|
+
if (typeof settings !== "object" || settings === null) {
|
|
31164
|
+
return null;
|
|
31165
|
+
}
|
|
31166
|
+
const enabledPlugins = settings.enabledPlugins;
|
|
31167
|
+
if (typeof enabledPlugins !== "object" || enabledPlugins === null) {
|
|
31168
|
+
return null;
|
|
31169
|
+
}
|
|
31170
|
+
for (const [key, enabled] of Object.entries(
|
|
31171
|
+
enabledPlugins
|
|
31172
|
+
)) {
|
|
31173
|
+
if (key.startsWith("bitfab@") && key !== PLUGIN_KEY && enabled === true) {
|
|
31174
|
+
return key;
|
|
31175
|
+
}
|
|
31176
|
+
}
|
|
31177
|
+
return null;
|
|
31178
|
+
}
|
|
31049
31179
|
function pullLatestClaudePlugin() {
|
|
31050
31180
|
runCli(CLI, ["plugin", "marketplace", "update", MARKETPLACE]);
|
|
31051
31181
|
return runCli(CLI, ["plugin", "update", PLUGIN_KEY, "--scope", "user"]);
|
|
@@ -31140,10 +31270,18 @@ async function runClaudeAnalyzeRepo(captureOverride, limit, prompt) {
|
|
|
31140
31270
|
`analyze-repo exited with status ${exitCode}. See ${logPath}`
|
|
31141
31271
|
);
|
|
31142
31272
|
}
|
|
31143
|
-
const
|
|
31273
|
+
const logText = stdoutChunks.join("");
|
|
31274
|
+
const uploaded = countUploadedTracePlans(logText);
|
|
31144
31275
|
spinner6.stop(
|
|
31145
31276
|
uploaded === 0 ? "No AI workflows found - nothing to upload" : `Uploaded ${uploaded} draft trace plan${uploaded === 1 ? "" : "s"} to Bitfab`
|
|
31146
31277
|
);
|
|
31278
|
+
const report = extractAnalyzeRepoReport(logText);
|
|
31279
|
+
if (report !== null) {
|
|
31280
|
+
process.stdout.write(`
|
|
31281
|
+
${formatAnalyzeRepoReport(report)}
|
|
31282
|
+
|
|
31283
|
+
`);
|
|
31284
|
+
}
|
|
31147
31285
|
} catch (err) {
|
|
31148
31286
|
spinner6.stop("analyze-repo failed");
|
|
31149
31287
|
throw err;
|
|
@@ -31153,6 +31291,557 @@ async function runClaudeAnalyzeRepo(captureOverride, limit, prompt) {
|
|
|
31153
31291
|
}
|
|
31154
31292
|
p3.log.info(`Full run log: ${logPath}`);
|
|
31155
31293
|
}
|
|
31294
|
+
function extractAnalyzeRepoReport(logText) {
|
|
31295
|
+
let lastAssistantText = null;
|
|
31296
|
+
let lastSubstantiveReport = null;
|
|
31297
|
+
let lastResultText = null;
|
|
31298
|
+
for (const line of logText.split("\n")) {
|
|
31299
|
+
if (line.trim() === "") {
|
|
31300
|
+
continue;
|
|
31301
|
+
}
|
|
31302
|
+
let event;
|
|
31303
|
+
try {
|
|
31304
|
+
event = JSON.parse(line);
|
|
31305
|
+
} catch {
|
|
31306
|
+
continue;
|
|
31307
|
+
}
|
|
31308
|
+
for (const assistantText of textCandidatesFromEvent(event)) {
|
|
31309
|
+
if (uploadedHeadingFromLine(assistantText) === null) {
|
|
31310
|
+
continue;
|
|
31311
|
+
}
|
|
31312
|
+
lastAssistantText = assistantText;
|
|
31313
|
+
lastSubstantiveReport = extractSubstantiveAnalyzeRepoReport(assistantText) ?? lastSubstantiveReport;
|
|
31314
|
+
}
|
|
31315
|
+
const resultText = resultTextFromEvent(event);
|
|
31316
|
+
if (resultText !== null && uploadedHeadingFromLine(resultText) !== null) {
|
|
31317
|
+
lastResultText = resultText;
|
|
31318
|
+
}
|
|
31319
|
+
}
|
|
31320
|
+
return lastSubstantiveReport ?? lastAssistantText ?? lastResultText;
|
|
31321
|
+
}
|
|
31322
|
+
function extractSubstantiveAnalyzeRepoReport(text) {
|
|
31323
|
+
const lines = text.trim().split(/\r?\n/);
|
|
31324
|
+
const tableStart = lines.findIndex((line, index) => {
|
|
31325
|
+
const next = lines[index + 1];
|
|
31326
|
+
return isMarkdownTableRow(line) && next !== void 0 && isTableDivider(next);
|
|
31327
|
+
});
|
|
31328
|
+
if (tableStart === -1) {
|
|
31329
|
+
return null;
|
|
31330
|
+
}
|
|
31331
|
+
const headingIndex = lines.findIndex((line, index) => {
|
|
31332
|
+
return index <= tableStart && uploadedHeadingFromLine(line) !== null;
|
|
31333
|
+
});
|
|
31334
|
+
if (headingIndex === -1) {
|
|
31335
|
+
return null;
|
|
31336
|
+
}
|
|
31337
|
+
let tableEnd = tableStart + 2;
|
|
31338
|
+
while (tableEnd < lines.length && isMarkdownTableRow(lines[tableEnd])) {
|
|
31339
|
+
tableEnd++;
|
|
31340
|
+
}
|
|
31341
|
+
const heading = uploadedHeadingFromLine(lines[headingIndex]);
|
|
31342
|
+
if (heading === null) {
|
|
31343
|
+
return null;
|
|
31344
|
+
}
|
|
31345
|
+
const followUp = usefulAnalyzeRepoFollowUp(lines.slice(tableEnd));
|
|
31346
|
+
return [heading, "", ...lines.slice(tableStart, tableEnd), ...followUp].join("\n").trim();
|
|
31347
|
+
}
|
|
31348
|
+
function formatAnalyzeRepoReport(report, options = {}) {
|
|
31349
|
+
const style = createAnalyzeRepoReportStyle(options);
|
|
31350
|
+
const lines = report.trim().split(/\r?\n/);
|
|
31351
|
+
const tableStart = lines.findIndex((line, index) => {
|
|
31352
|
+
const next = lines[index + 1];
|
|
31353
|
+
return isMarkdownTableRow(line) && next !== void 0 && isTableDivider(next);
|
|
31354
|
+
});
|
|
31355
|
+
if (tableStart === -1) {
|
|
31356
|
+
return styleNonTableReport(hideTracePlanLinks(report.trim()), style);
|
|
31357
|
+
}
|
|
31358
|
+
const headers = splitMarkdownTableRow(lines[tableStart]);
|
|
31359
|
+
const fieldValuePlan = formatFieldValuePlanTable(lines, tableStart, style);
|
|
31360
|
+
if (fieldValuePlan !== null) {
|
|
31361
|
+
return fieldValuePlan;
|
|
31362
|
+
}
|
|
31363
|
+
const indexes = {
|
|
31364
|
+
key: findColumnIndex(headers, ["trace function key", "function", "key"]),
|
|
31365
|
+
boundary: findColumnIndex(headers, ["boundary"]),
|
|
31366
|
+
description: findColumnIndex(headers, [
|
|
31367
|
+
"description of workflow",
|
|
31368
|
+
"workflow description",
|
|
31369
|
+
"description"
|
|
31370
|
+
]),
|
|
31371
|
+
frameworksUsed: findColumnIndex(headers, ["frameworks used", "frameworks"]),
|
|
31372
|
+
refactorComplexity: findColumnIndex(headers, [
|
|
31373
|
+
"refactor complexity to instrument",
|
|
31374
|
+
"refactor complexity",
|
|
31375
|
+
"complexity"
|
|
31376
|
+
]),
|
|
31377
|
+
methodsToCapture: findColumnIndex(headers, [
|
|
31378
|
+
"suggested methods to capture",
|
|
31379
|
+
"suggested methods to captured",
|
|
31380
|
+
"methods to capture",
|
|
31381
|
+
"methods captured"
|
|
31382
|
+
]),
|
|
31383
|
+
methodsToMock: findColumnIndex(headers, [
|
|
31384
|
+
"number of methods that need to mocked",
|
|
31385
|
+
"number of methods that need to be mocked",
|
|
31386
|
+
"methods that need to be mocked",
|
|
31387
|
+
"methods to mock",
|
|
31388
|
+
"mocked methods"
|
|
31389
|
+
]),
|
|
31390
|
+
realDataImprovement: findColumnIndex(headers, [
|
|
31391
|
+
"real data",
|
|
31392
|
+
"feature would improve",
|
|
31393
|
+
"improvement",
|
|
31394
|
+
"why",
|
|
31395
|
+
"worth tracing",
|
|
31396
|
+
"rationale"
|
|
31397
|
+
])
|
|
31398
|
+
};
|
|
31399
|
+
if (indexes.key === -1) {
|
|
31400
|
+
return styleNonTableReport(hideTracePlanLinks(report.trim()), style);
|
|
31401
|
+
}
|
|
31402
|
+
let tableEnd = tableStart + 2;
|
|
31403
|
+
while (tableEnd < lines.length && isMarkdownTableRow(lines[tableEnd])) {
|
|
31404
|
+
tableEnd++;
|
|
31405
|
+
}
|
|
31406
|
+
const rows = lines.slice(tableStart + 2, tableEnd).map(splitMarkdownTableRow).filter((columns) => columns.length > indexes.key);
|
|
31407
|
+
if (rows.length === 0) {
|
|
31408
|
+
return styleNonTableReport(hideTracePlanLinks(report.trim()), style);
|
|
31409
|
+
}
|
|
31410
|
+
const before = lines.slice(0, tableStart).join("\n").trim();
|
|
31411
|
+
const after = formatTrailingReportBlock(lines.slice(tableEnd), style);
|
|
31412
|
+
const formattedRows = rows.map(
|
|
31413
|
+
(columns, index) => formatPlanRow(style, index + 1, {
|
|
31414
|
+
key: columns[indexes.key] ?? "",
|
|
31415
|
+
boundary: indexes.boundary === -1 ? "" : columns[indexes.boundary] ?? "",
|
|
31416
|
+
description: indexes.description === -1 ? "" : columns[indexes.description] ?? "",
|
|
31417
|
+
frameworksUsed: indexes.frameworksUsed === -1 ? "" : columns[indexes.frameworksUsed] ?? "",
|
|
31418
|
+
refactorComplexity: indexes.refactorComplexity === -1 ? "" : columns[indexes.refactorComplexity] ?? "",
|
|
31419
|
+
methodsToCapture: indexes.methodsToCapture === -1 ? "" : columns[indexes.methodsToCapture] ?? "",
|
|
31420
|
+
methodsToMock: indexes.methodsToMock === -1 ? "" : columns[indexes.methodsToMock] ?? "",
|
|
31421
|
+
realDataImprovement: indexes.realDataImprovement === -1 ? "" : columns[indexes.realDataImprovement] ?? ""
|
|
31422
|
+
})
|
|
31423
|
+
).join("\n\n");
|
|
31424
|
+
return hideTracePlanLinks(
|
|
31425
|
+
[styleReportBlock(before, style), formattedRows, after].filter((part) => part !== "").join("\n\n")
|
|
31426
|
+
);
|
|
31427
|
+
}
|
|
31428
|
+
function formatPlanRow(style, number4, fields) {
|
|
31429
|
+
const lines = [
|
|
31430
|
+
`${number4}. ${style.bold(style.cyan(cleanInline(fields.key)))}`
|
|
31431
|
+
];
|
|
31432
|
+
if (fields.description !== "") {
|
|
31433
|
+
lines.push(formatLabeledLine(style, "Workflow", fields.description));
|
|
31434
|
+
}
|
|
31435
|
+
if (fields.boundary !== "") {
|
|
31436
|
+
lines.push(formatLabeledLine(style, "Boundary", fields.boundary));
|
|
31437
|
+
}
|
|
31438
|
+
if (fields.frameworksUsed !== "") {
|
|
31439
|
+
lines.push(formatLabeledLine(style, "Uses", fields.frameworksUsed));
|
|
31440
|
+
}
|
|
31441
|
+
const captureParts = [
|
|
31442
|
+
fields.refactorComplexity === "" ? "" : instrumentationComplexityLabel(style, fields.refactorComplexity),
|
|
31443
|
+
fields.methodsToCapture === "" ? "" : `Methods: ${fields.methodsToCapture}`
|
|
31444
|
+
].filter((part) => part !== "");
|
|
31445
|
+
if (captureParts.length > 0) {
|
|
31446
|
+
lines.push(
|
|
31447
|
+
formatLabeledLine(style, "Instrumentation", captureParts.join(". "))
|
|
31448
|
+
);
|
|
31449
|
+
}
|
|
31450
|
+
const replayParts = [
|
|
31451
|
+
fields.methodsToMock === "" ? "" : mockReplayLabel(fields.methodsToMock)
|
|
31452
|
+
].filter((part) => part !== "");
|
|
31453
|
+
if (replayParts.length > 0) {
|
|
31454
|
+
lines.push(formatLabeledLine(style, "Mocks", replayParts.join(". ")));
|
|
31455
|
+
}
|
|
31456
|
+
if (fields.realDataImprovement !== "") {
|
|
31457
|
+
lines.push(formatLabeledLine(style, "Value", fields.realDataImprovement));
|
|
31458
|
+
}
|
|
31459
|
+
return lines.join("\n");
|
|
31460
|
+
}
|
|
31461
|
+
function mockReplayLabel(value) {
|
|
31462
|
+
const trimmed = value.trim();
|
|
31463
|
+
const countWithNames = trimmed.match(/^(\d+)\s*\((.+)\)$/);
|
|
31464
|
+
if (countWithNames) {
|
|
31465
|
+
return `${mockCountLabel(countWithNames[1])}: ${countWithNames[2]}`;
|
|
31466
|
+
}
|
|
31467
|
+
const countWithDetails = trimmed.match(/^(\d+)\s*[:—-]\s*(.+)$/);
|
|
31468
|
+
if (countWithDetails) {
|
|
31469
|
+
return `${mockCountLabel(countWithDetails[1])}: ${countWithDetails[2]}`;
|
|
31470
|
+
}
|
|
31471
|
+
return /^\d+$/.test(trimmed) ? mockCountLabel(trimmed) : trimmed;
|
|
31472
|
+
}
|
|
31473
|
+
function mockCountLabel(count) {
|
|
31474
|
+
return `${count} method${count === "1" ? "" : "s"} mocked on replay`;
|
|
31475
|
+
}
|
|
31476
|
+
function styleInlineValue(value, style) {
|
|
31477
|
+
const parts = value.split(/(`[^`]+`)/g);
|
|
31478
|
+
return parts.map((part) => {
|
|
31479
|
+
if (part.startsWith("`") && part.endsWith("`")) {
|
|
31480
|
+
return style.cyan(part.slice(1, -1));
|
|
31481
|
+
}
|
|
31482
|
+
return cleanInline(part);
|
|
31483
|
+
}).join("");
|
|
31484
|
+
}
|
|
31485
|
+
function cleanInline(value) {
|
|
31486
|
+
return value.replace(/[`*_]/g, "");
|
|
31487
|
+
}
|
|
31488
|
+
function formatFieldValuePlanTable(lines, tableStart, style) {
|
|
31489
|
+
const headers = splitMarkdownTableRow(lines[tableStart]).map(
|
|
31490
|
+
(header) => normalizeMarkdownText(header)
|
|
31491
|
+
);
|
|
31492
|
+
if (headers.length !== 2 || headers[0] !== "field" || headers[1] !== "value" && headers[1] !== "detail") {
|
|
31493
|
+
return null;
|
|
31494
|
+
}
|
|
31495
|
+
let tableEnd = tableStart + 2;
|
|
31496
|
+
while (tableEnd < lines.length && isMarkdownTableRow(lines[tableEnd])) {
|
|
31497
|
+
tableEnd++;
|
|
31498
|
+
}
|
|
31499
|
+
const values = /* @__PURE__ */ new Map();
|
|
31500
|
+
for (const row of lines.slice(tableStart + 2, tableEnd)) {
|
|
31501
|
+
const [field, value] = splitMarkdownTableRow(row);
|
|
31502
|
+
if (field === void 0 || value === void 0) {
|
|
31503
|
+
continue;
|
|
31504
|
+
}
|
|
31505
|
+
values.set(normalizeMarkdownText(field), value);
|
|
31506
|
+
}
|
|
31507
|
+
const key = firstFieldValue(values, [
|
|
31508
|
+
"trace function key",
|
|
31509
|
+
"trace function",
|
|
31510
|
+
"function",
|
|
31511
|
+
"key"
|
|
31512
|
+
]);
|
|
31513
|
+
if (key === "") {
|
|
31514
|
+
return null;
|
|
31515
|
+
}
|
|
31516
|
+
return hideTracePlanLinks(
|
|
31517
|
+
[
|
|
31518
|
+
styleReportBlock(lines.slice(0, tableStart).join("\n").trim(), style),
|
|
31519
|
+
formatPlanRow(style, 1, {
|
|
31520
|
+
key,
|
|
31521
|
+
boundary: firstFieldValue(values, ["boundary"]),
|
|
31522
|
+
description: firstFieldValue(values, [
|
|
31523
|
+
"workflow",
|
|
31524
|
+
"description of workflow",
|
|
31525
|
+
"workflow description",
|
|
31526
|
+
"description"
|
|
31527
|
+
]),
|
|
31528
|
+
frameworksUsed: firstFieldValue(values, [
|
|
31529
|
+
"frameworks used",
|
|
31530
|
+
"frameworks"
|
|
31531
|
+
]),
|
|
31532
|
+
refactorComplexity: firstFieldValue(values, [
|
|
31533
|
+
"refactor complexity to instrument",
|
|
31534
|
+
"refactor complexity",
|
|
31535
|
+
"complexity"
|
|
31536
|
+
]),
|
|
31537
|
+
methodsToCapture: firstFieldValue(values, [
|
|
31538
|
+
"suggested methods to capture",
|
|
31539
|
+
"suggested methods to captured",
|
|
31540
|
+
"methods to capture",
|
|
31541
|
+
"methods captured"
|
|
31542
|
+
]),
|
|
31543
|
+
methodsToMock: firstFieldValue(values, [
|
|
31544
|
+
"methods to mock on replay",
|
|
31545
|
+
"number of methods that need to mocked",
|
|
31546
|
+
"number of methods that need to be mocked",
|
|
31547
|
+
"methods that need to be mocked",
|
|
31548
|
+
"methods to mock",
|
|
31549
|
+
"mocked methods"
|
|
31550
|
+
]),
|
|
31551
|
+
realDataImprovement: firstFieldValue(values, [
|
|
31552
|
+
"how real data improves this",
|
|
31553
|
+
"value of running real data through it",
|
|
31554
|
+
"real data",
|
|
31555
|
+
"feature would improve",
|
|
31556
|
+
"improvement",
|
|
31557
|
+
"why",
|
|
31558
|
+
"worth tracing",
|
|
31559
|
+
"rationale"
|
|
31560
|
+
])
|
|
31561
|
+
}),
|
|
31562
|
+
formatTrailingReportBlock(lines.slice(tableEnd), style)
|
|
31563
|
+
].filter((part) => part !== "").join("\n\n")
|
|
31564
|
+
);
|
|
31565
|
+
}
|
|
31566
|
+
function firstFieldValue(values, fields) {
|
|
31567
|
+
for (const field of fields) {
|
|
31568
|
+
const value = values.get(field);
|
|
31569
|
+
if (value !== void 0) {
|
|
31570
|
+
return value;
|
|
31571
|
+
}
|
|
31572
|
+
}
|
|
31573
|
+
return "";
|
|
31574
|
+
}
|
|
31575
|
+
function createAnalyzeRepoReportStyle(options) {
|
|
31576
|
+
const enabled = options.color ?? (process.stdout.isTTY === true && process.env.NO_COLOR === void 0 && process.env.TERM !== "dumb");
|
|
31577
|
+
const wrap = (code, text) => enabled ? `${code}${text}${ANSI.reset}` : text;
|
|
31578
|
+
return {
|
|
31579
|
+
enabled,
|
|
31580
|
+
bold: (text) => wrap(ANSI.bold, text),
|
|
31581
|
+
dim: (text) => wrap(ANSI.dim, text),
|
|
31582
|
+
green: (text) => wrap(ANSI.green, text),
|
|
31583
|
+
yellow: (text) => wrap(ANSI.yellow, text),
|
|
31584
|
+
cyan: (text) => wrap(ANSI.cyan, text),
|
|
31585
|
+
magenta: (text) => wrap(ANSI.magenta, text),
|
|
31586
|
+
red: (text) => wrap(ANSI.red, text),
|
|
31587
|
+
width: normalizeReportWidth(
|
|
31588
|
+
options.width ?? (process.stdout.isTTY === true ? process.stdout.columns : void 0)
|
|
31589
|
+
)
|
|
31590
|
+
};
|
|
31591
|
+
}
|
|
31592
|
+
function formatLabeledLine(style, label, value) {
|
|
31593
|
+
const prefix = ` - ${style.dim(`${label}:`)} `;
|
|
31594
|
+
const continuationPrefix = " ";
|
|
31595
|
+
return wrapStyledLine(
|
|
31596
|
+
`${prefix}${styleInlineValue(value, style)}`,
|
|
31597
|
+
prefixVisibleLength(label),
|
|
31598
|
+
continuationPrefix,
|
|
31599
|
+
style.width
|
|
31600
|
+
);
|
|
31601
|
+
}
|
|
31602
|
+
function prefixVisibleLength(label) {
|
|
31603
|
+
return ` - ${label}: `.length;
|
|
31604
|
+
}
|
|
31605
|
+
function normalizeReportWidth(width) {
|
|
31606
|
+
if (width === void 0 || !Number.isFinite(width) || width <= 0) {
|
|
31607
|
+
return 1e4;
|
|
31608
|
+
}
|
|
31609
|
+
return Math.max(48, Math.min(110, Math.floor(width)));
|
|
31610
|
+
}
|
|
31611
|
+
function wrapStyledLine(line, firstPrefixLength, continuationPrefix, width) {
|
|
31612
|
+
if (visibleLength(line) <= width) {
|
|
31613
|
+
return line;
|
|
31614
|
+
}
|
|
31615
|
+
const segments = ansiSegments(line);
|
|
31616
|
+
const out = [];
|
|
31617
|
+
let current = "";
|
|
31618
|
+
let currentVisible = 0;
|
|
31619
|
+
let limit = width;
|
|
31620
|
+
for (const segment of segments) {
|
|
31621
|
+
if (segment.visible === 0) {
|
|
31622
|
+
current += segment.text;
|
|
31623
|
+
continue;
|
|
31624
|
+
}
|
|
31625
|
+
const words = segment.text.split(/(\s+)/);
|
|
31626
|
+
for (const word of words) {
|
|
31627
|
+
if (word === "") {
|
|
31628
|
+
continue;
|
|
31629
|
+
}
|
|
31630
|
+
const wordVisible = visibleLength(word);
|
|
31631
|
+
const isSpace = /^\s+$/.test(word);
|
|
31632
|
+
if (currentVisible > firstPrefixLength && currentVisible + wordVisible > limit && !isSpace) {
|
|
31633
|
+
out.push(current.trimEnd());
|
|
31634
|
+
current = continuationPrefix;
|
|
31635
|
+
currentVisible = continuationPrefix.length;
|
|
31636
|
+
limit = width;
|
|
31637
|
+
}
|
|
31638
|
+
if (!(currentVisible === continuationPrefix.length && isSpace)) {
|
|
31639
|
+
current += word;
|
|
31640
|
+
currentVisible += wordVisible;
|
|
31641
|
+
}
|
|
31642
|
+
}
|
|
31643
|
+
}
|
|
31644
|
+
if (current.trim() !== "") {
|
|
31645
|
+
out.push(current.trimEnd());
|
|
31646
|
+
}
|
|
31647
|
+
return out.join("\n");
|
|
31648
|
+
}
|
|
31649
|
+
function visibleLength(text) {
|
|
31650
|
+
return ansiSegments(text).reduce((sum, segment) => sum + segment.visible, 0);
|
|
31651
|
+
}
|
|
31652
|
+
function ansiSegments(text) {
|
|
31653
|
+
const segments = [];
|
|
31654
|
+
let index = 0;
|
|
31655
|
+
while (index < text.length) {
|
|
31656
|
+
const escapeIndex = text.indexOf("\x1B[", index);
|
|
31657
|
+
if (escapeIndex === -1) {
|
|
31658
|
+
const plain = text.slice(index);
|
|
31659
|
+
segments.push({ text: plain, visible: plain.length });
|
|
31660
|
+
break;
|
|
31661
|
+
}
|
|
31662
|
+
if (escapeIndex > index) {
|
|
31663
|
+
const plain = text.slice(index, escapeIndex);
|
|
31664
|
+
segments.push({ text: plain, visible: plain.length });
|
|
31665
|
+
}
|
|
31666
|
+
const endIndex = text.indexOf("m", escapeIndex);
|
|
31667
|
+
if (endIndex === -1) {
|
|
31668
|
+
const plain = text.slice(escapeIndex);
|
|
31669
|
+
segments.push({ text: plain, visible: plain.length });
|
|
31670
|
+
break;
|
|
31671
|
+
}
|
|
31672
|
+
segments.push({ text: text.slice(escapeIndex, endIndex + 1), visible: 0 });
|
|
31673
|
+
index = endIndex + 1;
|
|
31674
|
+
}
|
|
31675
|
+
return segments;
|
|
31676
|
+
}
|
|
31677
|
+
function usefulAnalyzeRepoFollowUp(lines) {
|
|
31678
|
+
const useful = [];
|
|
31679
|
+
let includeSkipped = false;
|
|
31680
|
+
for (const rawLine of lines) {
|
|
31681
|
+
const line = rawLine.trimEnd();
|
|
31682
|
+
const normalized = normalizeMarkdownText(line);
|
|
31683
|
+
if (line.trim() === "") {
|
|
31684
|
+
if (useful.length > 0 && useful[useful.length - 1] !== "") {
|
|
31685
|
+
useful.push("");
|
|
31686
|
+
}
|
|
31687
|
+
continue;
|
|
31688
|
+
}
|
|
31689
|
+
if (normalized === "skipped") {
|
|
31690
|
+
useful.push(line);
|
|
31691
|
+
includeSkipped = true;
|
|
31692
|
+
continue;
|
|
31693
|
+
}
|
|
31694
|
+
if (includeSkipped && line.trim().startsWith("- ")) {
|
|
31695
|
+
useful.push(line);
|
|
31696
|
+
continue;
|
|
31697
|
+
}
|
|
31698
|
+
includeSkipped = false;
|
|
31699
|
+
}
|
|
31700
|
+
while (useful[useful.length - 1] === "") {
|
|
31701
|
+
useful.pop();
|
|
31702
|
+
}
|
|
31703
|
+
return useful.length === 0 ? [] : ["", ...useful];
|
|
31704
|
+
}
|
|
31705
|
+
function formatTrailingReportBlock(lines, style) {
|
|
31706
|
+
return styleReportBlock(
|
|
31707
|
+
usefulAnalyzeRepoFollowUp(lines).join("\n").trim(),
|
|
31708
|
+
style
|
|
31709
|
+
);
|
|
31710
|
+
}
|
|
31711
|
+
function instrumentationComplexityLabel(style, raw) {
|
|
31712
|
+
const normalized = raw.trim().toLowerCase();
|
|
31713
|
+
if (normalized === "none" || normalized.startsWith("none ")) {
|
|
31714
|
+
return `${style.green("no refactor")}${raw.trim().slice(4)}`;
|
|
31715
|
+
}
|
|
31716
|
+
if (normalized === "low" || normalized.startsWith("low ")) {
|
|
31717
|
+
return `${style.green("low effort")}${raw.trim().slice(3)}`;
|
|
31718
|
+
}
|
|
31719
|
+
if (normalized === "med" || normalized === "medium") {
|
|
31720
|
+
return style.yellow("medium effort");
|
|
31721
|
+
}
|
|
31722
|
+
if (normalized.startsWith("med ") || normalized.startsWith("medium ")) {
|
|
31723
|
+
return `${style.yellow("medium effort")}${raw.trim().replace(/^medium|^med/i, "")}`;
|
|
31724
|
+
}
|
|
31725
|
+
if (normalized === "high" || normalized.startsWith("high ")) {
|
|
31726
|
+
return `${style.red("high effort")}${raw.trim().slice(4)}`;
|
|
31727
|
+
}
|
|
31728
|
+
return raw;
|
|
31729
|
+
}
|
|
31730
|
+
function styleReportBlock(block, style) {
|
|
31731
|
+
if (block === "") {
|
|
31732
|
+
return "";
|
|
31733
|
+
}
|
|
31734
|
+
return block.split(/\r?\n/).map((line) => styleReportLine(line, style)).join("\n");
|
|
31735
|
+
}
|
|
31736
|
+
function styleNonTableReport(report, style) {
|
|
31737
|
+
return report.split(/\r?\n/).map((line) => styleReportLine(line, style)).join("\n");
|
|
31738
|
+
}
|
|
31739
|
+
function styleReportLine(line, style) {
|
|
31740
|
+
const heading = uploadedHeadingFromLine(line);
|
|
31741
|
+
if (heading !== null) {
|
|
31742
|
+
return style.bold(style.green(heading));
|
|
31743
|
+
}
|
|
31744
|
+
if (line.trim() === "Skipped") {
|
|
31745
|
+
return style.bold(style.yellow(line));
|
|
31746
|
+
}
|
|
31747
|
+
if (line.startsWith("- ")) {
|
|
31748
|
+
return style.yellow(styleInlineValue(line, style));
|
|
31749
|
+
}
|
|
31750
|
+
if (line.startsWith("These are draft")) {
|
|
31751
|
+
return style.dim(styleInlineValue(line, style));
|
|
31752
|
+
}
|
|
31753
|
+
return styleInlineValue(line, style);
|
|
31754
|
+
}
|
|
31755
|
+
function uploadedHeadingFromLine(line) {
|
|
31756
|
+
const normalized = line.replace(/[`*_]/g, "").trim();
|
|
31757
|
+
const match = normalized.match(/Uploaded \d+ draft trace plans?/i);
|
|
31758
|
+
return match?.[0] ?? null;
|
|
31759
|
+
}
|
|
31760
|
+
function hideTracePlanLinks(report) {
|
|
31761
|
+
return report.split(/\r?\n/).map(
|
|
31762
|
+
(line) => line.replace(
|
|
31763
|
+
/\[([^\]]+)\]\(https:\/\/bitfab\.ai\/studio\/trace-plan\/[^\s)]+\)/g,
|
|
31764
|
+
"$1"
|
|
31765
|
+
).replace(/https:\/\/bitfab\.ai\/studio\/trace-plan\/\S+/g, "").trimEnd()
|
|
31766
|
+
).filter((line) => !/^\s*(plan|plan url):\s*$/i.test(line)).join("\n").trim();
|
|
31767
|
+
}
|
|
31768
|
+
function findColumnIndex(headers, needles) {
|
|
31769
|
+
return headers.findIndex((header) => {
|
|
31770
|
+
const normalized = normalizeMarkdownText(header);
|
|
31771
|
+
return needles.some((needle) => normalized.includes(needle));
|
|
31772
|
+
});
|
|
31773
|
+
}
|
|
31774
|
+
function normalizeMarkdownText(text) {
|
|
31775
|
+
return text.replace(/[`*_]/g, "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
31776
|
+
}
|
|
31777
|
+
function isMarkdownTableRow(line) {
|
|
31778
|
+
const trimmed = line.trim();
|
|
31779
|
+
return trimmed.startsWith("|") && trimmed.endsWith("|");
|
|
31780
|
+
}
|
|
31781
|
+
function isTableDivider(line) {
|
|
31782
|
+
const cells = splitMarkdownTableRow(line);
|
|
31783
|
+
return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell.replace(/\s/g, "")));
|
|
31784
|
+
}
|
|
31785
|
+
function splitMarkdownTableRow(line) {
|
|
31786
|
+
return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
|
|
31787
|
+
}
|
|
31788
|
+
function assistantTextFromEvent(event) {
|
|
31789
|
+
const content = eventMessageContent(event);
|
|
31790
|
+
if (!content) {
|
|
31791
|
+
return null;
|
|
31792
|
+
}
|
|
31793
|
+
const blocks = [];
|
|
31794
|
+
for (const block of content) {
|
|
31795
|
+
if (typeof block !== "object" || block === null) {
|
|
31796
|
+
continue;
|
|
31797
|
+
}
|
|
31798
|
+
const b = block;
|
|
31799
|
+
if (b.type === "text" && typeof b.text === "string") {
|
|
31800
|
+
blocks.push(b.text);
|
|
31801
|
+
}
|
|
31802
|
+
}
|
|
31803
|
+
const text = blocks.join("\n").trim();
|
|
31804
|
+
return text === "" ? null : text;
|
|
31805
|
+
}
|
|
31806
|
+
function textCandidatesFromEvent(event) {
|
|
31807
|
+
const candidates = [];
|
|
31808
|
+
const assistantText = assistantTextFromEvent(event);
|
|
31809
|
+
if (assistantText !== null) {
|
|
31810
|
+
candidates.push(assistantText);
|
|
31811
|
+
}
|
|
31812
|
+
for (const text of stringValuesFromUnknown(event)) {
|
|
31813
|
+
const trimmed = text.trim();
|
|
31814
|
+
if (trimmed !== "" && !candidates.includes(trimmed) && !candidates.some((candidate) => candidate.includes(trimmed))) {
|
|
31815
|
+
candidates.push(trimmed);
|
|
31816
|
+
}
|
|
31817
|
+
}
|
|
31818
|
+
return candidates;
|
|
31819
|
+
}
|
|
31820
|
+
function stringValuesFromUnknown(value) {
|
|
31821
|
+
if (typeof value === "string") {
|
|
31822
|
+
return [value];
|
|
31823
|
+
}
|
|
31824
|
+
if (Array.isArray(value)) {
|
|
31825
|
+
return value.flatMap(stringValuesFromUnknown);
|
|
31826
|
+
}
|
|
31827
|
+
if (typeof value !== "object" || value === null) {
|
|
31828
|
+
return [];
|
|
31829
|
+
}
|
|
31830
|
+
return Object.values(value).flatMap(
|
|
31831
|
+
stringValuesFromUnknown
|
|
31832
|
+
);
|
|
31833
|
+
}
|
|
31834
|
+
function resultTextFromEvent(event) {
|
|
31835
|
+
if (typeof event !== "object" || event === null) {
|
|
31836
|
+
return null;
|
|
31837
|
+
}
|
|
31838
|
+
const e = event;
|
|
31839
|
+
if (e.type !== "result" || typeof e.result !== "string") {
|
|
31840
|
+
return null;
|
|
31841
|
+
}
|
|
31842
|
+
const text = e.result.trim();
|
|
31843
|
+
return text === "" ? null : text;
|
|
31844
|
+
}
|
|
31156
31845
|
function latestActivityLabel(chunk2) {
|
|
31157
31846
|
const lines = chunk2.split("\n").filter((l) => l.trim() !== "");
|
|
31158
31847
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
@@ -31463,10 +32152,18 @@ async function runCodexAnalyzeRepo(captureOverride, limit, prompt) {
|
|
|
31463
32152
|
`analyze-repo exited with status ${exitCode}. See ${logPath}`
|
|
31464
32153
|
);
|
|
31465
32154
|
}
|
|
31466
|
-
const
|
|
32155
|
+
const logText = stdoutChunks.join("");
|
|
32156
|
+
const uploaded = countUploadedTracePlans2(logText);
|
|
31467
32157
|
spinner6.stop(
|
|
31468
32158
|
uploaded === 0 ? "No AI workflows found - nothing to upload" : `Uploaded ${uploaded} draft trace plan${uploaded === 1 ? "" : "s"} to Bitfab`
|
|
31469
32159
|
);
|
|
32160
|
+
const report = extractAnalyzeRepoReport(logText);
|
|
32161
|
+
if (report !== null) {
|
|
32162
|
+
process.stdout.write(`
|
|
32163
|
+
${formatAnalyzeRepoReport(report)}
|
|
32164
|
+
|
|
32165
|
+
`);
|
|
32166
|
+
}
|
|
31470
32167
|
} catch (err) {
|
|
31471
32168
|
spinner6.stop("analyze-repo failed");
|
|
31472
32169
|
throw err;
|
|
@@ -31834,10 +32531,18 @@ async function runCursorAnalyzeRepo(captureOverride, limit, prompt) {
|
|
|
31834
32531
|
`analyze-repo exited with status ${exitCode}. See ${logPath}`
|
|
31835
32532
|
);
|
|
31836
32533
|
}
|
|
31837
|
-
const
|
|
32534
|
+
const logText = stdoutChunks.join("");
|
|
32535
|
+
const uploaded = countUploadedTracePlans3(logText);
|
|
31838
32536
|
spinner6.stop(
|
|
31839
32537
|
uploaded === 0 ? "No AI workflows found - nothing to upload" : `Uploaded ${uploaded} draft trace plan${uploaded === 1 ? "" : "s"} to Bitfab`
|
|
31840
32538
|
);
|
|
32539
|
+
const report = extractAnalyzeRepoReport(logText);
|
|
32540
|
+
if (report !== null) {
|
|
32541
|
+
process.stdout.write(`
|
|
32542
|
+
${formatAnalyzeRepoReport(report)}
|
|
32543
|
+
|
|
32544
|
+
`);
|
|
32545
|
+
}
|
|
31841
32546
|
} catch (err) {
|
|
31842
32547
|
spinner6.stop("analyze-repo failed");
|
|
31843
32548
|
throw err;
|
|
@@ -32382,7 +33087,7 @@ var GLOBAL_HELP_TEXT = `Usage: bitfab <command> [options]
|
|
|
32382
33087
|
Commands:
|
|
32383
33088
|
init [--editor <name>] Full onboarding: plugin-install, login, and setup
|
|
32384
33089
|
plugin-install [--editor <name>] Install the Bitfab plugin in one editor
|
|
32385
|
-
login
|
|
33090
|
+
login [--force] Authenticate with Bitfab (opens browser)
|
|
32386
33091
|
logout Remove stored credentials
|
|
32387
33092
|
session-logs [status|enable|disable] Read or update session log collection
|
|
32388
33093
|
setup [--editor <name>] Launch /bitfab:setup in the editor
|
|
@@ -32399,6 +33104,7 @@ Options:
|
|
|
32399
33104
|
--no-upload-logs analyze-repo: keep session logs local (skips the prompt)
|
|
32400
33105
|
--limit <n> analyze-repo: cap how many draft trace plans to upload (default 5)
|
|
32401
33106
|
--prompt, -p <text> analyze-repo: free-text guidance steering what to focus on
|
|
33107
|
+
--force login: re-authenticate and clear any stale Studio session first
|
|
32402
33108
|
|
|
32403
33109
|
Examples:
|
|
32404
33110
|
bitfab init Full setup (detect editor, install, login, setup)
|
|
@@ -32406,6 +33112,7 @@ Examples:
|
|
|
32406
33112
|
bitfab analyze-repo Scan the repo and upload draft trace plans (headless)
|
|
32407
33113
|
bitfab analyze-repo --limit 3 Scan the repo and upload at most 3 draft trace plans
|
|
32408
33114
|
bitfab analyze-repo "focus on billing" Scan with free-text guidance on what to prioritize
|
|
33115
|
+
bitfab login --force Re-authenticate and clear a stale Studio session
|
|
32409
33116
|
bitfab session-logs enable Enable session log collection
|
|
32410
33117
|
bitfab session-logs disable Disable session log collection
|
|
32411
33118
|
bitfab assistant investigate Investigate traces
|
|
@@ -32438,15 +33145,16 @@ Examples:
|
|
|
32438
33145
|
bitfab plugin-install
|
|
32439
33146
|
bitfab plugin-install --editor cursor
|
|
32440
33147
|
`,
|
|
32441
|
-
login: `Usage: bitfab login
|
|
33148
|
+
login: `Usage: bitfab login [--force]
|
|
32442
33149
|
|
|
32443
33150
|
Authenticate with Bitfab in the browser and save credentials locally.
|
|
32444
33151
|
|
|
32445
33152
|
Options:
|
|
32446
|
-
|
|
33153
|
+
--force Re-authenticate even when already signed in, and clear any stale Studio session first
|
|
32447
33154
|
|
|
32448
33155
|
Examples:
|
|
32449
33156
|
bitfab login
|
|
33157
|
+
bitfab login --force
|
|
32450
33158
|
`,
|
|
32451
33159
|
logout: `Usage: bitfab logout
|
|
32452
33160
|
|
|
@@ -32608,7 +33316,7 @@ function wantsHelp(argv) {
|
|
|
32608
33316
|
var COMMAND_SPECS = {
|
|
32609
33317
|
init: { flags: ["editor", "skipPermissions"], unknownOption: "error" },
|
|
32610
33318
|
"plugin-install": { flags: ["editor"], unknownOption: "error" },
|
|
32611
|
-
login: { flags: [], unknownOption: "error" },
|
|
33319
|
+
login: { flags: ["force"], unknownOption: "error" },
|
|
32612
33320
|
logout: { flags: [], unknownOption: "error" },
|
|
32613
33321
|
"session-logs": { flags: [], unknownOption: "error" },
|
|
32614
33322
|
setup: { flags: ["editor", "skipPermissions"], unknownOption: "error" },
|
|
@@ -32682,6 +33390,12 @@ function applyFlag(key, token, next, values) {
|
|
|
32682
33390
|
return 1;
|
|
32683
33391
|
}
|
|
32684
33392
|
return null;
|
|
33393
|
+
case "force":
|
|
33394
|
+
if (token === "--force") {
|
|
33395
|
+
values.force = true;
|
|
33396
|
+
return 1;
|
|
33397
|
+
}
|
|
33398
|
+
return null;
|
|
32685
33399
|
}
|
|
32686
33400
|
}
|
|
32687
33401
|
function parseArgs2(argv) {
|
|
@@ -32728,6 +33442,7 @@ function parseArgs2(argv) {
|
|
|
32728
33442
|
uploadLogs: values.uploadLogs,
|
|
32729
33443
|
limit: values.limit,
|
|
32730
33444
|
prompt: values.prompt,
|
|
33445
|
+
force: values.force,
|
|
32731
33446
|
rest
|
|
32732
33447
|
};
|
|
32733
33448
|
}
|
|
@@ -32747,7 +33462,16 @@ ${GLOBAL_HELP_TEXT}`
|
|
|
32747
33462
|
process.stdout.write(helpText);
|
|
32748
33463
|
return;
|
|
32749
33464
|
}
|
|
32750
|
-
const {
|
|
33465
|
+
const {
|
|
33466
|
+
command,
|
|
33467
|
+
editor,
|
|
33468
|
+
skipPermissions,
|
|
33469
|
+
uploadLogs,
|
|
33470
|
+
limit,
|
|
33471
|
+
prompt,
|
|
33472
|
+
force,
|
|
33473
|
+
rest
|
|
33474
|
+
} = parseArgs2(argv);
|
|
32751
33475
|
const abortUpdateCheck = startUpdateCheck();
|
|
32752
33476
|
if (command === "init") {
|
|
32753
33477
|
await runInit({ editor, skipPermissions });
|
|
@@ -32760,7 +33484,7 @@ ${GLOBAL_HELP_TEXT}`
|
|
|
32760
33484
|
return;
|
|
32761
33485
|
}
|
|
32762
33486
|
if (command === "login") {
|
|
32763
|
-
await runLoginCommand();
|
|
33487
|
+
await runLoginCommand({ force });
|
|
32764
33488
|
abortUpdateCheck();
|
|
32765
33489
|
return;
|
|
32766
33490
|
}
|