@bobsworkshop/cli 0.1.2 → 0.1.4

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/bob.js ADDED
@@ -0,0 +1,3006 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildLocalContext,
4
+ callCloudFunction,
5
+ callLocalModel,
6
+ extractProposedFile,
7
+ getConfig,
8
+ getConfigPath,
9
+ loadLocalSuggestions,
10
+ markSuggestionStatus,
11
+ proposeAndWriteFile,
12
+ readFileContent,
13
+ registerLoginCommand,
14
+ setConfigValue,
15
+ stripCodeBlockFromResponse
16
+ } from "./chunk-LHWBSCJ4.js";
17
+
18
+ // bin/bob.ts
19
+ import { Command } from "commander";
20
+ import chalk17 from "chalk";
21
+ import * as path9 from "path";
22
+
23
+ // src/commands/config.ts
24
+ import chalk from "chalk";
25
+ var VALID_KEYS = [
26
+ "provider",
27
+ "providerKey",
28
+ "localEndpoint",
29
+ "tier",
30
+ "idrp",
31
+ "idrpFilter",
32
+ "activeProject",
33
+ "activePersona",
34
+ "hasSeenWelcome",
35
+ "autoMode"
36
+ ];
37
+ var VALID_PROVIDERS = ["claude", "gemini", "openai", "grok", "local"];
38
+ function registerConfigCommand(program2) {
39
+ const configCmd = program2.command("config").description("View or update Bob CLI configuration");
40
+ configCmd.command("show").description("Display current configuration").action(() => {
41
+ const config = getConfig();
42
+ console.log("");
43
+ console.log(chalk.bold(" \u2699\uFE0F Bob CLI Configuration"));
44
+ console.log(chalk.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
45
+ console.log(` ${chalk.cyan("Tier:")} ${config.tier}`);
46
+ console.log(` ${chalk.cyan("Logged In:")} ${config.loggedIn}`);
47
+ console.log(` ${chalk.cyan("Email:")} ${config.email || "None"}`);
48
+ console.log(` ${chalk.cyan("Provider:")} ${config.provider || "Not set"}`);
49
+ console.log(` ${chalk.cyan("Provider Key:")} ${config.providerKey ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : "Not set"}`);
50
+ console.log(` ${chalk.cyan("Local Endpoint:")} ${config.localEndpoint || "Not set"}`);
51
+ console.log(` ${chalk.cyan("IDRP:")} ${config.idrp ? "Enabled" : "Disabled"}`);
52
+ console.log(` ${chalk.cyan("IDRP Filter:")} ${config.idrpFilter}`);
53
+ console.log(` ${chalk.cyan("Auto Mode:")} ${config.autoMode ? "Enabled" : "Disabled"}`);
54
+ console.log(` ${chalk.cyan("Active Project:")} ${config.activeProject || "None"}`);
55
+ console.log(` ${chalk.cyan("Active Persona:")} ${config.activePersona || "None"}`);
56
+ console.log(` ${chalk.cyan("Has Seen Welcome:")} ${config.hasSeenWelcome}`);
57
+ console.log("");
58
+ console.log(chalk.gray(` Config file: ${getConfigPath()}`));
59
+ console.log("");
60
+ });
61
+ configCmd.command("set <key> <value>").description("Set a configuration value").action((key, value) => {
62
+ if (!VALID_KEYS.includes(key)) {
63
+ console.log("");
64
+ console.log(chalk.red(` \u274C Invalid key: "${key}"`));
65
+ console.log(chalk.gray(` Valid keys: ${VALID_KEYS.join(", ")}`));
66
+ console.log("");
67
+ return;
68
+ }
69
+ if (key === "provider" && !VALID_PROVIDERS.includes(value)) {
70
+ console.log("");
71
+ console.log(chalk.red(` \u274C Invalid provider: "${value}"`));
72
+ console.log(chalk.gray(` Valid providers: ${VALID_PROVIDERS.join(", ")}`));
73
+ console.log("");
74
+ return;
75
+ }
76
+ let finalValue = value;
77
+ if (key === "idrp") {
78
+ finalValue = value === "true" || value === "enabled" || value === "on";
79
+ }
80
+ if (key === "hasSeenWelcome") {
81
+ finalValue = value === "true";
82
+ }
83
+ if (key === "autoMode") {
84
+ finalValue = value === "true" || value === "enabled" || value === "on";
85
+ }
86
+ if (key === "tier") {
87
+ if (value !== "local" && value !== "platform") {
88
+ console.log("");
89
+ console.log(chalk.red(` \u274C Invalid tier: "${value}"`));
90
+ console.log(chalk.gray(` Valid tiers: local, platform`));
91
+ console.log("");
92
+ return;
93
+ }
94
+ }
95
+ setConfigValue(key, finalValue);
96
+ console.log("");
97
+ console.log(chalk.green(` \u2705 ${key} \u2192 ${key === "providerKey" ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : finalValue}`));
98
+ console.log("");
99
+ });
100
+ configCmd.command("path").description("Show the config file location").action(() => {
101
+ console.log("");
102
+ console.log(chalk.cyan(` \u{1F4C1} ${getConfigPath()}`));
103
+ console.log("");
104
+ });
105
+ }
106
+
107
+ // src/commands/chat.ts
108
+ import chalk7 from "chalk";
109
+ import ora2 from "ora";
110
+ import * as fs4 from "fs";
111
+ import * as path5 from "path";
112
+ import * as readline from "readline";
113
+
114
+ // src/ai/persona.ts
115
+ var STANDARD_STYLE_PROMPT = `You are Bob: friendly, direct, senior-level engineering partner.
116
+ CONVERSATIONAL + BREVITY RULES (strict):
117
+ - Warm + concise.
118
+ - If code is appropriate, lead with code.
119
+ - Preface: at most 20 short sentence(s) (<= 500 words).
120
+ - After code: up to 5 bullets (<= 100 words).
121
+ - One fenced block only.
122
+ - Expand only if asked to "explain" or "why" next turn.
123
+
124
+ FILE OUTPUT RULES (strict):
125
+ - When you generate or modify a file, ALWAYS start the code block with a comment on the first line indicating the FULL file path from the project root.
126
+ - Format: // File: <relative-path-from-project-root>
127
+ - Examples: // File: src/core/auth.ts or // File: lib/services/api_service.dart
128
+ - This applies to NEW files and EDITED files.
129
+ - If you are showing a code snippet that is NOT a full file, do NOT include the file path comment.
130
+ - When editing an existing file, output the COMPLETE updated file contents, not just the changed section.
131
+ - When editing an existing file, PRESERVE the existing code structure, imports, naming conventions, and patterns.
132
+ - Do NOT rewrite the file from scratch unless the user explicitly asks for a full rewrite.
133
+ - ADD your changes surgically into the existing code \u2014 keep everything else intact.
134
+ - If you believe a structural change would be significantly better, ASK the user first before implementing it. Do not assume permission to refactor.`;
135
+ var CONSULTANT_STYLE_PROMPT = `You are Bob in "Consultant Mode": a friendly, direct, senior-level engineering partner.
136
+ CONSULTANT MODE RULES (VERY STRICT):
137
+ - Your ONLY goal is to provide strategic advice, conceptual guidance, and high-level architectural ideas.
138
+ - DO NOT, under any circumstances, generate code.
139
+ - Focus entirely on the conceptual and strategic aspects of the user's query.
140
+ - Be warm, concise, and direct in your advice.`;
141
+
142
+ // src/ui/renderer.ts
143
+ import chalk2 from "chalk";
144
+ function renderMarkdown(text) {
145
+ return text.replace(/^#{1,6}\s+(.+)$/gm, chalk2.bold.cyan("$1")).replace(/\*\*(.+?)\*\*/g, chalk2.bold("$1")).replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, chalk2.italic("$1")).replace(/^\s*[\*\-]\s+/gm, " \u2022 ").replace(/^\s*(\d+)\.\s+/gm, " $1. ").replace(/^[\-\*]{3,}$/gm, chalk2.gray("\u2500".repeat(60))).replace(/`([^`]+)`/g, chalk2.yellow("$1")).replace(/```[\w]*\n?/g, "").replace(/\n{3,}/g, "\n\n");
146
+ }
147
+
148
+ // src/core/conversation-store.ts
149
+ import * as fs2 from "fs";
150
+ import * as path2 from "path";
151
+
152
+ // src/core/project-map.ts
153
+ import * as fs from "fs";
154
+ import * as path from "path";
155
+ import * as os from "os";
156
+ var BOB_DIR = path.join(os.homedir(), ".bob");
157
+ var PROJECTS_DIR = path.join(BOB_DIR, "projects");
158
+ function getProjectName(workingDir) {
159
+ return path.basename(workingDir);
160
+ }
161
+ function getProjectDir(workingDir) {
162
+ const name = getProjectName(workingDir);
163
+ return path.join(PROJECTS_DIR, name);
164
+ }
165
+ function ensureProjectStructure(workingDir) {
166
+ const projectDir = getProjectDir(workingDir);
167
+ const conversationsDir = path.join(projectDir, "conversations");
168
+ const analysisDir = path.join(projectDir, "analysis");
169
+ const runsDir = path.join(analysisDir, "runs");
170
+ for (const dir of [BOB_DIR, PROJECTS_DIR, projectDir, conversationsDir, analysisDir, runsDir]) {
171
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
172
+ }
173
+ const metaPath = path.join(projectDir, "project.json");
174
+ if (!fs.existsSync(metaPath)) {
175
+ const meta = {
176
+ name: getProjectName(workingDir),
177
+ path: workingDir,
178
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
179
+ lastIndexed: null
180
+ };
181
+ fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
182
+ }
183
+ return { projectDir, conversationsDir, analysisDir, runsDir };
184
+ }
185
+ function createAnalysisRun(workingDir, files) {
186
+ const { runsDir } = ensureProjectStructure(workingDir);
187
+ const runId = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
188
+ const runDir = path.join(runsDir, runId);
189
+ const tasksDir = path.join(runDir, "tasks");
190
+ fs.mkdirSync(runDir, { recursive: true });
191
+ fs.mkdirSync(tasksDir, { recursive: true });
192
+ const manifest = {
193
+ runId,
194
+ status: "in_progress",
195
+ totalFiles: files.length,
196
+ completedFiles: 0,
197
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
198
+ projectPath: workingDir
199
+ };
200
+ fs.writeFileSync(path.join(runDir, "manifest.json"), JSON.stringify(manifest, null, 2));
201
+ for (const filePath of files) {
202
+ const taskId = filePath.replace(/[\/\\]/g, "_");
203
+ const task = {
204
+ filePath,
205
+ status: false,
206
+ summary: null,
207
+ dependencies: [],
208
+ error: null
209
+ };
210
+ fs.writeFileSync(path.join(tasksDir, `${taskId}.json`), JSON.stringify(task, null, 2));
211
+ }
212
+ return { runId, runDir, tasksDir };
213
+ }
214
+ function completeTask(tasksDir, filePath, summary) {
215
+ const taskId = filePath.replace(/[\/\\]/g, "_");
216
+ const taskPath = path.join(tasksDir, `${taskId}.json`);
217
+ if (fs.existsSync(taskPath)) {
218
+ const task = JSON.parse(fs.readFileSync(taskPath, "utf-8"));
219
+ task.status = true;
220
+ task.summary = summary;
221
+ fs.writeFileSync(taskPath, JSON.stringify(task, null, 2));
222
+ }
223
+ }
224
+ function updateManifestProgress(runDir, completedFiles, status) {
225
+ const manifestPath = path.join(runDir, "manifest.json");
226
+ if (fs.existsSync(manifestPath)) {
227
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
228
+ manifest.completedFiles = completedFiles;
229
+ if (status) manifest.status = status;
230
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
231
+ }
232
+ }
233
+ function saveSummaries(workingDir, summaries) {
234
+ const { analysisDir } = ensureProjectStructure(workingDir);
235
+ fs.writeFileSync(path.join(analysisDir, "summaries.json"), JSON.stringify(summaries, null, 2));
236
+ const projectDir = getProjectDir(workingDir);
237
+ const metaPath = path.join(projectDir, "project.json");
238
+ if (fs.existsSync(metaPath)) {
239
+ const meta = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
240
+ meta.lastIndexed = (/* @__PURE__ */ new Date()).toISOString();
241
+ fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
242
+ }
243
+ }
244
+ function saveDependencies(workingDir, dependencies) {
245
+ const { analysisDir } = ensureProjectStructure(workingDir);
246
+ fs.writeFileSync(path.join(analysisDir, "dependencies.json"), JSON.stringify(dependencies, null, 2));
247
+ }
248
+ function loadSummaries(workingDir) {
249
+ const { analysisDir } = ensureProjectStructure(workingDir);
250
+ const summariesPath = path.join(analysisDir, "summaries.json");
251
+ if (!fs.existsSync(summariesPath)) return null;
252
+ try {
253
+ return JSON.parse(fs.readFileSync(summariesPath, "utf-8"));
254
+ } catch {
255
+ return null;
256
+ }
257
+ }
258
+ function loadDependencies(workingDir) {
259
+ const { analysisDir } = ensureProjectStructure(workingDir);
260
+ const depsPath = path.join(analysisDir, "dependencies.json");
261
+ if (!fs.existsSync(depsPath)) return null;
262
+ try {
263
+ return JSON.parse(fs.readFileSync(depsPath, "utf-8"));
264
+ } catch {
265
+ return null;
266
+ }
267
+ }
268
+
269
+ // src/core/conversation-store.ts
270
+ function saveMessage(conversationId, message, meta) {
271
+ const { conversationsDir } = ensureProjectStructure(process.cwd());
272
+ const convoDir = path2.join(conversationsDir, conversationId);
273
+ const messagesDir = path2.join(convoDir, "messages");
274
+ if (!fs2.existsSync(convoDir)) fs2.mkdirSync(convoDir, { recursive: true });
275
+ if (!fs2.existsSync(messagesDir)) fs2.mkdirSync(messagesDir, { recursive: true });
276
+ const messageFilename = `${Date.now()}_${message.sender}.json`;
277
+ fs2.writeFileSync(
278
+ path2.join(messagesDir, messageFilename),
279
+ JSON.stringify(message, null, 2)
280
+ );
281
+ const metaPath = path2.join(convoDir, "conversation.json");
282
+ let convoMeta;
283
+ if (fs2.existsSync(metaPath)) {
284
+ try {
285
+ convoMeta = JSON.parse(fs2.readFileSync(metaPath, "utf-8"));
286
+ } catch {
287
+ convoMeta = createMeta(conversationId, meta);
288
+ }
289
+ } else {
290
+ convoMeta = createMeta(conversationId, meta);
291
+ }
292
+ convoMeta.lastUpdated = message.timestamp;
293
+ convoMeta.lastMessage = message.message.slice(0, 200);
294
+ convoMeta.sender = message.sender;
295
+ if (!convoMeta.title && message.sender === "user") {
296
+ convoMeta.title = message.message.slice(0, 80);
297
+ }
298
+ fs2.writeFileSync(metaPath, JSON.stringify(convoMeta, null, 2));
299
+ }
300
+ function createMeta(conversationId, meta) {
301
+ return {
302
+ conversationId,
303
+ title: null,
304
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
305
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
306
+ lastMessage: "",
307
+ sender: "",
308
+ source: "cli",
309
+ tier: meta.tier,
310
+ provider: meta.provider,
311
+ mode: meta.mode
312
+ };
313
+ }
314
+
315
+ // src/core/file-retrieval.ts
316
+ import * as fs3 from "fs";
317
+ import * as path3 from "path";
318
+ async function getRelevantFileContents(userMessage, localEndpoint) {
319
+ const cwd = process.cwd();
320
+ const summaries = loadSummaries(cwd);
321
+ const dependencies = loadDependencies(cwd);
322
+ if (!summaries || Object.keys(summaries).length === 0) {
323
+ return { fileContents: "", selectedFiles: [] };
324
+ }
325
+ let mapContext = "PROJECT MAP:\n";
326
+ for (const [filePath, summary] of Object.entries(summaries)) {
327
+ mapContext += `- ${filePath}: "${summary}"
328
+ `;
329
+ }
330
+ if (dependencies && Object.keys(dependencies).length > 0) {
331
+ mapContext += "\nDEPENDENCIES:\n";
332
+ for (const [filePath, deps] of Object.entries(dependencies)) {
333
+ if (deps.length > 0) {
334
+ mapContext += `- ${filePath} depends on: [${deps.join(", ")}]
335
+ `;
336
+ }
337
+ }
338
+ }
339
+ const selectionMessages = [
340
+ {
341
+ role: "system",
342
+ content: 'You are a file selector. Based on the user request and project map, return ONLY a JSON array of file paths that are relevant to answering this request. Maximum 5 files. No explanation, no markdown, no code fences. Just a raw JSON array like: ["path/to/file.ts", "path/to/other.ts"]'
343
+ },
344
+ {
345
+ role: "user",
346
+ content: `USER REQUEST: "${userMessage}"
347
+
348
+ ${mapContext}
349
+
350
+ Return ONLY the JSON array of relevant file paths:`
351
+ }
352
+ ];
353
+ try {
354
+ const selectionResponse = await callLocalModel(localEndpoint, selectionMessages);
355
+ const jsonMatch = selectionResponse.match(/\[[\s\S]*?\]/);
356
+ if (!jsonMatch) return { fileContents: "", selectedFiles: [] };
357
+ const selectedFiles = JSON.parse(jsonMatch[0]);
358
+ if (!Array.isArray(selectedFiles) || selectedFiles.length === 0) {
359
+ return { fileContents: "", selectedFiles: [] };
360
+ }
361
+ let fileContents = "## RELEVANT FILES (selected by Bob from project index) ##\n\n";
362
+ const validFiles = [];
363
+ for (const filePath of selectedFiles.slice(0, 10)) {
364
+ const absolutePath = path3.join(cwd, filePath);
365
+ try {
366
+ if (fs3.existsSync(absolutePath)) {
367
+ const content = fs3.readFileSync(absolutePath, "utf-8");
368
+ fileContents += `--- FILE: ${filePath} ---
369
+ ${content}
370
+ --- END FILE ---
371
+
372
+ `;
373
+ validFiles.push(filePath);
374
+ }
375
+ } catch {
376
+ }
377
+ }
378
+ return { fileContents, selectedFiles: validFiles };
379
+ } catch {
380
+ return { fileContents: "", selectedFiles: [] };
381
+ }
382
+ }
383
+
384
+ // src/commands/deepdive.ts
385
+ import chalk4 from "chalk";
386
+ import ora from "ora";
387
+
388
+ // src/ui/animations/deep-dive.ts
389
+ import chalk3 from "chalk";
390
+
391
+ // src/ui/animations/engine.ts
392
+ function startAnimation(frames, config, statusText) {
393
+ let running = true;
394
+ const { frameDelay, frameHeight, loop = false } = config;
395
+ for (let i = 0; i < frameHeight + (statusText ? 2 : 0); i++) {
396
+ console.log("");
397
+ }
398
+ const promise = (async () => {
399
+ for (const frame of frames) {
400
+ if (!running) return;
401
+ renderFrame(frame, frameHeight, statusText);
402
+ await sleep(frameDelay);
403
+ }
404
+ if (loop) {
405
+ let toggle = false;
406
+ while (running) {
407
+ const idx = toggle ? frames.length - 1 : frames.length - 2;
408
+ renderFrame(frames[Math.max(0, idx)], frameHeight, statusText);
409
+ toggle = !toggle;
410
+ await sleep(frameDelay * 2);
411
+ }
412
+ }
413
+ })();
414
+ return {
415
+ stop: () => {
416
+ running = false;
417
+ },
418
+ promise
419
+ };
420
+ }
421
+ function renderFrame(frame, frameHeight, statusText) {
422
+ const totalHeight = frameHeight + (statusText ? 2 : 0);
423
+ process.stdout.write(`\x1B[${totalHeight}A`);
424
+ for (let i = 0; i < frameHeight; i++) {
425
+ process.stdout.write("\x1B[2K");
426
+ if (i < frame.lines.length) {
427
+ process.stdout.write(frame.lines[i]);
428
+ }
429
+ process.stdout.write("\n");
430
+ }
431
+ if (statusText) {
432
+ process.stdout.write("\x1B[2K");
433
+ process.stdout.write(statusText + "\n");
434
+ process.stdout.write("\x1B[2K\n");
435
+ }
436
+ }
437
+ function sleep(ms) {
438
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
439
+ }
440
+
441
+ // src/ui/animations/deep-dive.ts
442
+ var WATER = chalk3.bgHex("#1565C0").hex("#42A5F5");
443
+ var DEEP_WATER = chalk3.bgHex("#0D47A1").hex("#1565C0");
444
+ var BOARD = chalk3.hex("#8D6E63");
445
+ var FIGURE = chalk3.hex("#FFAB00");
446
+ var SPLASH = chalk3.hex("#81D4FA");
447
+ var SKY = chalk3.hex("#90CAF9");
448
+ var POOL_EDGE = chalk3.hex("#455A64");
449
+ var LIGHT = "\u2591";
450
+ var MED = "\u2592";
451
+ var DARK = "\u2593";
452
+ var FRAME_HEIGHT = 12;
453
+ function buildFrames() {
454
+ const frames = [];
455
+ frames.push({ lines: [
456
+ SKY(" "),
457
+ SKY(" "),
458
+ ` ${BOARD("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513")} `,
459
+ ` ${BOARD(" \u2503")} ${FIGURE("\u2593\u2588\u2593")} `,
460
+ ` ${BOARD(" \u2503")} ${FIGURE(" \u2588 ")} `,
461
+ ` ${BOARD(" \u2503")} ${FIGURE("\u2590 \u258C")} `,
462
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")} `,
463
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2551")}${WATER(`${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}`)}${POOL_EDGE("\u2551")} `,
464
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2551")}${WATER(`${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}`)}${POOL_EDGE("\u2551")} `,
465
+ ` ${POOL_EDGE("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253B\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}${POOL_EDGE("\u2551")}${DEEP_WATER(`${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}`)}${POOL_EDGE("\u2551")} `,
466
+ ` ${POOL_EDGE("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")} `,
467
+ ` `
468
+ ] });
469
+ frames.push({ lines: [
470
+ SKY(" "),
471
+ SKY(" "),
472
+ ` ${BOARD("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513")} `,
473
+ ` ${BOARD(" \u2503")} `,
474
+ ` ${BOARD(" \u2503")} ${FIGURE("\u2593\u2588\u2593")} `,
475
+ ` ${BOARD(" \u2503")} ${FIGURE("\u2590\u2588\u258C")} `,
476
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")} `,
477
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2551")}${WATER(`${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}`)}${POOL_EDGE("\u2551")} `,
478
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2551")}${WATER(`${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}`)}${POOL_EDGE("\u2551")} `,
479
+ ` ${POOL_EDGE("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253B\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}${POOL_EDGE("\u2551")}${DEEP_WATER(`${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}`)}${POOL_EDGE("\u2551")} `,
480
+ ` ${POOL_EDGE("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")} `,
481
+ ` `
482
+ ] });
483
+ frames.push({ lines: [
484
+ SKY(" "),
485
+ ` ${FIGURE("\u2593\u2588\u2593")} `,
486
+ ` ${BOARD("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513")} ${FIGURE(" \u2588 ")} `,
487
+ ` ${BOARD(" \u2503")} ${FIGURE("\u2590 \u258C")} `,
488
+ ` ${BOARD(" \u2503")} `,
489
+ ` ${BOARD(" \u2503")} `,
490
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")} `,
491
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2551")}${WATER(`${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}`)}${POOL_EDGE("\u2551")} `,
492
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2551")}${WATER(`${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}`)}${POOL_EDGE("\u2551")} `,
493
+ ` ${POOL_EDGE("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253B\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}${POOL_EDGE("\u2551")}${DEEP_WATER(`${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}`)}${POOL_EDGE("\u2551")} `,
494
+ ` ${POOL_EDGE("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")} `,
495
+ ` `
496
+ ] });
497
+ frames.push({ lines: [
498
+ SKY(" "),
499
+ SKY(" "),
500
+ ` ${BOARD("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513")} `,
501
+ ` ${BOARD(" \u2503")} `,
502
+ ` ${BOARD(" \u2503")} ${FIGURE("\u2590\u2588\u258C")} `,
503
+ ` ${BOARD(" \u2503")} ${FIGURE(" \u25BC ")} `,
504
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")} `,
505
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2551")}${WATER(`${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}`)}${POOL_EDGE("\u2551")} `,
506
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2551")}${WATER(`${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}`)}${POOL_EDGE("\u2551")} `,
507
+ ` ${POOL_EDGE("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253B\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}${POOL_EDGE("\u2551")}${DEEP_WATER(`${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}`)}${POOL_EDGE("\u2551")} `,
508
+ ` ${POOL_EDGE("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")} `,
509
+ ` `
510
+ ] });
511
+ frames.push({ lines: [
512
+ SKY(" "),
513
+ SKY(" "),
514
+ ` ${BOARD("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513")} `,
515
+ ` ${BOARD(" \u2503")} `,
516
+ ` ${BOARD(" \u2503")} `,
517
+ ` ${BOARD(" \u2503")} `,
518
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2554\u2550\u2550\u2550\u2550")}${SPLASH("\u{1F4A6}\u{1F4A6}\u{1F4A6}")}${POOL_EDGE("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")} `,
519
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2551")}${WATER(`${LIGHT}${LIGHT}${LIGHT}`)}${FIGURE("\u2593\u2588\u2593")}${WATER(`${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}`)}${POOL_EDGE("\u2551")} `,
520
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2551")}${WATER(`${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}`)}${POOL_EDGE("\u2551")} `,
521
+ ` ${POOL_EDGE("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253B\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}${POOL_EDGE("\u2551")}${DEEP_WATER(`${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}`)}${POOL_EDGE("\u2551")} `,
522
+ ` ${POOL_EDGE("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")} `,
523
+ ` `
524
+ ] });
525
+ frames.push({ lines: [
526
+ SKY(" "),
527
+ SKY(" "),
528
+ ` ${BOARD("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513")} `,
529
+ ` ${BOARD(" \u2503")} `,
530
+ ` ${BOARD(" \u2503")} `,
531
+ ` ${BOARD(" \u2503")} `,
532
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")} `,
533
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2551")}${WATER(`${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}`)}${POOL_EDGE("\u2551")} `,
534
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2551")}${WATER(`${MED}${MED}${MED}`)}${FIGURE("\u2593\u2588\u2593")}${WATER(`${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}`)}${POOL_EDGE("\u2551")} `,
535
+ ` ${POOL_EDGE("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253B\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}${POOL_EDGE("\u2551")}${DEEP_WATER(`${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}`)}${POOL_EDGE("\u2551")} `,
536
+ ` ${POOL_EDGE("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")} `,
537
+ ` `
538
+ ] });
539
+ frames.push({ lines: [
540
+ SKY(" "),
541
+ SKY(" "),
542
+ ` ${BOARD("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513")} `,
543
+ ` ${BOARD(" \u2503")} `,
544
+ ` ${BOARD(" \u2503")} `,
545
+ ` ${BOARD(" \u2503")} `,
546
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")} `,
547
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2551")}${WATER(`${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}`)}${POOL_EDGE("\u2551")} `,
548
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2551")}${WATER(`${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}`)}${POOL_EDGE("\u2551")} `,
549
+ ` ${POOL_EDGE("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253B\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}${POOL_EDGE("\u2551")}${DEEP_WATER(`${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}`)}${FIGURE("\u{1F93F}")}${DEEP_WATER(`${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}`)}${POOL_EDGE("\u2551")} `,
550
+ ` ${POOL_EDGE("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")} `,
551
+ ` `
552
+ ] });
553
+ frames.push({ lines: [
554
+ SKY(" "),
555
+ SKY(" "),
556
+ ` ${BOARD("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513")} `,
557
+ ` ${BOARD(" \u2503")} `,
558
+ ` ${BOARD(" \u2503")} `,
559
+ ` ${BOARD(" \u2503")} ${chalk3.bold.blue("\u26A1 DEEP DIVE ACTIVE")} `,
560
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")} `,
561
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2551")}${WATER(`${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}${LIGHT}`)}${POOL_EDGE("\u2551")} `,
562
+ ` ${POOL_EDGE("\u2503")} ${POOL_EDGE("\u2551")}${WATER(`${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}${MED}`)}${POOL_EDGE("\u2551")} `,
563
+ ` ${POOL_EDGE("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253B\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}${POOL_EDGE("\u2551")}${DEEP_WATER(`${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}`)}${FIGURE("\u{1F93F}")}${DEEP_WATER(`${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}${DARK}`)}${POOL_EDGE("\u2551")} `,
564
+ ` ${POOL_EDGE("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")} `,
565
+ ` `
566
+ ] });
567
+ return frames;
568
+ }
569
+ function startDeepDiveAnimation() {
570
+ const frames = buildFrames();
571
+ return startAnimation(frames, {
572
+ frameDelay: 400,
573
+ frameHeight: FRAME_HEIGHT,
574
+ loop: true
575
+ }, chalk3.blue(" \u{1F93F} Initiating deep dive..."));
576
+ }
577
+
578
+ // src/commands/deepdive.ts
579
+ var DIVE_BORDER = chalk4.blue;
580
+ function registerDeepDiveCommand(program2) {
581
+ program2.command("deepdives").description("List all deep dives in the current conversation").action(async () => {
582
+ const config = getConfig();
583
+ if (!config.loggedIn || !config.authToken) {
584
+ console.log("");
585
+ console.log(chalk4.red(" \u274C Not logged in. Deep dives require Tier 3."));
586
+ console.log("");
587
+ return;
588
+ }
589
+ if (!config.conversationId) {
590
+ console.log("");
591
+ console.log(chalk4.red(" \u274C No active conversation."));
592
+ console.log("");
593
+ return;
594
+ }
595
+ const spinner = ora({ text: chalk4.cyan(" Loading deep dives..."), spinner: "dots" }).start();
596
+ try {
597
+ const result = await callCloudFunction("listCLIDeepDives", {
598
+ conversationId: config.conversationId
599
+ });
600
+ spinner.stop();
601
+ const dives = result.deepDives || [];
602
+ console.log("");
603
+ console.log(DIVE_BORDER(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
604
+ console.log(DIVE_BORDER(" \u2551") + chalk4.bold.blue(" \u{1F93F} Deep Dives ") + DIVE_BORDER("\u2551"));
605
+ console.log(DIVE_BORDER(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
606
+ if (dives.length === 0) {
607
+ console.log(DIVE_BORDER(" \u2551") + chalk4.gray(" No deep dives yet. ") + DIVE_BORDER("\u2551"));
608
+ console.log(DIVE_BORDER(" \u2551") + chalk4.gray(" Use /deepdive in interactive mode. ") + DIVE_BORDER("\u2551"));
609
+ } else {
610
+ for (const dive of dives) {
611
+ const preview = (dive.initiatingPrompt || "No prompt").slice(0, 35);
612
+ const msgs = dive.messageCount || 0;
613
+ console.log(DIVE_BORDER(" \u2551") + ` ${chalk4.blue(dive.parentMessageId.slice(0, 8))} ${chalk4.white(preview)}${preview.length >= 35 ? "..." : ""}`);
614
+ console.log(DIVE_BORDER(" \u2551") + chalk4.gray(` ${msgs} messages | ${dive.status || "active"}`));
615
+ }
616
+ }
617
+ console.log(DIVE_BORDER(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
618
+ console.log("");
619
+ } catch (error) {
620
+ spinner.stop();
621
+ console.log(chalk4.red(` \u274C ${error.message}`));
622
+ console.log("");
623
+ }
624
+ });
625
+ }
626
+ async function enterDeepDive(config, conversationId, rl) {
627
+ if (!config.loggedIn || !config.authToken) {
628
+ console.log(chalk4.red(" \u274C Deep dives require Tier 3 (platform login)."));
629
+ return;
630
+ }
631
+ const spinner = ora({ text: chalk4.cyan(" Loading messages..."), spinner: "dots" }).start();
632
+ let messages;
633
+ try {
634
+ const result = await callCloudFunction("listCLIDeepDives", {
635
+ conversationId,
636
+ action: "listMessages"
637
+ });
638
+ messages = result.messages || [];
639
+ spinner.stop();
640
+ } catch (error) {
641
+ spinner.stop();
642
+ console.log(chalk4.red(` \u274C ${error.message}`));
643
+ return;
644
+ }
645
+ if (messages.length === 0) {
646
+ console.log(chalk4.yellow(" \u26A0\uFE0F No Bob messages found to deep dive on."));
647
+ return;
648
+ }
649
+ console.log("");
650
+ console.log(DIVE_BORDER(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
651
+ console.log(DIVE_BORDER(" \u2551") + chalk4.bold.blue(" \u{1F93F} Select a message to deep dive on ") + DIVE_BORDER("\u2551"));
652
+ console.log(DIVE_BORDER(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
653
+ for (let i = 0; i < messages.length; i++) {
654
+ const msg = messages[i];
655
+ const preview = (msg.message || "").slice(0, 40);
656
+ console.log(DIVE_BORDER(" \u2551") + ` ${chalk4.cyan(String(i + 1).padStart(2))}. ${chalk4.white(preview)}${preview.length >= 40 ? "..." : ""}`);
657
+ }
658
+ console.log(DIVE_BORDER(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
659
+ console.log("");
660
+ const answer = await new Promise((resolve2) => {
661
+ rl.question(chalk4.blue(" Select (1-" + messages.length + ") or 0 to cancel: "), resolve2);
662
+ });
663
+ const selection = parseInt(answer.trim());
664
+ if (isNaN(selection) || selection === 0 || selection < 1 || selection > messages.length) {
665
+ console.log(chalk4.gray(" Cancelled."));
666
+ return;
667
+ }
668
+ const selectedMessage = messages[selection - 1];
669
+ const parentMessageId = selectedMessage.id;
670
+ const initiatingPrompt = selectedMessage.message.slice(0, 100);
671
+ const animation = startDeepDiveAnimation();
672
+ const divePromise = callCloudFunction("initiateCLIDeepDive", {
673
+ conversationId,
674
+ parentMessageId,
675
+ initiatingPrompt
676
+ });
677
+ try {
678
+ await divePromise;
679
+ animation.stop();
680
+ await new Promise((resolve2) => setTimeout(resolve2, 300));
681
+ await runDeepDiveSession(config, conversationId, parentMessageId, initiatingPrompt, rl);
682
+ } catch (error) {
683
+ animation.stop();
684
+ await new Promise((resolve2) => setTimeout(resolve2, 200));
685
+ console.log(chalk4.red(` \u274C Could not initiate deep dive: ${error.message}`));
686
+ }
687
+ }
688
+ async function runDeepDiveSession(config, conversationId, parentMessageId, initiatingPrompt, rl) {
689
+ const previewText = initiatingPrompt.slice(0, 50) + (initiatingPrompt.length > 50 ? "..." : "");
690
+ console.log("");
691
+ console.log(DIVE_BORDER(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
692
+ console.log(DIVE_BORDER(" \u2551") + chalk4.bold.blue(" \u{1F93F} DEEP DIVE ") + DIVE_BORDER("\u2551"));
693
+ console.log(DIVE_BORDER(" \u2551") + chalk4.gray(` On: "${previewText}"`));
694
+ console.log(DIVE_BORDER(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
695
+ console.log(DIVE_BORDER(" \u2551") + chalk4.gray(" Commands: /surface /promote /clear ") + DIVE_BORDER("\u2551"));
696
+ console.log(DIVE_BORDER(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
697
+ console.log("");
698
+ return new Promise((resolve2) => {
699
+ const deepDivePrompt = () => {
700
+ rl.question(chalk4.blue(" \u{1F93F} You: "), async (input) => {
701
+ const trimmed = input.trim();
702
+ if (!trimmed) {
703
+ deepDivePrompt();
704
+ return;
705
+ }
706
+ if (trimmed === "/surface" || trimmed === "/exit") {
707
+ console.log("");
708
+ console.log(DIVE_BORDER(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
709
+ console.log(DIVE_BORDER(" \u2551") + chalk4.bold.blue(" \u{1F3CA} Surfaced from Deep Dive ") + DIVE_BORDER("\u2551"));
710
+ console.log(DIVE_BORDER(" \u2551") + chalk4.gray(` Back in: ${conversationId.slice(0, 24)}...`));
711
+ console.log(DIVE_BORDER(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
712
+ console.log("");
713
+ resolve2();
714
+ return;
715
+ }
716
+ if (trimmed === "/promote") {
717
+ const promoSpinner = ora({ text: chalk4.blue(" Promoting deep dive..."), spinner: "dots" }).start();
718
+ try {
719
+ await callCloudFunction("promoteDeepDive", {
720
+ conversationId,
721
+ parentMessageId
722
+ });
723
+ promoSpinner.stop();
724
+ console.log("");
725
+ console.log(chalk4.green(" \u2705 Deep dive promoted! Summary merged into main conversation."));
726
+ console.log("");
727
+ } catch (error) {
728
+ promoSpinner.stop();
729
+ console.log(chalk4.red(` \u274C Promote failed: ${error.message}`));
730
+ console.log("");
731
+ }
732
+ resolve2();
733
+ return;
734
+ }
735
+ if (trimmed === "/clear") {
736
+ console.clear();
737
+ console.log(DIVE_BORDER(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
738
+ console.log(DIVE_BORDER(" \u2551") + chalk4.bold.blue(" \u{1F93F} DEEP DIVE (continued) ") + DIVE_BORDER("\u2551"));
739
+ console.log(DIVE_BORDER(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
740
+ console.log("");
741
+ deepDivePrompt();
742
+ return;
743
+ }
744
+ const msgSpinner = ora({ text: chalk4.blue(" \u{1F93F} Bob is diving deep..."), spinner: "dots" }).start();
745
+ try {
746
+ await callCloudFunction("generateDeepDiveResponse", {
747
+ conversationId,
748
+ parentMessageId,
749
+ userMessage: trimmed,
750
+ isLocalModel: false,
751
+ activePersonaId: null
752
+ });
753
+ msgSpinner.stop();
754
+ const latestResult = await callCloudFunction("listCLIDeepDives", {
755
+ conversationId,
756
+ action: "getLatestSandboxMessage",
757
+ parentMessageId
758
+ });
759
+ const responseText = latestResult?.message || "Deep dive response saved.";
760
+ const rendered = renderMarkdown(responseText);
761
+ console.log("");
762
+ console.log(DIVE_BORDER(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
763
+ console.log(DIVE_BORDER(" \u2551") + chalk4.bold.blue(" \u{1F93F} Bob (Deep Dive): ") + DIVE_BORDER("\u2551"));
764
+ console.log(DIVE_BORDER(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
765
+ for (const line of rendered.split("\n")) {
766
+ console.log(DIVE_BORDER(" \u2551") + ` ${line}`);
767
+ }
768
+ console.log(DIVE_BORDER(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
769
+ console.log("");
770
+ } catch (error) {
771
+ msgSpinner.stop();
772
+ console.log(chalk4.red(` \u274C ${error.message}`));
773
+ console.log("");
774
+ }
775
+ deepDivePrompt();
776
+ });
777
+ };
778
+ deepDivePrompt();
779
+ });
780
+ }
781
+
782
+ // src/ui/session-header.ts
783
+ import chalk5 from "chalk";
784
+ import * as path4 from "path";
785
+ var AMBER = chalk5.hex("#FFAB00");
786
+ var ORANGE = chalk5.hex("#E66F24");
787
+ var GREEN = chalk5.hex("#2E7D32");
788
+ var BLUE = chalk5.hex("#42A5F5");
789
+ var DARK_BG = chalk5.bgHex("#222C22");
790
+ function renderSessionHeader(mode) {
791
+ const config = getConfig();
792
+ const projectName = path4.basename(process.cwd());
793
+ const summaries = loadSummaries(process.cwd());
794
+ const fileCount = summaries ? Object.keys(summaries).length : 0;
795
+ const isIndexed = fileCount > 0;
796
+ const modeLabel = mode === "chat" ? "\u{1F916} Code Mode" : "\u{1F3AF} Consultant Mode";
797
+ const modeColor = mode === "chat" ? chalk5.cyan : chalk5.magenta;
798
+ console.log("");
799
+ console.log(DARK_BG(chalk5.gray(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E")));
800
+ console.log(DARK_BG(chalk5.gray(" \u2502 ") + ORANGE("\u25C9") + AMBER(" BOB CLI") + chalk5.gray(" v0.1.0") + chalk5.gray(" \u2502")));
801
+ console.log(DARK_BG(chalk5.gray(" \u2502 ") + modeColor(modeLabel) + chalk5.gray(" \u2502")));
802
+ console.log(DARK_BG(chalk5.gray(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F")));
803
+ console.log("");
804
+ const projectStatus = isIndexed ? GREEN(` \u{1F4DA} ${projectName}`) + chalk5.gray(` (${fileCount} files indexed)`) : chalk5.yellow(` \u26A0\uFE0F ${projectName}`) + chalk5.gray(" (not indexed \u2014 run `bob index`)");
805
+ console.log(projectStatus);
806
+ if (config.loggedIn && config.tier === "platform") {
807
+ console.log(BLUE(` \u{1F4E1} ${config.email}`) + chalk5.gray(` \xB7 Tier 3 \xB7 Provider: ${config.provider || "default"}`));
808
+ } else {
809
+ console.log(chalk5.gray(` \u{1F512} Local-first (Tier 1) \xB7 Provider: ${config.provider || "not set"}`));
810
+ }
811
+ console.log("");
812
+ console.log(chalk5.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
813
+ if (mode === "chat") {
814
+ console.log(chalk5.gray(" /exit \xB7 /new \xB7 /clear \xB7 /include \xB7 /delete \xB7 /deepdive"));
815
+ } else {
816
+ console.log(chalk5.gray(" /exit \xB7 /new \xB7 /clear \xB7 /include"));
817
+ }
818
+ console.log(chalk5.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
819
+ console.log("");
820
+ }
821
+
822
+ // src/ui/welcome.ts
823
+ import chalk6 from "chalk";
824
+ var AMBER2 = chalk6.hex("#FFAB00");
825
+ var ORANGE2 = chalk6.hex("#E66F24");
826
+ var GREEN2 = chalk6.hex("#2E7D32");
827
+ var SKY2 = chalk6.hex("#87CEEB");
828
+ var WHITE = chalk6.white;
829
+ var BORDER = chalk6.hex("#2E7D32");
830
+ var TYPEWRITER_DELAY = 80;
831
+ async function showWelcomeIfFirstRun() {
832
+ const config = getConfig();
833
+ if (config.hasSeenWelcome) return;
834
+ await playWelcomeAnimation();
835
+ setConfigValue("hasSeenWelcome", true);
836
+ }
837
+ async function playWelcomeAnimation() {
838
+ console.clear();
839
+ console.log("");
840
+ console.log(BORDER(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
841
+ console.log(BORDER(" \u2551") + SKY2(" \u2601 \u2601 \u2601 \u2601 \u2601 \u2601 \u2601") + BORDER("\u2551"));
842
+ console.log(BORDER(" \u2551") + SKY2(" \u2601 \u2601 ") + chalk6.yellow("\u2600\uFE0F") + SKY2(" \u2601 \u2601 \u2601") + BORDER("\u2551"));
843
+ console.log(BORDER(" \u2551") + SKY2(" \u2601 \u2601 \u2601 \u2601 \u2601 \u2601 \u2601 \u2601") + BORDER("\u2551"));
844
+ console.log(BORDER(" \u2551") + SKY2(" \u2601 \u2601 \u2601 \u2601 \u2601 \u2601 \u2601 ") + BORDER(" \u2551"));
845
+ console.log(BORDER(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
846
+ console.log(BORDER(" \u2551") + ORANGE2(" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ") + AMBER2("\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557") + " " + BORDER("\u2551"));
847
+ console.log(BORDER(" \u2551") + ORANGE2(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557") + AMBER2("\u255A\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D") + " " + BORDER("\u2551"));
848
+ console.log(BORDER(" \u2551") + ORANGE2(" \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D") + AMBER2(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557") + " " + BORDER("\u2551"));
849
+ console.log(BORDER(" \u2551") + ORANGE2(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557") + AMBER2(" \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551") + " " + BORDER("\u2551"));
850
+ console.log(BORDER(" \u2551") + ORANGE2(" \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D") + AMBER2(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551") + " " + BORDER("\u2551"));
851
+ console.log(BORDER(" \u2551") + ORANGE2(" \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D ") + AMBER2(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D") + " " + BORDER("\u2551"));
852
+ console.log(BORDER(" \u2551") + " " + BORDER("\u2551"));
853
+ console.log(BORDER(" \u2551") + WHITE(" C L I") + chalk6.gray(" v0.1.0") + " " + BORDER("\u2551"));
854
+ console.log(BORDER(" \u2551") + " " + BORDER("\u2551"));
855
+ console.log(BORDER(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
856
+ console.log(BORDER(" \u2551") + " " + BORDER("\u2551"));
857
+ process.stdout.write(BORDER(" \u2551"));
858
+ const tagline = " \u{1F528}\u{1FA9B}\u{1F4BB} We Can Build It!";
859
+ for (let i = 0; i <= tagline.length; i++) {
860
+ process.stdout.write(`\r${BORDER(" \u2551")}${AMBER2(tagline.slice(0, i))}`);
861
+ await sleep2(TYPEWRITER_DELAY);
862
+ }
863
+ const pad = 56 - tagline.length;
864
+ process.stdout.write(" ".repeat(pad > 0 ? pad : 0) + BORDER("\u2551") + "\n");
865
+ console.log(BORDER(" \u2551") + " " + BORDER("\u2551"));
866
+ console.log(BORDER(" \u2551") + chalk6.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500") + " " + BORDER("\u2551"));
867
+ console.log(BORDER(" \u2551") + GREEN2(" \u{1F331} Bob's Workshop") + chalk6.gray(" | A Seedling Company") + " " + BORDER("\u2551"));
868
+ console.log(BORDER(" \u2551") + chalk6.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500") + " " + BORDER("\u2551"));
869
+ console.log(BORDER(" \u2551") + " " + BORDER("\u2551"));
870
+ console.log(BORDER(" \u2551") + chalk6.gray(" Quick Start:") + " " + BORDER("\u2551"));
871
+ console.log(BORDER(" \u2551") + chalk6.gray(" ") + AMBER2("bob chat") + chalk6.gray(" \u2014 Talk to Bob") + " " + BORDER("\u2551"));
872
+ console.log(BORDER(" \u2551") + chalk6.gray(" ") + AMBER2("bob consult") + chalk6.gray(" \u2014 Strategic advice (no code)") + " " + BORDER("\u2551"));
873
+ console.log(BORDER(" \u2551") + chalk6.gray(" ") + AMBER2("bob index") + chalk6.gray(" \u2014 Index your project") + " " + BORDER("\u2551"));
874
+ console.log(BORDER(" \u2551") + chalk6.gray(" ") + AMBER2("bob login") + chalk6.gray(" \u2014 Connect to the platform") + " " + BORDER("\u2551"));
875
+ console.log(BORDER(" \u2551") + chalk6.gray(" ") + AMBER2('bob push "msg"') + chalk6.gray(" \u2014 Git commit + push") + " " + BORDER("\u2551"));
876
+ console.log(BORDER(" \u2551") + chalk6.gray(" ") + AMBER2("bob --help") + chalk6.gray(" \u2014 See all commands") + " " + BORDER("\u2551"));
877
+ console.log(BORDER(" \u2551") + " " + BORDER("\u2551"));
878
+ console.log(BORDER(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
879
+ console.log("");
880
+ await sleep2(800);
881
+ }
882
+ function sleep2(ms) {
883
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
884
+ }
885
+
886
+ // src/commands/chat.ts
887
+ function registerChatCommand(program2) {
888
+ program2.command("chat [message]").description("Chat with Bob \u2014 code-friendly engineering partner").option("-f, --file <path>", "Include a specific file as context").option("--no-context", "Skip local directory context").option("--personalized", "Use personalization mode (Tier 3 only)").option("--new", "Start a fresh conversation").option("-i, --interactive", "Enter interactive conversation mode").action(async (message, options) => {
889
+ const config = getConfig();
890
+ let conversationId = config.conversationId;
891
+ if (options.new || !conversationId) {
892
+ conversationId = `cli_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
893
+ setConfigValue("conversationId", conversationId);
894
+ }
895
+ let localContext = "";
896
+ if (options.context !== false) {
897
+ localContext = buildLocalContext(process.cwd());
898
+ }
899
+ if (options.file) {
900
+ const fileContent = readFileContent(options.file);
901
+ if (fileContent) {
902
+ localContext += `
903
+
904
+ --- INCLUDED FILE: ${options.file} ---
905
+ ${fileContent}
906
+ --- END FILE ---`;
907
+ } else {
908
+ console.log(chalk7.yellow(` \u26A0\uFE0F Could not read file: ${options.file}`));
909
+ }
910
+ }
911
+ if (options.interactive || !message) {
912
+ await runInteractiveSession(config, conversationId, localContext, options.personalized || false, "standard");
913
+ return;
914
+ }
915
+ await sendMessage(message, config, conversationId, localContext, options.personalized || false, "standard", []);
916
+ });
917
+ }
918
+ async function sendMessage(message, config, conversationId, localContext, personalized, mode, history) {
919
+ const spinner = ora2({
920
+ text: chalk7.cyan(" Bob is thinking..."),
921
+ spinner: "dots"
922
+ }).start();
923
+ let selectedFiles = [];
924
+ let hasProjectContext = null;
925
+ try {
926
+ let response;
927
+ let relevantFiles = "";
928
+ if (config.localEndpoint) {
929
+ spinner.text = chalk7.cyan(" Bob is finding relevant files...");
930
+ const retrieval = await getRelevantFileContents(message, config.localEndpoint);
931
+ relevantFiles = retrieval.fileContents;
932
+ selectedFiles = retrieval.selectedFiles;
933
+ }
934
+ spinner.text = chalk7.cyan(" Bob is thinking...");
935
+ let fullContext = localContext;
936
+ if (relevantFiles) {
937
+ fullContext += `
938
+
939
+ ${relevantFiles}`;
940
+ }
941
+ if (config.provider === "local") {
942
+ if (!config.localEndpoint) {
943
+ spinner.stop();
944
+ console.log(chalk7.red(" \u274C No local endpoint configured."));
945
+ console.log(chalk7.gray(" Run `bob config set localEndpoint http://127.0.0.1:11434/api/chat`"));
946
+ return "";
947
+ }
948
+ const messages = [
949
+ { role: "system", content: STANDARD_STYLE_PROMPT + (fullContext ? `
950
+
951
+ ## PROJECT CONTEXT ##
952
+ ${fullContext}` : "") },
953
+ ...history,
954
+ { role: "user", content: message }
955
+ ];
956
+ response = await callLocalModel(config.localEndpoint, messages);
957
+ saveMessage(conversationId, {
958
+ sender: "user",
959
+ message,
960
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
961
+ type: "text"
962
+ }, { tier: "local", provider: config.provider, mode });
963
+ saveMessage(conversationId, {
964
+ sender: "bob",
965
+ message: response,
966
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
967
+ type: "text"
968
+ }, { tier: "local", provider: config.provider, mode });
969
+ } else if (personalized || config.personalizationMode) {
970
+ if (!config.loggedIn || !config.authToken) {
971
+ spinner.stop();
972
+ console.log(chalk7.red(" \u274C Personalization mode requires Tier 3 (platform login)."));
973
+ return "";
974
+ }
975
+ const result = await callCloudFunction("getPersonalizedResponse", {
976
+ userEmail: config.email,
977
+ userId: config.uid,
978
+ conversationId,
979
+ userMessage: message,
980
+ useContext: true,
981
+ localContext: fullContext || null
982
+ });
983
+ response = result?.text || result?.response || result?.message || "No response received.";
984
+ hasProjectContext = result?.hasProjectContext ?? null;
985
+ } else {
986
+ if (!config.loggedIn || !config.authToken) {
987
+ spinner.stop();
988
+ console.log(chalk7.red(" \u274C Not logged in."));
989
+ console.log(chalk7.gray(" Run `bob login` to authenticate, or set provider to local."));
990
+ return "";
991
+ }
992
+ const result = await callCloudFunction("chatWithBobStream", {
993
+ userEmail: config.email,
994
+ userId: config.uid,
995
+ conversationId,
996
+ userMessage: message,
997
+ useContext: true,
998
+ consultantModelId: "gemini-2.5-flash",
999
+ useOrgContext: false,
1000
+ isPassalongActive: false,
1001
+ linkedConvoId: null,
1002
+ localContext: fullContext || null
1003
+ });
1004
+ response = result?.text || result?.response || result?.message || "No response received.";
1005
+ hasProjectContext = result?.hasProjectContext ?? null;
1006
+ }
1007
+ spinner.stop();
1008
+ const proposed = extractProposedFile(response);
1009
+ let displayResponse = response;
1010
+ if (proposed) {
1011
+ displayResponse = stripCodeBlockFromResponse(response);
1012
+ }
1013
+ const rendered = renderMarkdown(displayResponse);
1014
+ console.log("");
1015
+ console.log(chalk7.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1016
+ console.log(chalk7.bold.cyan(" \u{1F916} Bob:"));
1017
+ console.log("");
1018
+ for (const line of rendered.split("\n")) {
1019
+ console.log(` ${line}`);
1020
+ }
1021
+ console.log("");
1022
+ if (selectedFiles.length > 0) {
1023
+ console.log(chalk7.gray(` \u{1F4C2} Referenced: ${selectedFiles.join(", ")}`));
1024
+ }
1025
+ if (config.tier === "platform" && config.provider !== "local") {
1026
+ console.log(chalk7.gray(` \u{1F517} https://bobs-workshop.web.app/#/bobcodeassistant/${conversationId}`));
1027
+ if (hasProjectContext === false) {
1028
+ console.log(chalk7.red(" \u26A0\uFE0F No project workspace connected. Upload a project via the web app"));
1029
+ console.log(chalk7.red(" for full RAG + workspace capabilities."));
1030
+ }
1031
+ }
1032
+ console.log(chalk7.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1033
+ console.log("");
1034
+ if (proposed) {
1035
+ await proposeAndWriteFile(proposed);
1036
+ }
1037
+ return response;
1038
+ } catch (error) {
1039
+ spinner.stop();
1040
+ console.log(chalk7.red(` \u274C ${error.message || "Unknown error"}`));
1041
+ return "";
1042
+ }
1043
+ }
1044
+ async function runInteractiveSession(config, conversationId, localContext, personalized, mode) {
1045
+ if (!config.hasSeenWelcome) {
1046
+ await showWelcomeIfFirstRun();
1047
+ setConfigValue("hasSeenWelcome", true);
1048
+ }
1049
+ renderSessionHeader("chat");
1050
+ const rl = readline.createInterface({
1051
+ input: process.stdin,
1052
+ output: process.stdout
1053
+ });
1054
+ const history = [];
1055
+ const prompt = () => {
1056
+ rl.question(chalk7.green(" You: "), async (input) => {
1057
+ const trimmed = input.trim();
1058
+ if (!trimmed) {
1059
+ prompt();
1060
+ return;
1061
+ }
1062
+ if (trimmed === "/exit" || trimmed === "/quit") {
1063
+ console.log("");
1064
+ console.log(chalk7.gray(` \u{1F4BE} Session: ${conversationId.slice(0, 24)}...`));
1065
+ if (config.tier === "platform" && config.provider !== "local") {
1066
+ console.log(chalk7.gray(` \u{1F517} https://bobs-workshop.web.app/#/bobcodeassistant/${conversationId}`));
1067
+ }
1068
+ console.log(chalk7.gray(" \u{1F44B} See you next time."));
1069
+ console.log("");
1070
+ rl.close();
1071
+ return;
1072
+ }
1073
+ if (trimmed === "/new") {
1074
+ history.length = 0;
1075
+ conversationId = `cli_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1076
+ setConfigValue("conversationId", conversationId);
1077
+ console.log(chalk7.cyan(" \u{1F504} New session started."));
1078
+ console.log("");
1079
+ prompt();
1080
+ return;
1081
+ }
1082
+ if (trimmed === "/clear") {
1083
+ console.clear();
1084
+ renderSessionHeader("chat");
1085
+ prompt();
1086
+ return;
1087
+ }
1088
+ if (trimmed.startsWith("/include ")) {
1089
+ const filePath = trimmed.slice(9).trim();
1090
+ const content = readFileContent(filePath);
1091
+ if (content) {
1092
+ localContext += `
1093
+
1094
+ --- INCLUDED FILE: ${filePath} ---
1095
+ ${content}
1096
+ --- END FILE ---`;
1097
+ const lineCount = content.split("\n").length;
1098
+ console.log(chalk7.green(` \u{1F4C4} Loaded: ${filePath} (${lineCount} lines)`));
1099
+ } else {
1100
+ console.log(chalk7.red(` \u274C Could not read: ${filePath}`));
1101
+ }
1102
+ console.log("");
1103
+ prompt();
1104
+ return;
1105
+ }
1106
+ if (trimmed.startsWith("/delete ")) {
1107
+ const filePath = trimmed.slice(8).trim();
1108
+ const absolutePath = path5.resolve(process.cwd(), filePath);
1109
+ if (!fs4.existsSync(absolutePath)) {
1110
+ console.log(chalk7.red(` \u274C File not found: ${filePath}`));
1111
+ console.log("");
1112
+ prompt();
1113
+ return;
1114
+ }
1115
+ const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
1116
+ const confirm = await new Promise((resolve2) => {
1117
+ rl2.question(chalk7.red(` \u{1F5D1}\uFE0F Delete ${filePath}? This cannot be undone. (y/n): `), resolve2);
1118
+ });
1119
+ rl2.close();
1120
+ if (confirm.toLowerCase() === "y" || confirm.toLowerCase() === "yes") {
1121
+ try {
1122
+ const backupDir = path5.join(process.cwd(), ".bob-backups");
1123
+ if (!fs4.existsSync(backupDir)) fs4.mkdirSync(backupDir, { recursive: true });
1124
+ const timestamp = Date.now();
1125
+ const backupName = filePath.replace(/[\/\\]/g, "_") + `.${timestamp}.deleted`;
1126
+ fs4.copyFileSync(absolutePath, path5.join(backupDir, backupName));
1127
+ fs4.unlinkSync(absolutePath);
1128
+ console.log(chalk7.green(` \u2705 Deleted: ${filePath}`));
1129
+ console.log(chalk7.gray(` \u{1F4E6} Backup saved to .bob-backups/ (recoverable)`));
1130
+ } catch (e) {
1131
+ console.log(chalk7.red(` \u274C Delete failed: ${e.message}`));
1132
+ }
1133
+ } else {
1134
+ console.log(chalk7.gray(" Cancelled."));
1135
+ }
1136
+ console.log("");
1137
+ prompt();
1138
+ return;
1139
+ }
1140
+ if (trimmed === "/deepdive") {
1141
+ await enterDeepDive(config, conversationId, rl);
1142
+ prompt();
1143
+ return;
1144
+ }
1145
+ const response = await sendMessage(trimmed, config, conversationId, localContext, personalized, mode, history);
1146
+ if (response) {
1147
+ history.push({ role: "user", content: trimmed });
1148
+ history.push({ role: "assistant", content: response });
1149
+ }
1150
+ prompt();
1151
+ });
1152
+ };
1153
+ prompt();
1154
+ }
1155
+
1156
+ // src/commands/consult.ts
1157
+ import chalk8 from "chalk";
1158
+ import ora3 from "ora";
1159
+ import * as readline2 from "readline";
1160
+ function registerConsultCommand(program2) {
1161
+ program2.command("consult [message]").description("Consult with Bob \u2014 strategic advice only, no code").option("-f, --file <path>", "Include a specific file as context").option("--no-context", "Skip local directory context").option("--new", "Start a fresh conversation").option("-i, --interactive", "Enter interactive consultant session").action(async (message, options) => {
1162
+ const config = getConfig();
1163
+ let conversationId = config.conversationId;
1164
+ if (options.new || !conversationId) {
1165
+ conversationId = `cli_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1166
+ setConfigValue("conversationId", conversationId);
1167
+ }
1168
+ let localContext = "";
1169
+ if (options.context !== false) {
1170
+ localContext = buildLocalContext(process.cwd());
1171
+ }
1172
+ if (options.file) {
1173
+ const fileContent = readFileContent(options.file);
1174
+ if (fileContent) {
1175
+ localContext += `
1176
+
1177
+ --- INCLUDED FILE: ${options.file} ---
1178
+ ${fileContent}
1179
+ --- END FILE ---`;
1180
+ } else {
1181
+ console.log(chalk8.yellow(` \u26A0\uFE0F Could not read file: ${options.file}`));
1182
+ }
1183
+ }
1184
+ if (options.interactive || !message) {
1185
+ await runInteractiveSession2(config, conversationId, localContext);
1186
+ return;
1187
+ }
1188
+ await sendConsultMessage(message, config, conversationId, localContext, []);
1189
+ });
1190
+ }
1191
+ async function sendConsultMessage(message, config, conversationId, localContext, history) {
1192
+ const spinner = ora3({
1193
+ text: chalk8.cyan(" Bob is thinking (consultant mode)..."),
1194
+ spinner: "dots"
1195
+ }).start();
1196
+ let selectedFiles = [];
1197
+ let hasProjectContext = null;
1198
+ try {
1199
+ let response;
1200
+ if (config.provider === "local") {
1201
+ if (!config.localEndpoint) {
1202
+ spinner.stop();
1203
+ console.log(chalk8.red(" \u274C No local endpoint configured."));
1204
+ console.log(chalk8.gray(" Run `bob config set localEndpoint http://127.0.0.1:11434/api/chat`"));
1205
+ return "";
1206
+ }
1207
+ spinner.text = chalk8.cyan(" Bob is finding relevant files...");
1208
+ const retrieval = await getRelevantFileContents(message, config.localEndpoint);
1209
+ const relevantFiles = retrieval.fileContents;
1210
+ selectedFiles = retrieval.selectedFiles;
1211
+ spinner.text = chalk8.cyan(" Bob is thinking (consultant mode)...");
1212
+ let fullContext = localContext;
1213
+ if (relevantFiles) {
1214
+ fullContext += `
1215
+
1216
+ ${relevantFiles}`;
1217
+ }
1218
+ const messages = [
1219
+ { role: "system", content: CONSULTANT_STYLE_PROMPT + (fullContext ? `
1220
+
1221
+ ## PROJECT CONTEXT ##
1222
+ ${fullContext}` : "") },
1223
+ ...history,
1224
+ { role: "user", content: message }
1225
+ ];
1226
+ response = await callLocalModel(config.localEndpoint, messages);
1227
+ saveMessage(conversationId, {
1228
+ sender: "user",
1229
+ message,
1230
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1231
+ type: "text"
1232
+ }, { tier: "local", provider: config.provider, mode: "consultant" });
1233
+ saveMessage(conversationId, {
1234
+ sender: "bob",
1235
+ message: response,
1236
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1237
+ type: "text"
1238
+ }, { tier: "local", provider: config.provider, mode: "consultant" });
1239
+ } else {
1240
+ if (!config.loggedIn || !config.authToken) {
1241
+ spinner.stop();
1242
+ console.log(chalk8.red(" \u274C Not logged in."));
1243
+ console.log(chalk8.gray(" Run `bob login` to authenticate, or set provider to local."));
1244
+ return "";
1245
+ }
1246
+ const result = await callCloudFunction("consultWithBobStream", {
1247
+ userEmail: config.email,
1248
+ userId: config.uid,
1249
+ conversationId,
1250
+ userMessage: message,
1251
+ useContext: true,
1252
+ consultantModelId: "gemini-2.5-flash",
1253
+ useOrgContext: false,
1254
+ isPassalongActive: false,
1255
+ linkedConvoId: null,
1256
+ localContext: localContext || null
1257
+ });
1258
+ response = result?.text || result?.response || result?.message || "No response received.";
1259
+ hasProjectContext = result?.hasProjectContext ?? null;
1260
+ }
1261
+ spinner.stop();
1262
+ const rendered = renderMarkdown(response);
1263
+ console.log("");
1264
+ console.log(chalk8.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1265
+ console.log(chalk8.bold.magenta(" \u{1F3AF} Bob (Consultant):"));
1266
+ console.log("");
1267
+ for (const line of rendered.split("\n")) {
1268
+ console.log(` ${line}`);
1269
+ }
1270
+ console.log("");
1271
+ if (selectedFiles.length > 0) {
1272
+ console.log(chalk8.gray(` \u{1F4C2} Referenced: ${selectedFiles.join(", ")}`));
1273
+ }
1274
+ if (config.tier === "platform" && config.provider !== "local") {
1275
+ console.log(chalk8.gray(` \u{1F517} https://bobs-workshop.web.app/#/bobcodeassistant/${conversationId}`));
1276
+ if (hasProjectContext === false) {
1277
+ console.log(chalk8.red(" \u26A0\uFE0F No project workspace connected. Upload a project via the web app"));
1278
+ console.log(chalk8.red(" for full RAG + workspace capabilities."));
1279
+ }
1280
+ }
1281
+ console.log(chalk8.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1282
+ console.log("");
1283
+ return response;
1284
+ } catch (error) {
1285
+ spinner.stop();
1286
+ console.log(chalk8.red(` \u274C ${error.message || "Unknown error"}`));
1287
+ return "";
1288
+ }
1289
+ }
1290
+ async function runInteractiveSession2(config, conversationId, localContext) {
1291
+ if (config.hasSeenWelcome === void 0 || !config.hasSeenWelcome) {
1292
+ await showWelcomeIfFirstRun();
1293
+ setConfigValue("hasSeenWelcome", true);
1294
+ }
1295
+ renderSessionHeader("consult");
1296
+ const rl = readline2.createInterface({
1297
+ input: process.stdin,
1298
+ output: process.stdout
1299
+ });
1300
+ const history = [];
1301
+ const prompt = () => {
1302
+ rl.question(chalk8.green(" You: "), async (input) => {
1303
+ const trimmed = input.trim();
1304
+ if (!trimmed) {
1305
+ prompt();
1306
+ return;
1307
+ }
1308
+ if (trimmed === "/exit" || trimmed === "/quit") {
1309
+ console.log("");
1310
+ console.log(chalk8.gray(` \u{1F4BE} Session: ${conversationId.slice(0, 24)}...`));
1311
+ if (config.tier === "platform" && config.provider !== "local") {
1312
+ console.log(chalk8.gray(` \u{1F517} https://bobs-workshop.web.app/#/bobcodeassistant/${conversationId}`));
1313
+ }
1314
+ console.log(chalk8.gray(" \u{1F44B} See you next time."));
1315
+ console.log("");
1316
+ rl.close();
1317
+ return;
1318
+ }
1319
+ if (trimmed === "/new") {
1320
+ history.length = 0;
1321
+ conversationId = `cli_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1322
+ setConfigValue("conversationId", conversationId);
1323
+ console.log(chalk8.magenta(" \u{1F504} New consultant session started."));
1324
+ console.log("");
1325
+ prompt();
1326
+ return;
1327
+ }
1328
+ if (trimmed === "/clear") {
1329
+ console.clear();
1330
+ renderSessionHeader("consult");
1331
+ prompt();
1332
+ return;
1333
+ }
1334
+ if (trimmed.startsWith("/include ")) {
1335
+ const filePath = trimmed.slice(9).trim();
1336
+ const content = readFileContent(filePath);
1337
+ if (content) {
1338
+ localContext += `
1339
+
1340
+ --- INCLUDED FILE: ${filePath} ---
1341
+ ${content}
1342
+ --- END FILE ---`;
1343
+ const lineCount = content.split("\n").length;
1344
+ console.log(chalk8.green(` \u{1F4C4} Loaded: ${filePath} (${lineCount} lines)`));
1345
+ } else {
1346
+ console.log(chalk8.red(` \u274C Could not read: ${filePath}`));
1347
+ }
1348
+ console.log("");
1349
+ prompt();
1350
+ return;
1351
+ }
1352
+ const response = await sendConsultMessage(trimmed, config, conversationId, localContext, history);
1353
+ if (response) {
1354
+ history.push({ role: "user", content: trimmed });
1355
+ history.push({ role: "assistant", content: response });
1356
+ }
1357
+ prompt();
1358
+ });
1359
+ };
1360
+ prompt();
1361
+ }
1362
+
1363
+ // src/commands/index.ts
1364
+ import chalk9 from "chalk";
1365
+ import * as fs5 from "fs";
1366
+ import * as path6 from "path";
1367
+ var IGNORE_DIRS = ["node_modules", ".git", "dist", "build", ".dart_tool", ".idea", ".gradle", ".pub-cache", ".bob"];
1368
+ var CODE_EXTENSIONS = /* @__PURE__ */ new Set([".dart", ".js", ".ts", ".html", ".css", ".json", ".yaml", ".yml", ".xml", ".sh", ".md"]);
1369
+ function registerIndexCommand(program2) {
1370
+ program2.command("index").description("Index the current project \u2014 generates summaries and dependency map").option("--verbose", "Show detailed progress with summaries").action(async (options) => {
1371
+ const config = getConfig();
1372
+ const cwd = process.cwd();
1373
+ const projectName = getProjectName(cwd);
1374
+ if (config.provider !== "local" || !config.localEndpoint) {
1375
+ console.log("");
1376
+ console.log(chalk9.red(" \u274C Indexing requires a local model."));
1377
+ console.log(chalk9.gray(" Run `bob config set provider local`"));
1378
+ console.log(chalk9.gray(" Run `bob config set localEndpoint http://127.0.0.1:11434/api/chat`"));
1379
+ console.log("");
1380
+ return;
1381
+ }
1382
+ console.log("");
1383
+ console.log(chalk9.bold.cyan(` \u26A1 Indexing project: ${projectName}`));
1384
+ console.log(chalk9.gray(` \u{1F4C1} ${cwd}`));
1385
+ console.log(chalk9.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1386
+ console.log("");
1387
+ const files = scanProjectFiles(cwd);
1388
+ if (files.length === 0) {
1389
+ console.log(chalk9.yellow(" \u26A0\uFE0F No code files found to index."));
1390
+ return;
1391
+ }
1392
+ console.log(chalk9.gray(` Found ${files.length} files to analyze.`));
1393
+ console.log("");
1394
+ console.log("");
1395
+ console.log("");
1396
+ console.log("");
1397
+ console.log("");
1398
+ const { runId, runDir, tasksDir } = createAnalysisRun(cwd, files);
1399
+ const summaries = {};
1400
+ let completed = 0;
1401
+ for (const filePath of files) {
1402
+ const absolutePath = path6.join(cwd, filePath);
1403
+ let content;
1404
+ try {
1405
+ content = fs5.readFileSync(absolutePath, "utf-8");
1406
+ } catch {
1407
+ console.log(chalk9.red(` \u274C Could not read: ${filePath}`));
1408
+ continue;
1409
+ }
1410
+ if (content.length > 5e4) {
1411
+ const shortSummary = `Large file (${Math.round(content.length / 1e3)}KB). Skipped detailed analysis.`;
1412
+ summaries[filePath] = shortSummary;
1413
+ completeTask(tasksDir, filePath, shortSummary);
1414
+ completed++;
1415
+ updateManifestProgress(runDir, completed);
1416
+ printProgress(completed, files.length, filePath, shortSummary, [], options.verbose);
1417
+ continue;
1418
+ }
1419
+ try {
1420
+ const messages = [
1421
+ {
1422
+ role: "system",
1423
+ content: "You are a code analyst. Respond with ONLY a 2-3 sentence summary. No formatting, no headers, no bullets. Just plain sentences."
1424
+ },
1425
+ {
1426
+ role: "user",
1427
+ content: `Summarize this file. What does it do, what does it export, and what does it depend on?
1428
+
1429
+ File: ${filePath}
1430
+
1431
+ ${content}`
1432
+ }
1433
+ ];
1434
+ const summary = await callLocalModel(config.localEndpoint, messages);
1435
+ summaries[filePath] = summary.trim();
1436
+ completeTask(tasksDir, filePath, summary.trim());
1437
+ completed++;
1438
+ updateManifestProgress(runDir, completed);
1439
+ printProgress(completed, files.length, filePath, summary.trim(), [], options.verbose);
1440
+ } catch (error) {
1441
+ console.log(chalk9.red(` \u274C Failed: ${filePath} \u2014 ${error.message}`));
1442
+ completed++;
1443
+ updateManifestProgress(runDir, completed);
1444
+ }
1445
+ }
1446
+ console.log("");
1447
+ console.log("");
1448
+ console.log(chalk9.cyan(" \u{1F517} Generating dependency map..."));
1449
+ try {
1450
+ const summaryContext = Object.entries(summaries).map(([fp, summary]) => `[${fp}]: ${summary}`).join("\n\n");
1451
+ const messages = [
1452
+ {
1453
+ role: "system",
1454
+ content: "You are a senior software architect. Respond with ONLY a valid JSON object. No explanation, no markdown, no code fences. Just raw JSON."
1455
+ },
1456
+ {
1457
+ role: "user",
1458
+ content: `Based on these file summaries, generate a JSON dependency map. Each key is a file path, each value is an array of file paths that file depends on or interacts with. Only include direct, meaningful dependencies.
1459
+
1460
+ FILE SUMMARIES:
1461
+ ${summaryContext}
1462
+
1463
+ Respond with ONLY the JSON object:`
1464
+ }
1465
+ ];
1466
+ const depResponse = await callLocalModel(config.localEndpoint, messages);
1467
+ let dependencies = {};
1468
+ try {
1469
+ const jsonMatch = depResponse.match(/\{[\s\S]*\}/);
1470
+ if (jsonMatch) {
1471
+ dependencies = JSON.parse(jsonMatch[0]);
1472
+ }
1473
+ } catch {
1474
+ console.log(chalk9.yellow(" \u26A0\uFE0F Could not parse dependency map. Saving empty map."));
1475
+ dependencies = {};
1476
+ }
1477
+ saveSummaries(cwd, summaries);
1478
+ saveDependencies(cwd, dependencies);
1479
+ for (const [filePath, deps] of Object.entries(dependencies)) {
1480
+ const taskId = filePath.replace(/[\/\\]/g, "_");
1481
+ const taskPath = path6.join(tasksDir, `${taskId}.json`);
1482
+ if (fs5.existsSync(taskPath)) {
1483
+ const task = JSON.parse(fs5.readFileSync(taskPath, "utf-8"));
1484
+ task.dependencies = deps;
1485
+ fs5.writeFileSync(taskPath, JSON.stringify(task, null, 2));
1486
+ }
1487
+ }
1488
+ updateManifestProgress(runDir, completed, "completed");
1489
+ console.log(chalk9.green(` \u2705 Dependency map generated for ${Object.keys(dependencies).length} files.`));
1490
+ } catch (error) {
1491
+ console.log(chalk9.red(` \u274C Dependency mapping failed: ${error.message}`));
1492
+ saveSummaries(cwd, summaries);
1493
+ saveDependencies(cwd, {});
1494
+ updateManifestProgress(runDir, completed, "completed_partial");
1495
+ }
1496
+ console.log("");
1497
+ console.log(chalk9.bold.green(` \u2705 Indexing complete: ${projectName}`));
1498
+ console.log(chalk9.gray(` \u{1F4C4} ${Object.keys(summaries).length} files summarized`));
1499
+ console.log(chalk9.gray(` \u{1F4BE} Saved to: ~/.bob/projects/${projectName}/analysis/`));
1500
+ console.log("");
1501
+ });
1502
+ }
1503
+ function scanProjectFiles(rootDir, currentDir, depth = 0) {
1504
+ if (depth > 6) return [];
1505
+ const dir = currentDir || rootDir;
1506
+ const files = [];
1507
+ try {
1508
+ const entries = fs5.readdirSync(dir, { withFileTypes: true });
1509
+ for (const entry of entries) {
1510
+ if (IGNORE_DIRS.includes(entry.name)) continue;
1511
+ if (entry.name.startsWith(".")) continue;
1512
+ const fullPath = path6.join(dir, entry.name);
1513
+ const relativePath = path6.relative(rootDir, fullPath).replace(/\\/g, "/");
1514
+ if (entry.isDirectory()) {
1515
+ files.push(...scanProjectFiles(rootDir, fullPath, depth + 1));
1516
+ } else {
1517
+ const ext = path6.extname(entry.name).toLowerCase();
1518
+ if (CODE_EXTENSIONS.has(ext)) {
1519
+ files.push(relativePath);
1520
+ }
1521
+ }
1522
+ }
1523
+ } catch {
1524
+ }
1525
+ return files;
1526
+ }
1527
+ function printProgress(completed, total, filePath, summary, dependencies, verbose) {
1528
+ const percent = completed / total;
1529
+ const barLength = 30;
1530
+ const filled = Math.round(percent * barLength);
1531
+ let barColor;
1532
+ if (percent < 0.25) {
1533
+ barColor = chalk9.red;
1534
+ } else if (percent < 0.5) {
1535
+ barColor = chalk9.hex("#FF8C00");
1536
+ } else if (percent < 0.75) {
1537
+ barColor = chalk9.yellow;
1538
+ } else {
1539
+ barColor = chalk9.green;
1540
+ }
1541
+ const filledBar = barColor("\u2588".repeat(filled));
1542
+ const emptyBar = chalk9.gray("\u2591".repeat(barLength - filled));
1543
+ const percentText = barColor(`${Math.round(percent * 100)}%`);
1544
+ process.stdout.write("\x1B[2K\x1B[1A\x1B[2K\x1B[1A\x1B[2K\x1B[1A\x1B[2K\r");
1545
+ console.log(` ${chalk9.cyan("\u26A1")} Indexing [${filledBar}${emptyBar}] ${completed}/${total} ${percentText}`);
1546
+ console.log(chalk9.green(` \u2705 ${filePath}`));
1547
+ if (verbose) {
1548
+ console.log(chalk9.gray(` "${summary.slice(0, 120)}${summary.length > 120 ? "..." : ""}"`));
1549
+ if (dependencies.length > 0) {
1550
+ console.log(chalk9.gray(` \u2192 depends on: ${dependencies.join(", ")}`));
1551
+ } else {
1552
+ console.log(chalk9.gray(` \u2192 depends on: (mapping after all summaries)`));
1553
+ }
1554
+ } else {
1555
+ console.log(chalk9.gray(` "${summary.slice(0, 80)}${summary.length > 80 ? "..." : ""}"`));
1556
+ console.log("");
1557
+ }
1558
+ }
1559
+
1560
+ // src/commands/push.ts
1561
+ import chalk10 from "chalk";
1562
+ import ora4 from "ora";
1563
+ import simpleGit from "simple-git";
1564
+ function registerPushCommand(program2) {
1565
+ program2.command("push <message>").description("Stage all changes, commit, and push to remote").option("--no-stage", "Skip staging (commit only tracked changes)").option("-b, --branch <name>", "Push to a specific branch").action(async (message, options) => {
1566
+ const git = simpleGit(process.cwd());
1567
+ const isRepo = await git.checkIsRepo();
1568
+ if (!isRepo) {
1569
+ console.log("");
1570
+ console.log(chalk10.red(" \u274C Not a git repository."));
1571
+ console.log(chalk10.gray(" Run this command from inside a git project."));
1572
+ console.log("");
1573
+ return;
1574
+ }
1575
+ const spinner = ora4({
1576
+ text: chalk10.cyan(" Preparing commit..."),
1577
+ spinner: "dots"
1578
+ }).start();
1579
+ try {
1580
+ const status = await git.status();
1581
+ if (status.files.length === 0) {
1582
+ spinner.stop();
1583
+ console.log("");
1584
+ console.log(chalk10.yellow(" \u26A0\uFE0F Nothing to commit. Working tree is clean."));
1585
+ console.log("");
1586
+ return;
1587
+ }
1588
+ if (options.stage !== false) {
1589
+ spinner.text = chalk10.cyan(` Staging ${status.files.length} file(s)...`);
1590
+ await git.add(".");
1591
+ }
1592
+ spinner.text = chalk10.cyan(" Committing...");
1593
+ const commitResult = await git.commit(message);
1594
+ const commitHash = commitResult.commit ? commitResult.commit.slice(0, 7) : "unknown";
1595
+ spinner.text = chalk10.cyan(" Pushing to remote...");
1596
+ const currentBranch = options.branch || (await git.branchLocal()).current;
1597
+ try {
1598
+ await git.push("origin", currentBranch);
1599
+ } catch (pushError) {
1600
+ if (pushError.message?.includes("no upstream") || pushError.message?.includes("has no upstream")) {
1601
+ await git.push(["--set-upstream", "origin", currentBranch]);
1602
+ } else {
1603
+ throw pushError;
1604
+ }
1605
+ }
1606
+ spinner.stop();
1607
+ console.log("");
1608
+ console.log(chalk10.green(" \u2705 Pushed successfully"));
1609
+ console.log(chalk10.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1610
+ console.log(` ${chalk10.cyan("Commit:")} ${commitHash}`);
1611
+ console.log(` ${chalk10.cyan("Branch:")} ${currentBranch}`);
1612
+ console.log(` ${chalk10.cyan("Message:")} ${message}`);
1613
+ console.log(` ${chalk10.cyan("Files:")} ${status.files.length} changed`);
1614
+ console.log(chalk10.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1615
+ console.log("");
1616
+ if (status.files.length <= 10) {
1617
+ for (const file of status.files) {
1618
+ const icon = file.index === "?" ? "\u2795" : file.index === "D" ? "\u{1F5D1}\uFE0F" : "\u270F\uFE0F";
1619
+ console.log(chalk10.gray(` ${icon} ${file.path}`));
1620
+ }
1621
+ console.log("");
1622
+ } else {
1623
+ console.log(chalk10.gray(` ${status.created.length} added, ${status.modified.length} modified, ${status.deleted.length} deleted`));
1624
+ console.log("");
1625
+ }
1626
+ } catch (error) {
1627
+ spinner.stop();
1628
+ console.log("");
1629
+ console.log(chalk10.red(` \u274C Push failed: ${error.message}`));
1630
+ if (error.message?.includes("Authentication failed") || error.message?.includes("could not read Username")) {
1631
+ console.log(chalk10.gray(" Make sure your git credentials are configured."));
1632
+ console.log(chalk10.gray(" Run: git config --global credential.helper store"));
1633
+ }
1634
+ if (error.message?.includes("conflict") || error.message?.includes("rejected")) {
1635
+ console.log(chalk10.gray(" There may be remote changes. Try: git pull --rebase"));
1636
+ }
1637
+ console.log("");
1638
+ }
1639
+ });
1640
+ }
1641
+
1642
+ // src/commands/byok.ts
1643
+ import chalk11 from "chalk";
1644
+ import ora5 from "ora";
1645
+ import * as readline3 from "readline";
1646
+ var VALID_PROVIDERS2 = ["google", "bedrock", "claude", "openai", "grok"];
1647
+ function registerByokCommand(program2) {
1648
+ const byokCmd = program2.command("byok").description("Manage your Bring Your Own Key (BYOK) configuration");
1649
+ byokCmd.command("set <provider> <key>").description("Configure an API key for a provider").action(async (provider, key) => {
1650
+ const config = getConfig();
1651
+ if (!config.loggedIn || !config.authToken) {
1652
+ console.log("");
1653
+ console.log(chalk11.red(" \u274C Not logged in."));
1654
+ console.log(chalk11.gray(" Run `bob login` first."));
1655
+ console.log("");
1656
+ return;
1657
+ }
1658
+ if (!VALID_PROVIDERS2.includes(provider.toLowerCase())) {
1659
+ console.log("");
1660
+ console.log(chalk11.red(` \u274C Invalid provider: "${provider}"`));
1661
+ console.log(chalk11.gray(` Valid providers: ${VALID_PROVIDERS2.join(", ")}`));
1662
+ console.log("");
1663
+ return;
1664
+ }
1665
+ const spinner = ora5({
1666
+ text: chalk11.cyan(" Saving key..."),
1667
+ spinner: "dots"
1668
+ }).start();
1669
+ try {
1670
+ const result = await callCloudFunction("updateBYOKFromCLI", {
1671
+ action: "set",
1672
+ provider: provider.toLowerCase(),
1673
+ apiKey: key
1674
+ });
1675
+ spinner.stop();
1676
+ console.log("");
1677
+ console.log(chalk11.green(` \u2705 ${result.message}`));
1678
+ console.log(chalk11.gray(` Provider: ${provider.toLowerCase()}`));
1679
+ console.log(chalk11.gray(" Key stored securely on the platform."));
1680
+ console.log("");
1681
+ } catch (error) {
1682
+ spinner.stop();
1683
+ if (error.message?.includes("ORG_USER_BLOCKED") || error.response?.data?.error?.message?.includes("ORG_USER_BLOCKED")) {
1684
+ console.log("");
1685
+ console.log(chalk11.yellow(" \u26A0\uFE0F BYOK configuration for Organization accounts is managed by your admin."));
1686
+ console.log(chalk11.gray(" Contact your administrator to update keys from the Admin Dashboard:"));
1687
+ console.log(chalk11.cyan(" https://bobs-workshop.web.app/#/bobsadmindashboard"));
1688
+ console.log("");
1689
+ } else {
1690
+ console.log("");
1691
+ console.log(chalk11.red(` \u274C ${error.message || "Failed to save key."}`));
1692
+ console.log("");
1693
+ }
1694
+ }
1695
+ });
1696
+ byokCmd.command("remove <provider>").description("Remove an API key for a provider").action(async (provider) => {
1697
+ const config = getConfig();
1698
+ if (!config.loggedIn || !config.authToken) {
1699
+ console.log("");
1700
+ console.log(chalk11.red(" \u274C Not logged in."));
1701
+ console.log(chalk11.gray(" Run `bob login` first."));
1702
+ console.log("");
1703
+ return;
1704
+ }
1705
+ if (!VALID_PROVIDERS2.includes(provider.toLowerCase())) {
1706
+ console.log("");
1707
+ console.log(chalk11.red(` \u274C Invalid provider: "${provider}"`));
1708
+ console.log(chalk11.gray(` Valid providers: ${VALID_PROVIDERS2.join(", ")}`));
1709
+ console.log("");
1710
+ return;
1711
+ }
1712
+ const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
1713
+ const answer = await new Promise((resolve2) => {
1714
+ rl.question(chalk11.yellow(` Remove ${provider} key? (y/n): `), resolve2);
1715
+ });
1716
+ rl.close();
1717
+ if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
1718
+ console.log(chalk11.gray(" Cancelled."));
1719
+ return;
1720
+ }
1721
+ const spinner = ora5({
1722
+ text: chalk11.cyan(" Removing key..."),
1723
+ spinner: "dots"
1724
+ }).start();
1725
+ try {
1726
+ const result = await callCloudFunction("updateBYOKFromCLI", {
1727
+ action: "remove",
1728
+ provider: provider.toLowerCase()
1729
+ });
1730
+ spinner.stop();
1731
+ console.log("");
1732
+ console.log(chalk11.green(` \u2705 ${result.message}`));
1733
+ console.log("");
1734
+ } catch (error) {
1735
+ spinner.stop();
1736
+ if (error.message?.includes("ORG_USER_BLOCKED") || error.response?.data?.error?.message?.includes("ORG_USER_BLOCKED")) {
1737
+ console.log("");
1738
+ console.log(chalk11.yellow(" \u26A0\uFE0F BYOK configuration for Organization accounts is managed by your admin."));
1739
+ console.log(chalk11.gray(" Contact your administrator to update keys from the Admin Dashboard:"));
1740
+ console.log(chalk11.cyan(" https://bobs-workshop.web.app/#/bobsadmindashboard"));
1741
+ console.log("");
1742
+ } else {
1743
+ console.log("");
1744
+ console.log(chalk11.red(` \u274C ${error.message || "Failed to remove key."}`));
1745
+ console.log("");
1746
+ }
1747
+ }
1748
+ });
1749
+ byokCmd.command("status").description("Show which BYOK keys are configured").action(async () => {
1750
+ const config = getConfig();
1751
+ if (!config.loggedIn || !config.authToken) {
1752
+ console.log("");
1753
+ console.log(chalk11.red(" \u274C Not logged in."));
1754
+ console.log(chalk11.gray(" Run `bob login` first."));
1755
+ console.log("");
1756
+ return;
1757
+ }
1758
+ const spinner = ora5({
1759
+ text: chalk11.cyan(" Checking BYOK status..."),
1760
+ spinner: "dots"
1761
+ }).start();
1762
+ try {
1763
+ const result = await callCloudFunction("updateBYOKFromCLI", {
1764
+ action: "status"
1765
+ });
1766
+ spinner.stop();
1767
+ const keys = result.keys || [];
1768
+ console.log("");
1769
+ console.log(chalk11.bold(" \u{1F511} BYOK Status"));
1770
+ console.log(chalk11.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1771
+ if (keys.length === 0) {
1772
+ console.log(chalk11.gray(" No keys configured."));
1773
+ console.log(chalk11.gray(" Run `bob byok set <provider> <key>` to add one."));
1774
+ } else {
1775
+ for (const key of keys) {
1776
+ const statusIcon = key.isActive ? chalk11.green("\u25CF") : chalk11.red("\u25CB");
1777
+ const statusText = key.isActive ? chalk11.green("Active") : chalk11.red("Inactive");
1778
+ console.log(` ${statusIcon} ${chalk11.cyan(key.provider.padEnd(12))} ${statusText} (via ${key.source})`);
1779
+ }
1780
+ }
1781
+ console.log(chalk11.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1782
+ console.log("");
1783
+ } catch (error) {
1784
+ spinner.stop();
1785
+ if (error.message?.includes("ORG_USER_BLOCKED") || error.response?.data?.error?.message?.includes("ORG_USER_BLOCKED")) {
1786
+ console.log("");
1787
+ console.log(chalk11.yellow(" \u26A0\uFE0F BYOK configuration for Organization accounts is managed by your admin."));
1788
+ console.log(chalk11.gray(" Contact your administrator to update keys from the Admin Dashboard:"));
1789
+ console.log(chalk11.cyan(" https://bobs-workshop.web.app/#/bobsadmindashboard"));
1790
+ console.log("");
1791
+ } else {
1792
+ console.log("");
1793
+ console.log(chalk11.red(` \u274C ${error.message || "Failed to check status."}`));
1794
+ console.log("");
1795
+ }
1796
+ }
1797
+ });
1798
+ }
1799
+
1800
+ // src/commands/conversations.ts
1801
+ import chalk12 from "chalk";
1802
+ import ora6 from "ora";
1803
+ import * as readline4 from "readline";
1804
+ function registerConversationsCommand(program2) {
1805
+ const convosCmd = program2.command("conversations").description("List, search, and join existing conversations").option("-p, --page <number>", "Page number", "1").option("-s, --search <query>", "Search conversations by title or content").action(async (options) => {
1806
+ const config = getConfig();
1807
+ if (!config.loggedIn || !config.authToken) {
1808
+ console.log("");
1809
+ console.log(chalk12.red(" \u274C Not logged in."));
1810
+ console.log(chalk12.gray(" Run `bob login` first."));
1811
+ console.log("");
1812
+ return;
1813
+ }
1814
+ const spinner = ora6({
1815
+ text: chalk12.cyan(" Loading conversations..."),
1816
+ spinner: "dots"
1817
+ }).start();
1818
+ try {
1819
+ const result = await callCloudFunction("listCLIConversations", {
1820
+ page: parseInt(options.page || "1"),
1821
+ limit: 10,
1822
+ search: options.search || null
1823
+ });
1824
+ spinner.stop();
1825
+ const conversations = result.conversations || [];
1826
+ if (conversations.length === 0) {
1827
+ console.log("");
1828
+ console.log(chalk12.yellow(" No conversations found."));
1829
+ if (options.search) {
1830
+ console.log(chalk12.gray(` Search: "${options.search}"`));
1831
+ }
1832
+ console.log("");
1833
+ return;
1834
+ }
1835
+ console.log("");
1836
+ console.log(chalk12.bold(" \u{1F4AC} Your Conversations"));
1837
+ console.log(chalk12.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1838
+ if (options.search) {
1839
+ console.log(chalk12.gray(` Search: "${options.search}" (${result.total} results)`));
1840
+ console.log("");
1841
+ }
1842
+ conversations.forEach((convo, index) => {
1843
+ const num = index + 1;
1844
+ const timeAgo = convo.lastUpdated ? getTimeAgo(convo.lastUpdated) : "unknown";
1845
+ const sourceIcon = convo.source === "cli" ? "\u2328\uFE0F" : "\u{1F310}";
1846
+ const projectIcon = convo.hasProject ? "\u{1F4C1}" : " ";
1847
+ console.log(` ${chalk12.cyan(String(num).padStart(2, " "))}. ${projectIcon} ${chalk12.white(convo.title)}`);
1848
+ console.log(chalk12.gray(` ${sourceIcon} ${timeAgo} \xB7 ${convo.sender === "bob" ? "Bob" : "You"}: ${convo.lastMessage.slice(0, 60)}${convo.lastMessage.length > 60 ? "..." : ""}`));
1849
+ console.log("");
1850
+ });
1851
+ if (result.totalPages && result.totalPages > 1) {
1852
+ console.log(chalk12.gray(` Page ${result.page}/${result.totalPages} (${result.total} total)`));
1853
+ if (result.page < result.totalPages) {
1854
+ console.log(chalk12.gray(` Run: bob conversations --page ${result.page + 1}`));
1855
+ }
1856
+ }
1857
+ console.log(chalk12.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1858
+ console.log(chalk12.gray(" Join: bob conversations join"));
1859
+ console.log("");
1860
+ } catch (error) {
1861
+ spinner.stop();
1862
+ console.log("");
1863
+ console.log(chalk12.red(` \u274C ${error.message}`));
1864
+ console.log("");
1865
+ }
1866
+ });
1867
+ convosCmd.command("join").description("Pick a conversation to continue").option("-s, --search <query>", "Search first").action(async (options) => {
1868
+ const config = getConfig();
1869
+ if (!config.loggedIn || !config.authToken) {
1870
+ console.log("");
1871
+ console.log(chalk12.red(" \u274C Not logged in."));
1872
+ console.log(chalk12.gray(" Run `bob login` first."));
1873
+ console.log("");
1874
+ return;
1875
+ }
1876
+ const spinner = ora6({
1877
+ text: chalk12.cyan(" Loading conversations..."),
1878
+ spinner: "dots"
1879
+ }).start();
1880
+ try {
1881
+ const result = await callCloudFunction("listCLIConversations", {
1882
+ page: 1,
1883
+ limit: 15,
1884
+ search: options.search || null
1885
+ });
1886
+ spinner.stop();
1887
+ const conversations = result.conversations || [];
1888
+ if (conversations.length === 0) {
1889
+ console.log("");
1890
+ console.log(chalk12.yellow(" No conversations found."));
1891
+ console.log("");
1892
+ return;
1893
+ }
1894
+ console.log("");
1895
+ console.log(chalk12.bold(" \u{1F4AC} Select a Conversation"));
1896
+ console.log(chalk12.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1897
+ console.log("");
1898
+ conversations.forEach((convo, index) => {
1899
+ const num = index + 1;
1900
+ const timeAgo = convo.lastUpdated ? getTimeAgo(convo.lastUpdated) : "unknown";
1901
+ const sourceIcon = convo.source === "cli" ? "\u2328\uFE0F" : "\u{1F310}";
1902
+ const projectIcon = convo.hasProject ? "\u{1F4C1}" : " ";
1903
+ console.log(` ${chalk12.cyan(String(num).padStart(2, " "))}. ${projectIcon} ${chalk12.white(convo.title)}`);
1904
+ console.log(chalk12.gray(` ${sourceIcon} ${timeAgo}`));
1905
+ });
1906
+ console.log("");
1907
+ const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
1908
+ const answer = await new Promise((resolve2) => {
1909
+ rl.question(chalk12.cyan(" Select (1-" + conversations.length + ") or 0 to cancel: "), resolve2);
1910
+ });
1911
+ rl.close();
1912
+ const selection = parseInt(answer.trim());
1913
+ if (isNaN(selection) || selection === 0) {
1914
+ console.log(chalk12.gray(" Cancelled."));
1915
+ console.log("");
1916
+ return;
1917
+ }
1918
+ if (selection < 1 || selection > conversations.length) {
1919
+ console.log(chalk12.red(" \u274C Invalid selection."));
1920
+ console.log("");
1921
+ return;
1922
+ }
1923
+ const selected = conversations[selection - 1];
1924
+ setConfigValue("conversationId", selected.id);
1925
+ console.log("");
1926
+ console.log(chalk12.green(` \u2705 Joined: "${selected.title}"`));
1927
+ console.log(chalk12.gray(` Session ID: ${selected.id}`));
1928
+ console.log(chalk12.gray(" Your next `bob chat` message will continue this conversation."));
1929
+ console.log("");
1930
+ } catch (error) {
1931
+ spinner.stop();
1932
+ console.log("");
1933
+ console.log(chalk12.red(` \u274C ${error.message}`));
1934
+ console.log("");
1935
+ }
1936
+ });
1937
+ }
1938
+ function getTimeAgo(isoDate) {
1939
+ const now = Date.now();
1940
+ const then = new Date(isoDate).getTime();
1941
+ const diffMs = now - then;
1942
+ const diffMins = Math.floor(diffMs / 6e4);
1943
+ const diffHours = Math.floor(diffMs / 36e5);
1944
+ const diffDays = Math.floor(diffMs / 864e5);
1945
+ if (diffMins < 1) return "just now";
1946
+ if (diffMins < 60) return `${diffMins}m ago`;
1947
+ if (diffHours < 24) return `${diffHours}h ago`;
1948
+ if (diffDays < 7) return `${diffDays}d ago`;
1949
+ if (diffDays < 30) return `${Math.floor(diffDays / 7)}w ago`;
1950
+ return `${Math.floor(diffDays / 30)}mo ago`;
1951
+ }
1952
+
1953
+ // src/commands/fork.ts
1954
+ import chalk14 from "chalk";
1955
+
1956
+ // src/ui/animations/fork-split.ts
1957
+ import chalk13 from "chalk";
1958
+ var FRAME_DELAY_MS = 350;
1959
+ var FRAMES = [
1960
+ `
1961
+ \u{1F477} \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 \u{1F374} \u2501\u2501\u256E
1962
+ \u2503
1963
+ \u2570\u2501\u2501
1964
+ `,
1965
+ `
1966
+ \u{1F477} \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 \u{1F374} \u2501\u2501\u256E
1967
+ \u2503
1968
+ \u2570\u2501\u2501
1969
+ `,
1970
+ `
1971
+ \u{1F477} \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 \u{1F374} \u2501\u2501\u256E
1972
+ \u2503
1973
+ \u2570\u2501\u2501
1974
+ `,
1975
+ `
1976
+ \u{1F477} \u2501\u2501\u2501\u2501\u2501\u2501 \u{1F374} \u2501\u2501\u256E
1977
+ \u2503
1978
+ \u2570\u2501\u2501
1979
+ `,
1980
+ `
1981
+ \u{1F477} \u{1F374} \u2501\u2501\u2501\u256E
1982
+ \u2503
1983
+ \u2570\u2501\u2501
1984
+ `,
1985
+ `
1986
+ \u{1F477}\u{1F374}
1987
+ \u2571 \u2572
1988
+ \u2571 \u2572
1989
+ `,
1990
+ `
1991
+ \u{1F374}
1992
+ \u2571 \u2572
1993
+ \u2571 \u2572
1994
+ \u2502 \u2502
1995
+ \u25BC \u25BC
1996
+ `
1997
+ ];
1998
+ var FRAME_HEIGHT2 = 6;
1999
+ function startForkAnimation(parentTitle, forkTitle) {
2000
+ let running = true;
2001
+ for (let i = 0; i < FRAME_HEIGHT2 + 2; i++) {
2002
+ console.log("");
2003
+ }
2004
+ const run = async () => {
2005
+ for (const frame of FRAMES) {
2006
+ if (!running) return;
2007
+ renderFrame2(frame);
2008
+ await sleep3(FRAME_DELAY_MS);
2009
+ }
2010
+ let toggle = false;
2011
+ while (running) {
2012
+ if (!running) return;
2013
+ renderFrame2(FRAMES[toggle ? FRAMES.length - 1 : FRAMES.length - 2]);
2014
+ toggle = !toggle;
2015
+ await sleep3(600);
2016
+ }
2017
+ };
2018
+ run();
2019
+ return {
2020
+ stop: () => {
2021
+ running = false;
2022
+ setTimeout(() => {
2023
+ renderFinalFrame(parentTitle, forkTitle);
2024
+ }, 100);
2025
+ }
2026
+ };
2027
+ }
2028
+ function renderFrame2(frame) {
2029
+ const totalHeight = FRAME_HEIGHT2 + 2;
2030
+ process.stdout.write(`\x1B[${totalHeight}A`);
2031
+ for (let i = 0; i < totalHeight; i++) {
2032
+ process.stdout.write("\x1B[2K\n");
2033
+ }
2034
+ process.stdout.write(`\x1B[${totalHeight}A`);
2035
+ const lines = frame.split("\n").filter((l) => l.length > 0);
2036
+ for (const line of lines) {
2037
+ console.log(line);
2038
+ }
2039
+ for (let i = lines.length; i < FRAME_HEIGHT2; i++) {
2040
+ console.log("");
2041
+ }
2042
+ console.log(chalk13.magenta(" \u26A1 Fork initializing..."));
2043
+ console.log("");
2044
+ }
2045
+ function renderFinalFrame(parentTitle, forkTitle) {
2046
+ const totalHeight = FRAME_HEIGHT2 + 2;
2047
+ process.stdout.write(`\x1B[${totalHeight}A`);
2048
+ for (let i = 0; i < totalHeight; i++) {
2049
+ process.stdout.write("\x1B[2K\n");
2050
+ }
2051
+ process.stdout.write(`\x1B[${totalHeight}A`);
2052
+ console.log(chalk13.gray(" \u{1F374}"));
2053
+ console.log(chalk13.gray(" \u2571 \u2572"));
2054
+ console.log(` ${chalk13.green("\u25CB")} ${chalk13.gray(truncate(parentTitle, 18))} ${chalk13.magenta("\u26A1")} ${chalk13.bold(truncate(forkTitle, 18))}`);
2055
+ console.log(chalk13.gray(" \u2571 \u2572"));
2056
+ console.log(chalk13.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2057
+ console.log(chalk13.green(" \u2705 Fork created!"));
2058
+ console.log("");
2059
+ console.log("");
2060
+ }
2061
+ function truncate(text, max) {
2062
+ return text.length > max ? text.slice(0, max - 3) + "..." : text;
2063
+ }
2064
+ function sleep3(ms) {
2065
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
2066
+ }
2067
+
2068
+ // src/commands/fork.ts
2069
+ function registerForkCommand(program2) {
2070
+ program2.command("fork <title>").description("Fork the current conversation into a focused sub-project").action(async (title) => {
2071
+ const config = getConfig();
2072
+ if (!config.loggedIn || !config.authToken) {
2073
+ console.log("");
2074
+ console.log(chalk14.red(" \u274C Not logged in. Forks require Tier 3 (platform)."));
2075
+ console.log(chalk14.gray(" Run `bob login` to authenticate."));
2076
+ console.log("");
2077
+ return;
2078
+ }
2079
+ if (!config.conversationId) {
2080
+ console.log("");
2081
+ console.log(chalk14.red(" \u274C No active conversation to fork from."));
2082
+ console.log(chalk14.gray(" Start a conversation first with `bob chat`, or join one with `bob conversations join`."));
2083
+ console.log("");
2084
+ return;
2085
+ }
2086
+ const parentConvoId = config.conversationId;
2087
+ console.log("");
2088
+ console.log(chalk14.bold.magenta(` \u26A1 Forking: "${title}"`));
2089
+ console.log(chalk14.gray(` From: ${parentConvoId.slice(0, 24)}...`));
2090
+ console.log("");
2091
+ const forkPromise = callCloudFunction("createConversationFork", {
2092
+ parentConversationId: parentConvoId,
2093
+ forkTitle: title,
2094
+ userEmail: config.email,
2095
+ userId: config.uid
2096
+ });
2097
+ const animation = startForkAnimation("Parent", title);
2098
+ try {
2099
+ const result = await forkPromise;
2100
+ animation.stop();
2101
+ await new Promise((resolve2) => setTimeout(resolve2, 200));
2102
+ if (result?.conversationId) {
2103
+ setConfigValue("conversationId", result.conversationId);
2104
+ console.log("");
2105
+ console.log(chalk14.green(` \u2705 Fork created: "${title}"`));
2106
+ console.log(chalk14.gray(` Session: ${result.conversationId.slice(0, 24)}...`));
2107
+ console.log(chalk14.gray(" Your next `bob chat` message continues in this fork."));
2108
+ console.log(chalk14.gray(` \u{1F517} https://bobs-workshop.web.app/#/bobcodeassistant/${result.conversationId}`));
2109
+ console.log("");
2110
+ if (result.kickstartMessage) {
2111
+ console.log(chalk14.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2112
+ console.log(chalk14.bold.cyan(" \u{1F916} Bob:"));
2113
+ console.log("");
2114
+ for (const line of result.kickstartMessage.split("\n")) {
2115
+ console.log(` ${line}`);
2116
+ }
2117
+ console.log("");
2118
+ console.log(chalk14.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2119
+ console.log("");
2120
+ }
2121
+ if (result.keyPoints && result.keyPoints.length > 0) {
2122
+ console.log(chalk14.gray(" \u{1F4CB} Context carried forward:"));
2123
+ for (const point of result.keyPoints.slice(0, 4)) {
2124
+ console.log(chalk14.gray(` \u2022 ${point}`));
2125
+ }
2126
+ console.log("");
2127
+ }
2128
+ } else {
2129
+ console.log("");
2130
+ console.log(chalk14.red(" \u274C Fork failed \u2014 no conversation ID returned."));
2131
+ console.log("");
2132
+ }
2133
+ } catch (error) {
2134
+ animation.stop();
2135
+ await new Promise((resolve2) => setTimeout(resolve2, 200));
2136
+ console.log("");
2137
+ console.log(chalk14.red(` \u274C Fork failed: ${error.message}`));
2138
+ console.log("");
2139
+ }
2140
+ });
2141
+ program2.command("forks").description("List all forks of the current conversation").action(async () => {
2142
+ const config = getConfig();
2143
+ if (!config.loggedIn || !config.authToken) {
2144
+ console.log("");
2145
+ console.log(chalk14.red(" \u274C Not logged in."));
2146
+ console.log("");
2147
+ return;
2148
+ }
2149
+ if (!config.conversationId) {
2150
+ console.log("");
2151
+ console.log(chalk14.red(" \u274C No active conversation."));
2152
+ console.log("");
2153
+ return;
2154
+ }
2155
+ console.log("");
2156
+ console.log(chalk14.bold.magenta(" \u{1F500} Loading forks..."));
2157
+ try {
2158
+ const result = await callCloudFunction("listConversationForks", {
2159
+ conversationId: config.conversationId
2160
+ });
2161
+ const forks = result.forks || [];
2162
+ console.log("");
2163
+ console.log(chalk14.bold.magenta(" \u{1F500} Forks"));
2164
+ console.log(chalk14.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2165
+ if (forks.length === 0) {
2166
+ console.log(chalk14.gray(" No forks yet."));
2167
+ console.log(chalk14.gray(' Run `bob fork "title"` to create one.'));
2168
+ } else {
2169
+ for (const fork of forks) {
2170
+ console.log(` ${chalk14.magenta("\u26A1")} ${chalk14.white(fork.title || "Untitled")}`);
2171
+ console.log(chalk14.gray(` ${fork.summary?.slice(0, 60) || "No summary"}${fork.summary?.length > 60 ? "..." : ""}`));
2172
+ console.log(chalk14.gray(` ID: ${fork.forkConversationId?.slice(0, 24) || fork.id.slice(0, 24)}...`));
2173
+ console.log("");
2174
+ }
2175
+ }
2176
+ console.log(chalk14.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2177
+ console.log(chalk14.gray(" Join a fork: bob conversations join \u2192 select it"));
2178
+ console.log("");
2179
+ } catch (error) {
2180
+ console.log("");
2181
+ console.log(chalk14.red(` \u274C ${error.message}`));
2182
+ console.log("");
2183
+ }
2184
+ });
2185
+ }
2186
+
2187
+ // src/commands/analyse.ts
2188
+ import chalk15 from "chalk";
2189
+ import ora7 from "ora";
2190
+ import * as fs6 from "fs";
2191
+ import * as path7 from "path";
2192
+ var RED = chalk15.hex("#EF5350");
2193
+ var PURPLE = chalk15.hex("#AB47BC");
2194
+ var BLUE2 = chalk15.hex("#42A5F5");
2195
+ var TEAL = chalk15.hex("#26A69A");
2196
+ var AMBER3 = chalk15.hex("#FFAB00");
2197
+ var GRAY = chalk15.gray;
2198
+ var BORDER2 = chalk15.hex("#455A64");
2199
+ var BG_RED = chalk15.bgHex("#2D1111");
2200
+ var BG_PURPLE = chalk15.bgHex("#1A0D2B");
2201
+ var BG_BLUE = chalk15.bgHex("#0D1B2A");
2202
+ var BG_TEAL = chalk15.bgHex("#0D2420");
2203
+ function registerAnalyseCommand(program2) {
2204
+ program2.command("analyse").description("Analyse the current project for bugs, features, improvements, and upgrades").option("--results", "Show analysis dashboard or filtered list").option("--bugs", "Show bugs list (interactive)").option("--features", "Show features list (interactive)").option("--improvements", "Show improvements list (interactive)").option("--upgrades", "Show upgrades list (interactive)").option("--sort <method>", "Sort by: priority (default) or file").option("--search <query>", "Filter results by keyword").option("--status", "Show current analysis job status").option("--auto", "Auto-fix mode: Bob triages and MiniBob implements").option("--confidence <number>", "Confidence gate for auto-fix (default: 90)", "90").option("--priority <level>", "Priority gate for auto-fix: critical, high, medium, low (default: critical)", "critical").action(async (options) => {
2205
+ const config = getConfig();
2206
+ if (options.auto) {
2207
+ const { runAutoFix } = await import("./analyse-auto-OBCDWYWX.js");
2208
+ const category = options.bugs ? "bugs" : options.features ? "features" : options.improvements ? "improvements" : options.upgrades ? "upgrades" : void 0;
2209
+ await runAutoFix({
2210
+ category,
2211
+ confidence: parseInt(options.confidence || "90"),
2212
+ priority: options.priority || "critical"
2213
+ });
2214
+ return;
2215
+ }
2216
+ if (options.bugs || options.features || options.improvements || options.upgrades) {
2217
+ const { showInteractiveResults } = await import("./analyse-results-QSOD3KVC.js");
2218
+ const category = options.bugs ? "bugs" : options.features ? "features" : options.improvements ? "improvements" : "upgrades";
2219
+ await showInteractiveResults(config, category, options.sort, options.search);
2220
+ return;
2221
+ }
2222
+ if (options.results) {
2223
+ await showDashboard(config);
2224
+ return;
2225
+ }
2226
+ if (options.status) {
2227
+ await showStatus(config);
2228
+ return;
2229
+ }
2230
+ await runAnalysis(config);
2231
+ });
2232
+ }
2233
+ async function showDashboard(config) {
2234
+ const spinner = ora7({ text: chalk15.cyan(" Loading analysis results..."), spinner: "dots" }).start();
2235
+ try {
2236
+ let counts;
2237
+ if (config.tier === "platform" && config.provider !== "local" && config.loggedIn && config.conversationId) {
2238
+ const result = await callCloudFunction("getCLIAnalysisResults", {
2239
+ conversationId: config.conversationId,
2240
+ category: "all"
2241
+ });
2242
+ counts = result?.counts;
2243
+ } else {
2244
+ counts = loadLocalCounts();
2245
+ }
2246
+ spinner.stop();
2247
+ if (!counts) {
2248
+ console.log("");
2249
+ console.log(chalk15.yellow(" \u26A0\uFE0F No analysis results found."));
2250
+ console.log(GRAY(" Run `bob analyse` first to analyse your project."));
2251
+ console.log("");
2252
+ return;
2253
+ }
2254
+ renderDashboard(counts);
2255
+ } catch (error) {
2256
+ spinner.stop();
2257
+ console.log(chalk15.red(` \u274C ${error.message}`));
2258
+ console.log("");
2259
+ }
2260
+ }
2261
+ function renderDashboard(counts) {
2262
+ const total = counts.bugs + counts.features + counts.improvements + counts.upgrades;
2263
+ console.log("");
2264
+ console.log(BORDER2(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2566\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2566\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2566\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
2265
+ console.log(BORDER2(" \u2551") + AMBER3(" \u25C6 MINIBOB ANALYSIS COMPLETE") + GRAY(` ${total} pts`) + BORDER2(" \u2551"));
2266
+ console.log(BORDER2(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256C\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256C\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256C\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
2267
+ console.log(BORDER2(" \u2551") + BG_RED(" ") + BORDER2("\u2551") + BG_PURPLE(" ") + BORDER2("\u2551") + BG_BLUE(" ") + BORDER2("\u2551") + BG_TEAL(" ") + BORDER2("\u2551"));
2268
+ console.log(BORDER2(" \u2551") + BG_RED(` ${RED("\u{1F534} BUGS")} `) + BORDER2("\u2551") + BG_PURPLE(` ${PURPLE("\u{1F7E3} FEAT")} `) + BORDER2("\u2551") + BG_BLUE(` ${BLUE2("\u{1F535} OPTZ")} `) + BORDER2("\u2551") + BG_TEAL(` ${TEAL("\u{1F7E2} UPGR")} `) + BORDER2("\u2551"));
2269
+ const bugsStr = String(counts.bugs).padStart(4);
2270
+ const featStr = String(counts.features).padStart(4);
2271
+ const imprStr = String(counts.improvements).padStart(4);
2272
+ const upgrStr = String(counts.upgrades).padStart(4);
2273
+ console.log(BORDER2(" \u2551") + BG_RED(` ${RED(bugsStr)} `) + BORDER2("\u2551") + BG_PURPLE(` ${PURPLE(featStr)} `) + BORDER2("\u2551") + BG_BLUE(` ${BLUE2(imprStr)} `) + BORDER2("\u2551") + BG_TEAL(` ${TEAL(upgrStr)} `) + BORDER2("\u2551"));
2274
+ console.log(BORDER2(" \u2551") + BG_RED(" ") + BORDER2("\u2551") + BG_PURPLE(" ") + BORDER2("\u2551") + BG_BLUE(" ") + BORDER2("\u2551") + BG_TEAL(" ") + BORDER2("\u2551"));
2275
+ console.log(BORDER2(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2569\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2569\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2569\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
2276
+ console.log(BORDER2(" \u2551") + chalk15.white(` ${total} POINTS IDENTIFIED`) + BORDER2(" \u2551"));
2277
+ console.log(BORDER2(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
2278
+ console.log("");
2279
+ console.log(GRAY(" View details (interactive):"));
2280
+ console.log(GRAY(" bob analyse --results --bugs"));
2281
+ console.log(GRAY(" bob analyse --results --features"));
2282
+ console.log(GRAY(" bob analyse --results --improvements"));
2283
+ console.log(GRAY(" bob analyse --results --upgrades"));
2284
+ console.log("");
2285
+ console.log(GRAY(" Auto-fix:"));
2286
+ console.log(GRAY(" bob analyse --auto"));
2287
+ console.log(GRAY(" bob analyse --auto --bugs --confidence 80"));
2288
+ console.log(GRAY(" bob analyse --auto --priority high"));
2289
+ console.log("");
2290
+ }
2291
+ async function showStatus(config) {
2292
+ if (!config.loggedIn || !config.authToken || !config.conversationId) {
2293
+ console.log("");
2294
+ console.log(chalk15.yellow(" \u26A0\uFE0F Status check requires Tier 3 with an active conversation."));
2295
+ console.log("");
2296
+ return;
2297
+ }
2298
+ const spinner = ora7({ text: chalk15.cyan(" Checking analysis status..."), spinner: "dots" }).start();
2299
+ try {
2300
+ const result = await callCloudFunction("getCLIAnalysisResults", {
2301
+ conversationId: config.conversationId,
2302
+ action: "status"
2303
+ });
2304
+ spinner.stop();
2305
+ if (result?.status) {
2306
+ console.log("");
2307
+ console.log(AMBER3(` \u25C6 Analysis Status: ${result.status.toUpperCase()}`));
2308
+ if (result.progress) {
2309
+ const pct = Math.round(result.progress.completed / result.progress.total * 100);
2310
+ const barLen = 30;
2311
+ const filled = Math.round(pct / 100 * barLen);
2312
+ let barColor;
2313
+ if (pct < 25) barColor = chalk15.red;
2314
+ else if (pct < 50) barColor = chalk15.hex("#FF8C00");
2315
+ else if (pct < 75) barColor = chalk15.yellow;
2316
+ else barColor = chalk15.green;
2317
+ const bar = barColor("\u2588".repeat(filled)) + GRAY("\u2591".repeat(barLen - filled));
2318
+ console.log(` [${bar}] ${result.progress.completed}/${result.progress.total} (${pct}%)`);
2319
+ }
2320
+ console.log("");
2321
+ } else {
2322
+ console.log("");
2323
+ console.log(GRAY(" No active analysis job found."));
2324
+ console.log("");
2325
+ }
2326
+ } catch (error) {
2327
+ spinner.stop();
2328
+ console.log(chalk15.red(` \u274C ${error.message}`));
2329
+ console.log("");
2330
+ }
2331
+ }
2332
+ async function runAnalysis(config) {
2333
+ const cwd = process.cwd();
2334
+ const projectName = getProjectName(cwd);
2335
+ console.log("");
2336
+ console.log(chalk15.bold.cyan(` \u26A1 Analysing project: ${projectName}`));
2337
+ console.log(GRAY(` \u{1F4C1} ${cwd}`));
2338
+ console.log(GRAY(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2339
+ console.log("");
2340
+ if (config.tier === "platform" && config.provider !== "local" && config.loggedIn && config.conversationId) {
2341
+ const spinner = ora7({ text: chalk15.cyan(" Triggering platform analysis..."), spinner: "dots" }).start();
2342
+ try {
2343
+ const result = await callCloudFunction("analyzeProjectWorkspace", {
2344
+ conversationId: config.conversationId
2345
+ });
2346
+ spinner.stop();
2347
+ if (result?.success) {
2348
+ console.log(chalk15.green(` \u2705 Analysis job created: ${result.jobId}`));
2349
+ console.log(GRAY(" Run `bob analyse --status` to check progress."));
2350
+ console.log(GRAY(" Run `bob analyse --results` when complete."));
2351
+ } else {
2352
+ console.log(chalk15.red(` \u274C ${result?.message || "Failed to start analysis."}`));
2353
+ }
2354
+ console.log("");
2355
+ } catch (error) {
2356
+ spinner.stop();
2357
+ console.log(chalk15.red(` \u274C ${error.message}`));
2358
+ console.log("");
2359
+ }
2360
+ return;
2361
+ }
2362
+ if (config.provider !== "local" || !config.localEndpoint) {
2363
+ console.log(chalk15.red(" \u274C Local analysis requires a local model."));
2364
+ console.log(GRAY(" Run `bob config set provider local`"));
2365
+ console.log(GRAY(" Run `bob config set localEndpoint http://127.0.0.1:11434/api/chat`"));
2366
+ console.log("");
2367
+ return;
2368
+ }
2369
+ const summaries = loadSummaries(cwd);
2370
+ if (!summaries || Object.keys(summaries).length === 0) {
2371
+ console.log(chalk15.yellow(" \u26A0\uFE0F Project not indexed. Run `bob index` first."));
2372
+ console.log("");
2373
+ return;
2374
+ }
2375
+ const dependencies = loadDependencies(cwd) || {};
2376
+ const files = Object.keys(summaries);
2377
+ console.log(GRAY(` Found ${files.length} indexed files. Starting deep analysis...`));
2378
+ console.log("");
2379
+ console.log("");
2380
+ console.log("");
2381
+ console.log("");
2382
+ const { analysisDir } = ensureProjectStructure(cwd);
2383
+ const resultsDir = path7.join(analysisDir, "results");
2384
+ if (!fs6.existsSync(resultsDir)) fs6.mkdirSync(resultsDir, { recursive: true });
2385
+ let completed = 0;
2386
+ const allResults = {};
2387
+ for (const filePath of files) {
2388
+ const absolutePath = path7.join(cwd, filePath);
2389
+ let content;
2390
+ try {
2391
+ content = fs6.readFileSync(absolutePath, "utf-8");
2392
+ } catch (error) {
2393
+ console.error(chalk15.red(` \u274C Could not read file ${filePath}: ${error.message}`));
2394
+ completed++;
2395
+ continue;
2396
+ }
2397
+ if (content.length > 3e4) {
2398
+ completed++;
2399
+ printProgress2(completed, files.length, filePath, "(skipped \u2014 too large)");
2400
+ continue;
2401
+ }
2402
+ const fileDeps = dependencies[filePath] || [];
2403
+ let depContext = "";
2404
+ if (fileDeps.length > 0) {
2405
+ depContext = `
2406
+ RELATED FILES:
2407
+ ${fileDeps.map((d) => `- ${d}: ${summaries[d] || "unknown"}`).join("\n")}
2408
+ `;
2409
+ }
2410
+ const analysisPrompt = `You are the Lead QA Engineer on this project. Your job is to perform a thorough, production-grade code review.
2411
+
2412
+ For each issue you find, you MUST provide:
2413
+ - A CLEAR, SPECIFIC title (not generic \u2014 name the exact problem)
2414
+ - A DETAILED description explaining WHY this is a problem and WHAT the impact is
2415
+ - A SPECIFIC implementation instruction \u2014 exact steps to fix it, referencing actual function/variable names from the code
2416
+ - An honest priority based on real-world impact
2417
+
2418
+ PRIORITY DEFINITIONS:
2419
+ - critical: Will cause crashes, data loss, security vulnerabilities, or breaks core functionality
2420
+ - high: Causes bugs in normal usage, performance degradation, or makes code unmaintainable
2421
+ - medium: Code smell, minor inefficiency, or could cause issues under edge cases
2422
+ - low: Style improvements, minor optimizations, or nice-to-haves
2423
+
2424
+ CONFIDENCE RUBRIC (you will use this later during triage):
2425
+ Your confidence should reflect: "How certain am I that implementing this fix will NOT break anything AND will ACTUALLY contribute positively to the project?"
2426
+ - 95-100%: Fix is 1-5 lines, explicit, zero side effects, purely additive
2427
+ - 85-94%: Clear fix, well-scoped, minimal risk, touches isolated logic
2428
+ - 75-84%: Good fix but touches shared logic or has minor behavioral implications
2429
+ - <75%: Requires judgment, structural changes, or has unpredictable side effects
2430
+
2431
+ DO NOT include vague suggestions like "improve error handling" without specifying EXACTLY what to change.
2432
+ DO NOT include items without clear implementation steps.
2433
+ Every suggestion must be actionable by a junior engineer reading only your instructions.
2434
+
2435
+ Respond with ONLY a JSON object:
2436
+ {
2437
+ "bugs": [{"title": "Specific bug name", "description": "Detailed explanation of the problem and its impact", "priority": "critical|high|medium|low", "implementation": "Exact steps: 1. In function X, change Y to Z. 2. Add error check for..."}],
2438
+ "features": [{"title": "...", "description": "...", "priority": "...", "implementation": "..."}],
2439
+ "improvements": [{"title": "...", "description": "...", "priority": "...", "implementation": "..."}],
2440
+ "upgrades": [{"title": "...", "description": "...", "priority": "...", "implementation": "..."}]
2441
+ }
2442
+
2443
+ Be thorough but practical. Quality over quantity. Only list GENUINE issues with REAL impact.
2444
+ ${depContext}
2445
+ FILE: ${filePath}
2446
+ ${content}`;
2447
+ try {
2448
+ const messages = [
2449
+ { role: "system", content: "You are the Lead QA Engineer. Respond with ONLY valid JSON. Every suggestion must have a specific title, detailed description, and actionable implementation steps. No vague or generic items. Quality over quantity." },
2450
+ { role: "user", content: analysisPrompt }
2451
+ ];
2452
+ const response = await callLocalModel(config.localEndpoint, messages);
2453
+ const jsonMatch = response.match(/\{[\s\S]*\}/);
2454
+ if (jsonMatch) {
2455
+ const parsed = JSON.parse(jsonMatch[0]);
2456
+ for (const cat of ["bugs", "features", "improvements", "upgrades"]) {
2457
+ if (parsed[cat]) {
2458
+ parsed[cat] = parsed[cat].map((item) => ({ ...item, filePath }));
2459
+ }
2460
+ }
2461
+ allResults[filePath] = parsed;
2462
+ const counts = `${parsed.bugs?.length || 0}B ${parsed.features?.length || 0}F ${parsed.improvements?.length || 0}I ${parsed.upgrades?.length || 0}U`;
2463
+ printProgress2(completed + 1, files.length, filePath, counts);
2464
+ } else {
2465
+ printProgress2(completed + 1, files.length, filePath, "(no results)");
2466
+ }
2467
+ } catch {
2468
+ printProgress2(completed + 1, files.length, filePath, "(error)");
2469
+ }
2470
+ completed++;
2471
+ }
2472
+ fs6.writeFileSync(path7.join(resultsDir, "analysis.json"), JSON.stringify(allResults, null, 2));
2473
+ let totalBugs = 0, totalFeatures = 0, totalImprovements = 0, totalUpgrades = 0;
2474
+ for (const fileResults of Object.values(allResults)) {
2475
+ const r = fileResults;
2476
+ totalBugs += r.bugs?.length || 0;
2477
+ totalFeatures += r.features?.length || 0;
2478
+ totalImprovements += r.improvements?.length || 0;
2479
+ totalUpgrades += r.upgrades?.length || 0;
2480
+ }
2481
+ fs6.writeFileSync(path7.join(resultsDir, "counts.json"), JSON.stringify({
2482
+ bugs: totalBugs,
2483
+ features: totalFeatures,
2484
+ improvements: totalImprovements,
2485
+ upgrades: totalUpgrades
2486
+ }, null, 2));
2487
+ console.log("");
2488
+ console.log("");
2489
+ console.log(chalk15.bold.green(` \u2705 Analysis complete: ${projectName}`));
2490
+ console.log(GRAY(` \u{1F4BE} Saved to: ~/.bob/projects/${projectName}/analysis/results/`));
2491
+ console.log(GRAY(" Run `bob analyse --results` to view the dashboard."));
2492
+ console.log(GRAY(" Run `bob analyse --auto` for auto-fix mode."));
2493
+ console.log("");
2494
+ }
2495
+ function loadLocalCounts() {
2496
+ const cwd = process.cwd();
2497
+ const { analysisDir } = ensureProjectStructure(cwd);
2498
+ const countsPath = path7.join(analysisDir, "results", "counts.json");
2499
+ if (!fs6.existsSync(countsPath)) return null;
2500
+ return JSON.parse(fs6.readFileSync(countsPath, "utf-8"));
2501
+ }
2502
+ function printProgress2(completed, total, filePath, info) {
2503
+ const percent = completed / total;
2504
+ const barLength = 30;
2505
+ const filled = Math.round(percent * barLength);
2506
+ let barColor;
2507
+ if (percent < 0.25) barColor = chalk15.red;
2508
+ else if (percent < 0.5) barColor = chalk15.hex("#FF8C00");
2509
+ else if (percent < 0.75) barColor = chalk15.yellow;
2510
+ else barColor = chalk15.green;
2511
+ const filledBar = barColor("\u2588".repeat(filled));
2512
+ const emptyBar = GRAY("\u2591".repeat(barLength - filled));
2513
+ const percentText = barColor(`${Math.round(percent * 100)}%`);
2514
+ process.stdout.write("\x1B[2K\x1B[1A\x1B[2K\x1B[1A\x1B[2K\x1B[1A\x1B[2K\r");
2515
+ console.log(` ${chalk15.cyan("\u26A1")} Analysing [${filledBar}${emptyBar}] ${completed}/${total} ${percentText}`);
2516
+ console.log(chalk15.green(` \u2705 ${filePath}`));
2517
+ console.log(GRAY(` ${info}`));
2518
+ console.log("");
2519
+ }
2520
+
2521
+ // src/commands/autonomy.ts
2522
+ import chalk16 from "chalk";
2523
+ import ora8 from "ora";
2524
+ import * as readline5 from "readline";
2525
+ import simpleGit2 from "simple-git";
2526
+ import * as fs7 from "fs";
2527
+ import * as path8 from "path";
2528
+ var RED2 = chalk16.hex("#EF5350");
2529
+ var GREEN3 = chalk16.hex("#66BB6A");
2530
+ var AMBER4 = chalk16.hex("#FFAB00");
2531
+ var BLUE3 = chalk16.hex("#42A5F5");
2532
+ var GRAY2 = chalk16.gray;
2533
+ var BORDER3 = chalk16.hex("#455A64");
2534
+ var CYAN = chalk16.cyan;
2535
+ function registerAutonomyCommand(program2) {
2536
+ program2.command("autonomy").description("Launch autonomous repair mode \u2014 MiniBob fixes all analysed issues").option("--status", "Check current autonomy run progress (Tier 3)").option("--stop", "Stop the current autonomy run (Tier 3)").option("--category <cat>", "Limit to: bugs, features, improvements, upgrades").option("--priority <level>", "Minimum priority: critical, high, medium, low (default: high)", "high").option("--no-push", "Skip git push after completion").action(async (options) => {
2537
+ const config = getConfig();
2538
+ if (options.status) {
2539
+ await showAutonomyStatus(config);
2540
+ return;
2541
+ }
2542
+ if (options.stop) {
2543
+ console.log(chalk16.yellow(" \u26A0\uFE0F Stop command not yet implemented for Tier 3."));
2544
+ return;
2545
+ }
2546
+ if (config.tier === "platform" && config.provider !== "local" && config.loggedIn && config.conversationId) {
2547
+ await runTier3Autonomy(config);
2548
+ } else {
2549
+ await runTier1Autonomy(config, options);
2550
+ }
2551
+ });
2552
+ }
2553
+ async function runTier3Autonomy(config) {
2554
+ console.log("");
2555
+ console.log(chalk16.bold.cyan(" \u26A1 MiniBob Autonomy Mode (Platform)"));
2556
+ console.log(GRAY2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2557
+ console.log(GRAY2(` \u{1F4E1} Conversation: ${config.conversationId?.slice(0, 24)}...`));
2558
+ console.log(GRAY2(` \u{1F517} https://bobs-workshop.web.app/#/bobcodeassistant/${config.conversationId}`));
2559
+ console.log(GRAY2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2560
+ console.log("");
2561
+ const spinner = ora8({ text: CYAN(" Igniting autonomy workers..."), spinner: "dots" }).start();
2562
+ try {
2563
+ const result = await callCloudFunction("startMiniBobAutonomy", {
2564
+ conversationId: config.conversationId,
2565
+ proxyEmail: null
2566
+ });
2567
+ spinner.stop();
2568
+ if (!result?.success) {
2569
+ console.log(RED2(` \u274C ${result?.message || "Failed to start autonomy."}`));
2570
+ return;
2571
+ }
2572
+ console.log(GREEN3(" \u2705 Autonomy loop ignited!"));
2573
+ console.log(GRAY2(" Streaming progress..."));
2574
+ console.log("");
2575
+ } catch (error) {
2576
+ spinner.stop();
2577
+ console.log(RED2(` \u274C ${error.message}`));
2578
+ return;
2579
+ }
2580
+ let lastTimestamp = (/* @__PURE__ */ new Date()).toISOString();
2581
+ let running = true;
2582
+ let tasksDone = 0;
2583
+ let totalTasks = 0;
2584
+ console.log(GRAY2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2585
+ console.log(GRAY2(" Press Ctrl+C to stop streaming (workers continue in background)"));
2586
+ console.log(GRAY2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2587
+ console.log("");
2588
+ process.on("SIGINT", () => {
2589
+ running = false;
2590
+ console.log("");
2591
+ console.log(GRAY2(" \u{1F4E1} Stopped streaming. Workers continue in the background."));
2592
+ console.log(GRAY2(` Check progress: bob autonomy --status`));
2593
+ console.log("");
2594
+ process.exit(0);
2595
+ });
2596
+ while (running) {
2597
+ try {
2598
+ const updates = await callCloudFunction("getCLITerminalUpdates", {
2599
+ conversationId: config.conversationId,
2600
+ since: lastTimestamp
2601
+ });
2602
+ if (updates?.lines && updates.lines.length > 0) {
2603
+ for (const line of updates.lines) {
2604
+ const text = line.text || "";
2605
+ const type = line.type || "system";
2606
+ if (text.includes("[ACTION:AUTONOMY_TICKER:")) {
2607
+ const parts = text.match(/\[ACTION:AUTONOMY_TICKER:(\d+):(\d+):(\d+):(\d+):(\d+):(\d+):(\d+)\]/);
2608
+ if (parts) {
2609
+ const bugs = parseInt(parts[2]);
2610
+ const features = parseInt(parts[3]);
2611
+ const improvements = parseInt(parts[4]);
2612
+ const upgrades = parseInt(parts[5]);
2613
+ const tokens = parseInt(parts[6]);
2614
+ totalTasks = parseInt(parts[7]);
2615
+ tasksDone = bugs + features + improvements + upgrades;
2616
+ renderTickerHUD(tasksDone, totalTasks, bugs, features, improvements, upgrades, tokens);
2617
+ }
2618
+ continue;
2619
+ }
2620
+ if (text.includes("[ACTION:GITHUB_PUSH_REQUEST:")) {
2621
+ console.log("");
2622
+ console.log(GREEN3(" \u2705 All tasks complete!"));
2623
+ console.log(AMBER4(" \u{1F4E4} MiniBob wants to push to GitHub."));
2624
+ const rl = readline5.createInterface({ input: process.stdin, output: process.stdout });
2625
+ const answer = await new Promise((resolve2) => {
2626
+ rl.question(CYAN(" Approve push? (y/n): "), resolve2);
2627
+ });
2628
+ rl.close();
2629
+ if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
2630
+ try {
2631
+ await callCloudFunction("commitAndPushChanges", { conversationId: config.conversationId });
2632
+ console.log(GREEN3(" \u2705 Pushed to GitHub!"));
2633
+ } catch (pushErr) {
2634
+ console.log(RED2(` \u274C Push failed: ${pushErr.message}`));
2635
+ }
2636
+ } else {
2637
+ console.log(GRAY2(" Push skipped. You can push manually later."));
2638
+ }
2639
+ running = false;
2640
+ continue;
2641
+ }
2642
+ if (text.includes("ALL TASKS COMPLETE")) {
2643
+ running = false;
2644
+ }
2645
+ let lineColor;
2646
+ if (type === "stderr") lineColor = RED2;
2647
+ else if (type === "stdout") lineColor = GREEN3;
2648
+ else lineColor = GRAY2;
2649
+ console.log(lineColor(` ${text}`));
2650
+ lastTimestamp = line.timestamp || lastTimestamp;
2651
+ }
2652
+ }
2653
+ } catch (pollError) {
2654
+ }
2655
+ if (running) {
2656
+ await new Promise((resolve2) => setTimeout(resolve2, 2500));
2657
+ }
2658
+ }
2659
+ console.log("");
2660
+ console.log(BORDER3(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
2661
+ console.log(BORDER3(" \u2551") + AMBER4(" \u25C6 AUTONOMY SESSION COMPLETE"));
2662
+ console.log(BORDER3(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
2663
+ console.log(BORDER3(" \u2551") + GREEN3(` \u2705 Tasks completed: ${tasksDone}/${totalTasks}`));
2664
+ console.log(BORDER3(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
2665
+ console.log("");
2666
+ }
2667
+ async function runTier1Autonomy(config, options) {
2668
+ if (config.provider !== "local" || !config.localEndpoint) {
2669
+ console.log(RED2(" \u274C Local autonomy requires a local model."));
2670
+ console.log(GRAY2(" Run `bob config set provider local`"));
2671
+ console.log(GRAY2(" Run `bob config set localEndpoint http://127.0.0.1:11434/api/chat`"));
2672
+ return;
2673
+ }
2674
+ const categories = options.category ? [options.category] : ["bugs", "features", "improvements", "upgrades"];
2675
+ const priorityGate = options.priority || "high";
2676
+ const shouldPush = options.push !== false;
2677
+ console.log("");
2678
+ console.log(chalk16.bold.cyan(" \u26A1 MiniBob Autonomy Mode (Local)"));
2679
+ console.log(GRAY2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2680
+ console.log(GRAY2(` Priority gate: ${priorityGate}+`));
2681
+ console.log(GRAY2(` Categories: ${categories.join(", ")}`));
2682
+ console.log(GRAY2(` Git push: ${shouldPush ? "enabled" : "disabled"}`));
2683
+ console.log(GRAY2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2684
+ console.log("");
2685
+ let allSuggestions = [];
2686
+ for (const cat of categories) {
2687
+ allSuggestions.push(...loadLocalSuggestions(cat));
2688
+ }
2689
+ const priorityOrder = ["critical", "high", "medium", "low"];
2690
+ const gateIndex = priorityOrder.indexOf(priorityGate.toLowerCase());
2691
+ if (gateIndex >= 0) {
2692
+ allSuggestions = allSuggestions.filter((s) => {
2693
+ const idx = priorityOrder.indexOf(s.priority?.toLowerCase());
2694
+ return idx >= 0 && idx <= gateIndex;
2695
+ });
2696
+ }
2697
+ if (allSuggestions.length === 0) {
2698
+ console.log(GREEN3(" \u2705 No pending tasks. Project is clean!"));
2699
+ return;
2700
+ }
2701
+ console.log(GRAY2(` Found ${allSuggestions.length} tasks to process.`));
2702
+ console.log("");
2703
+ const workQueue = allSuggestions.map((s) => ({
2704
+ suggestion: s,
2705
+ status: "pending"
2706
+ }));
2707
+ renderLocalTodoList(workQueue);
2708
+ let fixed = 0;
2709
+ let failed = 0;
2710
+ const fixedFiles = [];
2711
+ for (let i = 0; i < workQueue.length; i++) {
2712
+ const task = workQueue[i];
2713
+ task.status = "working";
2714
+ renderLocalTodoList(workQueue);
2715
+ const success = await implementLocalTask(task.suggestion, config.localEndpoint);
2716
+ task.status = success ? "done" : "failed";
2717
+ if (success) {
2718
+ fixed++;
2719
+ fixedFiles.push(task.suggestion.filePath);
2720
+ const suggestionIndex = parseInt(task.suggestion.id?.split("_").pop() || "0");
2721
+ const category = detectLocalCategory(task.suggestion);
2722
+ markSuggestionStatus(task.suggestion.filePath, suggestionIndex, category, "implemented", {
2723
+ confidence: 100,
2724
+ reason: "MiniBob autonomy",
2725
+ implementedBy: "minibob-local-autonomy"
2726
+ });
2727
+ } else {
2728
+ failed++;
2729
+ }
2730
+ renderLocalTodoList(workQueue);
2731
+ }
2732
+ console.log("");
2733
+ console.log("");
2734
+ console.log(BORDER3(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
2735
+ console.log(BORDER3(" \u2551") + AMBER4(" \u25C6 MINIBOB AUTONOMY REPORT"));
2736
+ console.log(BORDER3(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
2737
+ console.log(BORDER3(" \u2551") + GREEN3(` \u2705 Fixed: ${fixed} files`));
2738
+ if (failed > 0) {
2739
+ console.log(BORDER3(" \u2551") + RED2(` \u274C Failed: ${failed} files`));
2740
+ }
2741
+ console.log(BORDER3(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
2742
+ console.log("");
2743
+ if (shouldPush && fixed > 0) {
2744
+ const git = simpleGit2(process.cwd());
2745
+ const isRepo = await git.checkIsRepo();
2746
+ if (isRepo) {
2747
+ console.log(CYAN(" \u{1F4E4} Committing and pushing to git..."));
2748
+ try {
2749
+ await git.add(".");
2750
+ const commitMessage = `MiniBob Autonomy: Fixed ${fixed} issue(s)
2751
+
2752
+ Files modified:
2753
+ ${fixedFiles.map((f) => `- ${f}`).join("\n")}
2754
+
2755
+ Autonomous repair by Bob's CLI.`;
2756
+ await git.commit(commitMessage);
2757
+ const branch = (await git.branchLocal()).current;
2758
+ try {
2759
+ await git.push("origin", branch);
2760
+ } catch (pushErr) {
2761
+ if (pushErr.message?.includes("no upstream")) {
2762
+ await git.push(["--set-upstream", "origin", branch]);
2763
+ } else {
2764
+ throw pushErr;
2765
+ }
2766
+ }
2767
+ console.log(GREEN3(` \u2705 Pushed to ${branch}!`));
2768
+ console.log(GRAY2(` Commit: MiniBob Autonomy: Fixed ${fixed} issue(s)`));
2769
+ } catch (gitErr) {
2770
+ console.log(RED2(` \u274C Git push failed: ${gitErr.message}`));
2771
+ console.log(GRAY2(' Files are saved locally. Push manually with `bob push "message"`.'));
2772
+ }
2773
+ } else {
2774
+ console.log(GRAY2(" Not a git repo. Files saved locally only."));
2775
+ }
2776
+ }
2777
+ console.log("");
2778
+ console.log(GRAY2(" \u{1F4E6} All original files backed up to .bob-backups/"));
2779
+ console.log("");
2780
+ }
2781
+ async function showAutonomyStatus(config) {
2782
+ if (!config.loggedIn || !config.conversationId) {
2783
+ console.log(chalk16.yellow(" \u26A0\uFE0F Status requires Tier 3 with an active conversation."));
2784
+ return;
2785
+ }
2786
+ const spinner = ora8({ text: CYAN(" Checking autonomy status..."), spinner: "dots" }).start();
2787
+ try {
2788
+ const result = await callCloudFunction("getCLITerminalUpdates", {
2789
+ conversationId: config.conversationId,
2790
+ since: new Date(Date.now() - 6e4).toISOString(),
2791
+ // Last 60 seconds
2792
+ limit: 5
2793
+ });
2794
+ spinner.stop();
2795
+ if (result?.lines && result.lines.length > 0) {
2796
+ console.log("");
2797
+ console.log(AMBER4(" \u25C6 Recent Autonomy Activity:"));
2798
+ console.log(GRAY2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2799
+ for (const line of result.lines) {
2800
+ console.log(GRAY2(` ${line.text}`));
2801
+ }
2802
+ console.log("");
2803
+ } else {
2804
+ console.log("");
2805
+ console.log(GRAY2(" No recent autonomy activity."));
2806
+ console.log("");
2807
+ }
2808
+ } catch (error) {
2809
+ spinner.stop();
2810
+ console.log(RED2(` \u274C ${error.message}`));
2811
+ }
2812
+ }
2813
+ async function implementLocalTask(suggestion, endpoint) {
2814
+ const fileContent = readFileContent(suggestion.filePath);
2815
+ if (!fileContent) return false;
2816
+ const prompt = `You are MiniBob \u2014 a junior engineer making SURGICAL code fixes under strict supervision.
2817
+
2818
+ CURRENT FILE: ${suggestion.filePath}
2819
+ ${fileContent}
2820
+
2821
+ CHANGE TO IMPLEMENT:
2822
+ Title: ${suggestion.title || "Fix"}
2823
+ Description: ${suggestion.description}
2824
+ Implementation Instructions: ${suggestion.implementation || "Apply the fix described above."}
2825
+
2826
+ RULES (CRITICAL \u2014 VIOLATION = REJECTED):
2827
+ - Return ONLY valid source code. No markdown, no code fences, no \`\`\`, no explanation text.
2828
+ - Start the FIRST line with: // File: ${suggestion.filePath}
2829
+ - PRESERVE ALL existing imports exactly as they are.
2830
+ - PRESERVE ALL existing exports exactly as they are.
2831
+ - PRESERVE existing code structure, indentation, patterns, naming conventions.
2832
+ - Make the MINIMUM change necessary. Touch NOTHING else.
2833
+ - Do NOT refactor, reorganize, or "improve" unrelated code.
2834
+ - Do NOT add comments explaining what you changed.
2835
+ - If unsure, return the file UNCHANGED.
2836
+
2837
+ Return the complete file content now:`;
2838
+ try {
2839
+ const messages = [
2840
+ { role: "system", content: "You are MiniBob making SURGICAL fixes. Return ONLY valid source code. NO markdown. NO code fences. Start with // File: comment. MINIMUM change only." },
2841
+ { role: "user", content: prompt }
2842
+ ];
2843
+ const response = await callLocalModel(endpoint, messages);
2844
+ const lines = response.split("\n");
2845
+ const firstLine = lines[0].trim();
2846
+ let newContent;
2847
+ if (firstLine.match(/^\/\/\s*(File:)?\s*/)) {
2848
+ newContent = lines.slice(1).join("\n").trim();
2849
+ } else {
2850
+ newContent = response.trim();
2851
+ }
2852
+ if (newContent.includes("```") || newContent.includes("## ") || newContent.startsWith("Here") || newContent.startsWith("I have") || newContent.startsWith("Sure")) {
2853
+ return false;
2854
+ }
2855
+ if (newContent.length < fileContent.length * 0.5) {
2856
+ return false;
2857
+ }
2858
+ const originalExports = fileContent.match(/export\s+(function|class|const|interface|type|async\s+function)\s+\w+/g) || [];
2859
+ for (const exp of originalExports) {
2860
+ const exportName = exp.split(/\s+/).pop();
2861
+ if (!newContent.includes(exportName)) {
2862
+ return false;
2863
+ }
2864
+ }
2865
+ const absolutePath = path8.join(process.cwd(), suggestion.filePath);
2866
+ const backupDir = path8.join(process.cwd(), ".bob-backups");
2867
+ if (!fs7.existsSync(backupDir)) fs7.mkdirSync(backupDir, { recursive: true });
2868
+ if (fs7.existsSync(absolutePath)) {
2869
+ const timestamp = Date.now();
2870
+ const backupName = suggestion.filePath.replace(/[\/\\]/g, "_") + `.${timestamp}.bak`;
2871
+ fs7.copyFileSync(absolutePath, path8.join(backupDir, backupName));
2872
+ }
2873
+ const dir = path8.dirname(absolutePath);
2874
+ if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
2875
+ fs7.writeFileSync(absolutePath, newContent, "utf-8");
2876
+ return true;
2877
+ } catch {
2878
+ return false;
2879
+ }
2880
+ }
2881
+ function detectLocalCategory(suggestion) {
2882
+ const cwd = process.cwd();
2883
+ const projectName = path8.basename(cwd);
2884
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
2885
+ const analysisPath = path8.join(homeDir, ".bob", "projects", projectName, "analysis", "results", "analysis.json");
2886
+ if (!fs7.existsSync(analysisPath)) return "bugs";
2887
+ const allResults = JSON.parse(fs7.readFileSync(analysisPath, "utf-8"));
2888
+ const fileResults = allResults[suggestion.filePath];
2889
+ if (!fileResults) return "bugs";
2890
+ for (const cat of ["bugs", "features", "improvements", "upgrades"]) {
2891
+ const items = fileResults[cat] || [];
2892
+ for (const item of items) {
2893
+ if (item.title === suggestion.title && item.description === suggestion.description) return cat;
2894
+ }
2895
+ }
2896
+ return "bugs";
2897
+ }
2898
+ function renderTickerHUD(done, total, bugs, features, improvements, upgrades, tokens) {
2899
+ const percent = total > 0 ? done / total : 0;
2900
+ const barLen = 30;
2901
+ const filled = Math.round(percent * barLen);
2902
+ let barColor;
2903
+ if (percent < 0.25) barColor = chalk16.red;
2904
+ else if (percent < 0.5) barColor = chalk16.hex("#FF8C00");
2905
+ else if (percent < 0.75) barColor = chalk16.yellow;
2906
+ else barColor = chalk16.green;
2907
+ const bar = barColor("\u2588".repeat(filled)) + GRAY2("\u2591".repeat(barLen - filled));
2908
+ console.log(` \u26A1 [${bar}] ${done}/${total} ${barColor(Math.round(percent * 100) + "%")}`);
2909
+ console.log(GRAY2(` \u{1F41B} ${bugs} \u2B50 ${features} \u{1F527} ${improvements} \u2B06\uFE0F ${upgrades} | Tokens: ${tokens.toLocaleString()}`));
2910
+ }
2911
+ var lastLocalTodoLines = 0;
2912
+ function renderLocalTodoList(queue) {
2913
+ const lines = [];
2914
+ lines.push("");
2915
+ lines.push(AMBER4(" \u{1F4CB} MiniBob Autonomy Queue"));
2916
+ lines.push(GRAY2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2917
+ for (let i = 0; i < queue.length; i++) {
2918
+ const task = queue[i];
2919
+ const label = task.suggestion.title || task.suggestion.description?.slice(0, 35) || "No title";
2920
+ let icon;
2921
+ let color;
2922
+ switch (task.status) {
2923
+ case "done":
2924
+ icon = "\u2611";
2925
+ color = GREEN3;
2926
+ break;
2927
+ case "working":
2928
+ icon = "\u23F3";
2929
+ color = AMBER4;
2930
+ break;
2931
+ case "failed":
2932
+ icon = "\u2717";
2933
+ color = RED2;
2934
+ break;
2935
+ case "skipped":
2936
+ icon = "\u23F8\uFE0F";
2937
+ color = GRAY2;
2938
+ break;
2939
+ default:
2940
+ icon = "\u2610";
2941
+ color = GRAY2;
2942
+ }
2943
+ lines.push(color(` ${icon} [${i + 1}/${queue.length}] ${task.suggestion.filePath}`));
2944
+ lines.push(color(` ${label}`));
2945
+ }
2946
+ const completed = queue.filter((t) => t.status === "done" || t.status === "failed" || t.status === "skipped").length;
2947
+ const total = queue.length;
2948
+ const percent = total > 0 ? completed / total : 0;
2949
+ const barLen = 30;
2950
+ const filled = Math.round(percent * barLen);
2951
+ let barColor;
2952
+ if (percent < 0.25) barColor = chalk16.red;
2953
+ else if (percent < 0.5) barColor = chalk16.hex("#FF8C00");
2954
+ else if (percent < 0.75) barColor = chalk16.yellow;
2955
+ else barColor = chalk16.green;
2956
+ lines.push("");
2957
+ lines.push(` [${barColor("\u2588".repeat(filled))}${GRAY2("\u2591".repeat(barLen - filled))}] ${completed}/${total} ${barColor(Math.round(percent * 100) + "%")}`);
2958
+ lines.push("");
2959
+ if (lastLocalTodoLines > 0) {
2960
+ process.stdout.write(`\x1B[${lastLocalTodoLines}A`);
2961
+ for (let i = 0; i < lastLocalTodoLines; i++) {
2962
+ process.stdout.write("\x1B[2K\n");
2963
+ }
2964
+ process.stdout.write(`\x1B[${lastLocalTodoLines}A`);
2965
+ }
2966
+ for (const line of lines) {
2967
+ process.stdout.write(line + "\n");
2968
+ }
2969
+ lastLocalTodoLines = lines.length;
2970
+ }
2971
+
2972
+ // bin/bob.ts
2973
+ var program = new Command();
2974
+ program.name("bob").description("Bob's CLI \u2014 AI coding assistant and Forge orchestrator").version("0.1.0");
2975
+ program.command("whoami").description("Show current authentication status and configuration").action(() => {
2976
+ const config = getConfig();
2977
+ const projectName = path9.basename(process.cwd());
2978
+ console.log("");
2979
+ console.log(chalk17.bold(" \u{1F916} Bob's CLI"));
2980
+ console.log(chalk17.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2981
+ console.log(` ${chalk17.cyan("Status:")} ${config.loggedIn ? chalk17.green("Logged in as " + config.email) : "Not logged in"}`);
2982
+ console.log(` ${chalk17.cyan("Tier:")} ${config.tier === "platform" ? "Platform (Tier 3)" : "Local-first (Tier 1)"}`);
2983
+ console.log(` ${chalk17.cyan("Provider:")} ${config.provider || "Not configured"}`);
2984
+ console.log(` ${chalk17.cyan("Mode:")} ${config.personalizationMode ? "Personalized" : config.consultantMode ? "Consultant" : "Standard"}`);
2985
+ console.log(` ${chalk17.cyan("IDRP:")} ${config.idrp ? "Enabled" : "Disabled"}`);
2986
+ console.log(` ${chalk17.cyan("Project:")} ${projectName} (${process.cwd()})`);
2987
+ console.log(` ${chalk17.cyan("Session:")} ${config.conversationId ? config.conversationId.slice(0, 20) + "..." : "None"}`);
2988
+ console.log("");
2989
+ if (!config.loggedIn) {
2990
+ console.log(chalk17.gray(" Run `bob login` to authenticate."));
2991
+ console.log("");
2992
+ }
2993
+ });
2994
+ registerConfigCommand(program);
2995
+ registerChatCommand(program);
2996
+ registerConsultCommand(program);
2997
+ registerIndexCommand(program);
2998
+ registerLoginCommand(program);
2999
+ registerPushCommand(program);
3000
+ registerByokCommand(program);
3001
+ registerConversationsCommand(program);
3002
+ registerForkCommand(program);
3003
+ registerDeepDiveCommand(program);
3004
+ registerAnalyseCommand(program);
3005
+ registerAutonomyCommand(program);
3006
+ program.parse();