@bobsworkshop/cli 0.1.4 → 0.2.0

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.
@@ -304,16 +304,26 @@ async function callLocalModel(endpoint, messages) {
304
304
  }
305
305
  );
306
306
  if (response.data?.message?.content) {
307
- return response.data.message.content;
307
+ return {
308
+ text: response.data.message.content,
309
+ evalCount: response.data.eval_count || void 0,
310
+ promptEvalCount: response.data.prompt_eval_count || void 0,
311
+ evalDurationMs: response.data.eval_duration ? Math.round(response.data.eval_duration / 1e6) : void 0,
312
+ totalDurationMs: response.data.total_duration ? Math.round(response.data.total_duration / 1e6) : void 0
313
+ };
308
314
  }
309
315
  const choice = response.data?.choices?.[0];
310
316
  if (choice?.message?.content) {
311
- return choice.message.content;
317
+ return {
318
+ text: choice.message.content,
319
+ evalCount: response.data.usage?.completion_tokens || void 0,
320
+ promptEvalCount: response.data.usage?.prompt_tokens || void 0
321
+ };
312
322
  }
313
323
  if (typeof response.data?.response === "string") {
314
- return response.data.response;
324
+ return { text: response.data.response };
315
325
  }
316
- return "No response received from local model.";
326
+ return { text: "No response received from local model." };
317
327
  } catch (error) {
318
328
  if (error.code === "ECONNREFUSED") {
319
329
  throw new Error("Cannot connect to local model. Is Ollama running? Check your endpoint: " + endpoint);
@@ -369,6 +379,13 @@ import * as fs2 from "fs";
369
379
  import * as path2 from "path";
370
380
  import * as readline2 from "readline";
371
381
  import chalk2 from "chalk";
382
+ var SUCCESS = chalk2.hex("#66BB6A");
383
+ var INFO = chalk2.hex("#26C6DA");
384
+ var WARNING = chalk2.hex("#FFC107");
385
+ var ERROR = chalk2.hex("#EF5350");
386
+ var MUTED = chalk2.hex("#78909C");
387
+ var BRAND_SECONDARY = chalk2.hex("#FFAB00");
388
+ var BORDER = chalk2.hex("#455A64");
372
389
  function extractAllProposedFiles(response) {
373
390
  const proposals = [];
374
391
  const codeBlockRegex = /```[\w]*\n([\s\S]*?)```/g;
@@ -400,7 +417,9 @@ function extractProposedFile(response) {
400
417
  return all.length > 0 ? all[0] : null;
401
418
  }
402
419
  function stripCodeBlockFromResponse(response) {
403
- return response.replace(/```[\w]*\n\s*(?:\/\/\s*(?:File:)?\s*[\w\-\.\/\\]+\.\w+|#\s*(?:File:)?\s*[\w\-\.\/\\]+\.\w+|\*\s*\[FILE:)[^\n]*\n[\s\S]*?```/g, "").trim();
420
+ let stripped = response.replace(/```[\w]*\n\s*(?:\/\/\s*(?:File:)?\s*[\w\-\.\/\\]+\.\w+|#\s*(?:File:)?\s*[\w\-\.\/\\]+\.\w+|\*\s*\[FILE:)[^\n]*\n[\s\S]*?```/g, "").trim();
421
+ stripped = stripped.replace(/```capability_invocation\s*[\s\S]*?```/g, "").trim();
422
+ return stripped;
404
423
  }
405
424
  function isLocalProjectFile(filePath) {
406
425
  const cwd = process.cwd();
@@ -417,8 +436,10 @@ function isLocalProjectFile(filePath) {
417
436
  }
418
437
  async function processAllProposedFiles(response, autoApprove = false, existingRl) {
419
438
  const proposals = extractAllProposedFiles(response);
420
- if (proposals.length === 0) return;
421
- for (const proposed of proposals) {
439
+ const idrpProposals = extractIDRPFileProposals(response);
440
+ const allProposals = [...proposals, ...idrpProposals];
441
+ if (allProposals.length === 0) return;
442
+ for (const proposed of allProposals) {
422
443
  if (proposed.isLocal) {
423
444
  await proposeAndWriteFile(proposed, autoApprove, existingRl);
424
445
  } else {
@@ -429,19 +450,19 @@ async function processAllProposedFiles(response, autoApprove = false, existingRl
429
450
  function displayExternalFile(proposed) {
430
451
  const totalLines = proposed.content.split("\n").length;
431
452
  console.log("");
432
- console.log(chalk2.yellow(` \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510`));
433
- console.log(chalk2.yellow(` \u2502 \u{1F4CB} EXTERNAL: ${proposed.filePath}`));
434
- console.log(chalk2.yellow(` \u2502 This file belongs to another project.`));
435
- console.log(chalk2.yellow(` \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524`));
453
+ console.log(WARNING(` \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`));
454
+ console.log(WARNING(` \u2551`) + BRAND_SECONDARY(` \u{1F4CB} EXTERNAL: ${proposed.filePath}`));
455
+ console.log(WARNING(` \u2551`) + MUTED(` This file belongs to another project.`));
456
+ console.log(WARNING(` \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`));
436
457
  const previewLines = proposed.content.split("\n").slice(0, 6);
437
458
  for (const line of previewLines) {
438
- console.log(chalk2.gray(` \u2502 ${line}`));
459
+ console.log(WARNING(` \u2551`) + MUTED(` ${line}`));
439
460
  }
440
461
  if (totalLines > 6) {
441
- console.log(chalk2.gray(` \u2502 ... (${totalLines - 6} more lines)`));
462
+ console.log(WARNING(` \u2551`) + MUTED(` ... (${totalLines - 6} more lines)`));
442
463
  }
443
- console.log(chalk2.yellow(` \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518`));
444
- console.log(chalk2.gray(` Copy this file manually to your project at: ${proposed.filePath}`));
464
+ console.log(WARNING(` \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`));
465
+ console.log(MUTED(` Copy this file manually to your project at: ${proposed.filePath}`));
445
466
  console.log("");
446
467
  }
447
468
  async function proposeAndWriteFile(proposed, autoApprove = false, existingRl) {
@@ -452,37 +473,43 @@ async function proposeAndWriteFile(proposed, autoApprove = false, existingRl) {
452
473
  const absolutePath = path2.join(process.cwd(), proposed.filePath);
453
474
  const action = proposed.isNew ? "CREATE" : "UPDATE";
454
475
  const icon = proposed.isNew ? "\u{1F4C4}" : "\u270F\uFE0F";
455
- const color = proposed.isNew ? chalk2.green : chalk2.yellow;
476
+ const accentColor = proposed.isNew ? SUCCESS : BRAND_SECONDARY;
456
477
  const totalLines = proposed.content.split("\n").length;
457
478
  if (!autoApprove) {
458
479
  console.log("");
459
- console.log(color(` \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510`));
460
- console.log(color(` \u2502 ${icon} ${action}: ${proposed.filePath} (${totalLines} lines)`));
461
- console.log(color(` \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524`));
480
+ 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`));
481
+ console.log(BORDER(` \u2551`) + accentColor(` ${icon} ${action}: `) + chalk2.white(`${proposed.filePath}`) + MUTED(` (${totalLines} lines)`));
482
+ 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`));
462
483
  const previewLines = proposed.content.split("\n").slice(0, 6);
463
484
  for (const line of previewLines) {
464
- console.log(chalk2.gray(` \u2502 ${line}`));
485
+ console.log(BORDER(` \u2551`) + MUTED(` ${line}`));
465
486
  }
466
487
  if (totalLines > 6) {
467
- console.log(chalk2.gray(` \u2502 ... (${totalLines - 6} more lines)`));
488
+ console.log(BORDER(` \u2551`) + MUTED(` ... (${totalLines - 6} more lines)`));
468
489
  }
469
- console.log(color(` \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518`));
490
+ 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`));
470
491
  console.log("");
492
+ const promptText = INFO(` \u{1F4BE} ${action === "CREATE" ? "Write this file" : "Apply changes"}? `) + MUTED(`(y/n/path): `);
471
493
  let answer;
472
494
  if (existingRl) {
473
- answer = await new Promise((resolve3) => {
474
- existingRl.question(chalk2.cyan(` \u{1F4BE} ${action === "CREATE" ? "Write this file" : "Apply changes"}? (y/n/path): `), resolve3);
475
- });
495
+ existingRl.pause();
496
+ process.stdout.write(promptText);
497
+ const buf = Buffer.alloc(1024);
498
+ const bytesRead = fs2.readSync(0, buf, 0, 1024, null);
499
+ answer = buf.toString("utf-8", 0, bytesRead).replace(/\r?\n/, "").trim();
500
+ existingRl.resume();
476
501
  } else {
477
502
  const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
478
503
  answer = await new Promise((resolve3) => {
479
- rl.question(chalk2.cyan(` \u{1F4BE} ${action === "CREATE" ? "Write this file" : "Apply changes"}? (y/n/path): `), resolve3);
504
+ rl.question(promptText, (ans) => {
505
+ rl.close();
506
+ resolve3(ans);
507
+ });
480
508
  });
481
- rl.close();
482
509
  }
483
510
  const trimmed = answer.trim().toLowerCase();
484
511
  if (trimmed === "n" || trimmed === "no") {
485
- console.log(chalk2.gray(" \u23ED\uFE0F Skipped."));
512
+ console.log(MUTED(" \u23ED\uFE0F Skipped."));
486
513
  return false;
487
514
  }
488
515
  if (trimmed !== "y" && trimmed !== "yes" && trimmed.length > 0) {
@@ -505,17 +532,38 @@ function writeFile(targetPath, content, originalFilePath, isNew) {
505
532
  }
506
533
  fs2.writeFileSync(targetPath, content, "utf-8");
507
534
  const relativePath = path2.relative(process.cwd(), targetPath);
508
- console.log(chalk2.green(` \u2705 Written: ${relativePath}`));
535
+ console.log(SUCCESS(` \u2705 Written: ${relativePath}`));
509
536
  if (!isNew) {
510
- console.log(chalk2.gray(` \u{1F4E6} Backup saved to .bob-backups/`));
537
+ console.log(MUTED(` \u{1F4E6} Backup saved to .bob-backups/`));
511
538
  }
512
539
  console.log("");
513
540
  return true;
514
541
  } catch (error) {
515
- console.log(chalk2.red(` \u274C Write failed: ${error.message}`));
542
+ console.log(ERROR(` \u274C Write failed: ${error.message}`));
516
543
  return false;
517
544
  }
518
545
  }
546
+ function extractIDRPFileProposals(response) {
547
+ const proposals = [];
548
+ const invocationRegex = /```capability_invocation\s*([\s\S]*?)```/g;
549
+ let match;
550
+ while ((match = invocationRegex.exec(response)) !== null) {
551
+ try {
552
+ const invocation = JSON.parse(match[1].trim());
553
+ if (invocation.action !== "invoke") continue;
554
+ if (!["workspace_create_file", "workspace_update_file"].includes(invocation.capabilityId)) continue;
555
+ const filePath = invocation.params?.filePath;
556
+ const content = invocation.params?.content || invocation.params?.newContent;
557
+ if (!filePath || !content) continue;
558
+ const absolutePath = path2.join(process.cwd(), filePath);
559
+ const isNew = !fs2.existsSync(absolutePath);
560
+ const isLocal = isLocalProjectFile(filePath);
561
+ proposals.push({ filePath, content, isNew, isLocal });
562
+ } catch {
563
+ }
564
+ }
565
+ return proposals;
566
+ }
519
567
 
520
568
  // src/core/analysis-tracker.ts
521
569
  import * as fs3 from "fs";
@@ -583,13 +631,15 @@ function markSuggestionById(id, category, status, metadata) {
583
631
  }
584
632
 
585
633
  // src/commands/analyse-results.ts
586
- var RED = chalk3.hex("#EF5350");
587
- var PURPLE = chalk3.hex("#AB47BC");
588
- var BLUE = chalk3.hex("#42A5F5");
589
- var TEAL = chalk3.hex("#26A69A");
590
- var AMBER = chalk3.hex("#FFAB00");
591
- var GRAY = chalk3.gray;
592
- var BORDER = chalk3.hex("#455A64");
634
+ var BRAND_PRIMARY = chalk3.hex("#E66F24");
635
+ var BRAND_SECONDARY2 = chalk3.hex("#FFAB00");
636
+ var SUCCESS2 = chalk3.hex("#66BB6A");
637
+ var INFO2 = chalk3.hex("#26C6DA");
638
+ var WARNING2 = chalk3.hex("#FFC107");
639
+ var ERROR2 = chalk3.hex("#EF5350");
640
+ var MUTED2 = chalk3.hex("#78909C");
641
+ var BORDER2 = chalk3.hex("#455A64");
642
+ var MODE_CONSULTANT = chalk3.hex("#AB47BC");
593
643
  var PRIORITY_COLORS = {
594
644
  "critical": chalk3.bgHex("#B71C1C").white,
595
645
  "high": chalk3.hex("#FF6D00"),
@@ -597,10 +647,16 @@ var PRIORITY_COLORS = {
597
647
  "low": chalk3.hex("#66BB6A")
598
648
  };
599
649
  var CATEGORY_COLORS = {
600
- "bugs": RED,
601
- "features": PURPLE,
602
- "improvements": BLUE,
603
- "upgrades": TEAL
650
+ "bugs": ERROR2,
651
+ "features": MODE_CONSULTANT,
652
+ "improvements": INFO2,
653
+ "upgrades": SUCCESS2
654
+ };
655
+ var CATEGORY_ICONS = {
656
+ "bugs": "\u{1F534}",
657
+ "features": "\u{1F7E3}",
658
+ "improvements": "\u{1F535}",
659
+ "upgrades": "\u{1F7E2}"
604
660
  };
605
661
  async function showInteractiveResults(config, category, sort, search) {
606
662
  let allSuggestions = [];
@@ -614,7 +670,7 @@ async function showInteractiveResults(config, category, sort, search) {
614
670
  });
615
671
  allSuggestions = result?.suggestions || [];
616
672
  } catch (error) {
617
- console.log(chalk3.red(` \u274C ${error.message}`));
673
+ console.log(ERROR2(` \u274C ${error.message}`));
618
674
  return;
619
675
  }
620
676
  } else {
@@ -629,33 +685,34 @@ async function showInteractiveResults(config, category, sort, search) {
629
685
  sortSuggestions(allSuggestions, sort || "priority");
630
686
  if (allSuggestions.length === 0) {
631
687
  console.log("");
632
- console.log(chalk3.green(" \u2705 No items found. Clean!"));
688
+ console.log(SUCCESS2(" \u2705 No items found. Clean!"));
633
689
  console.log("");
634
690
  return;
635
691
  }
636
- const color = CATEGORY_COLORS[category] || GRAY;
692
+ const color = CATEGORY_COLORS[category] || MUTED2;
693
+ const icon = CATEGORY_ICONS[category] || "\u25C6";
637
694
  let running = true;
638
695
  let displaySuggestions = [...allSuggestions];
639
696
  let currentSort = sort || "priority";
640
697
  while (running) {
641
698
  console.log("");
642
- console.log(color(` \u25C6 ${category.toUpperCase()} (${displaySuggestions.length} items) | Sort: ${currentSort}`));
643
- 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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
699
+ console.log(color(` ${icon} ${category.toUpperCase()} (${displaySuggestions.length} items) \u2502 Sort: ${currentSort}`));
700
+ console.log(MUTED2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
644
701
  console.log("");
645
702
  const choices = [];
646
703
  choices.push({
647
- name: chalk3.cyan(" \u{1F500} Toggle sort"),
704
+ name: INFO2(" \u{1F500} Toggle sort"),
648
705
  value: "__sort__",
649
706
  short: "Sort"
650
707
  });
651
- choices.push(new inquirer.Separator(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")));
708
+ choices.push(new inquirer.Separator(MUTED2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")));
652
709
  for (let idx = 0; idx < displaySuggestions.length; idx++) {
653
710
  const item = displaySuggestions[idx];
654
- const pColor = PRIORITY_COLORS[item.priority?.toLowerCase()] || GRAY;
655
- const priorityLabel = (item.priority || "MEDIUM").toUpperCase().padEnd(9);
656
- const filePath = (item.filePath || "unknown").split("/").pop() || "unknown";
657
- const desc = (item.description || item.title || "No description").slice(0, 42);
658
- const displayName = `${pColor(priorityLabel)} ${chalk3.cyan(filePath.padEnd(18))} ${chalk3.white(desc)}`;
711
+ const pColor = PRIORITY_COLORS[item.priority?.toLowerCase()] || MUTED2;
712
+ const priorityLabel = pColor((item.priority || "MEDIUM").toUpperCase().padEnd(9));
713
+ const fileName = (item.filePath || "unknown").split("/").pop() || "unknown";
714
+ const title = (item.title || item.description || "No description").slice(0, 40);
715
+ const displayName = ` ${priorityLabel} ${INFO2(fileName.padEnd(20))} ${chalk3.white(title)}`;
659
716
  choices.push({
660
717
  name: displayName,
661
718
  value: idx,
@@ -663,9 +720,9 @@ async function showInteractiveResults(config, category, sort, search) {
663
720
  description: `${item.priority} ${item.filePath} ${item.title} ${item.description}`
664
721
  });
665
722
  }
666
- choices.push(new inquirer.Separator(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")));
723
+ choices.push(new inquirer.Separator(MUTED2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")));
667
724
  choices.push({
668
- name: chalk3.gray(" \u2190 Quit"),
725
+ name: MUTED2(" \u2190 Quit"),
669
726
  value: "__quit__",
670
727
  short: "Quit"
671
728
  });
@@ -695,7 +752,7 @@ async function showInteractiveResults(config, category, sort, search) {
695
752
  if (selected === "__sort__") {
696
753
  currentSort = currentSort === "priority" ? "file" : "priority";
697
754
  sortSuggestions(displaySuggestions, currentSort);
698
- console.log(chalk3.cyan(` Sort changed to: ${currentSort}`));
755
+ console.log(INFO2(` Sort changed to: ${currentSort}`));
699
756
  continue;
700
757
  }
701
758
  if (typeof selected === "number") {
@@ -716,48 +773,49 @@ async function showInteractiveResults(config, category, sort, search) {
716
773
  displaySuggestions.splice(selected, 1);
717
774
  const originalIdx = allSuggestions.findIndex((s) => s.id === item.id);
718
775
  if (originalIdx !== -1) allSuggestions.splice(originalIdx, 1);
719
- console.log(chalk3.gray(" \u23ED\uFE0F Dismissed and logged."));
776
+ console.log(MUTED2(" \u23ED\uFE0F Dismissed and logged."));
720
777
  }
721
778
  }
722
779
  }
723
780
  }
724
781
  async function showExpandedView(item, category) {
725
- const color = CATEGORY_COLORS[category] || GRAY;
726
- const pColor = PRIORITY_COLORS[item.priority?.toLowerCase()] || GRAY;
782
+ const color = CATEGORY_COLORS[category] || MUTED2;
783
+ const pColor = PRIORITY_COLORS[item.priority?.toLowerCase()] || MUTED2;
784
+ const icon = CATEGORY_ICONS[category] || "\u25C6";
727
785
  console.log("");
728
- console.log(color(" \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"));
729
- console.log(color(" \u2551 ") + pColor(`${(item.priority || "MEDIUM").toUpperCase()} ${category.toUpperCase().slice(0, -1)}`));
730
- console.log(color(" \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"));
731
- console.log(color(" \u2551") + chalk3.gray(" File: ") + chalk3.cyan(item.filePath || "unknown"));
732
- console.log(color(" \u2551") + chalk3.gray(" Priority: ") + pColor((item.priority || "medium").toUpperCase()));
733
- console.log(color(" \u2551"));
734
- console.log(color(" \u2551") + chalk3.gray(" Title:"));
735
- console.log(color(" \u2551") + chalk3.white.bold(` ${item.title || "No title"}`));
736
- console.log(color(" \u2551"));
737
- console.log(color(" \u2551") + chalk3.gray(" Description:"));
786
+ console.log(BORDER2(" \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"));
787
+ console.log(BORDER2(" \u2551") + ` ${icon} ` + pColor(`${(item.priority || "MEDIUM").toUpperCase()} ${category.toUpperCase().slice(0, -1)}`));
788
+ console.log(BORDER2(" \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"));
789
+ console.log(BORDER2(" \u2551") + MUTED2(" File: ") + INFO2(item.filePath || "unknown"));
790
+ console.log(BORDER2(" \u2551") + MUTED2(" Priority: ") + pColor((item.priority || "medium").toUpperCase()));
791
+ console.log(BORDER2(" \u2551"));
792
+ console.log(BORDER2(" \u2551") + MUTED2(" Title:"));
793
+ console.log(BORDER2(" \u2551") + chalk3.white.bold(` ${item.title || "No title"}`));
794
+ console.log(BORDER2(" \u2551"));
795
+ console.log(BORDER2(" \u2551") + MUTED2(" Description:"));
738
796
  const descLines = wrapText(item.description || "No description", 54);
739
797
  for (const line of descLines) {
740
- console.log(color(" \u2551") + chalk3.white(` ${line}`));
798
+ console.log(BORDER2(" \u2551") + chalk3.white(` ${line}`));
741
799
  }
742
800
  if (item.implementation) {
743
- console.log(color(" \u2551"));
744
- console.log(color(" \u2551") + chalk3.gray(" Implementation:"));
801
+ console.log(BORDER2(" \u2551"));
802
+ console.log(BORDER2(" \u2551") + MUTED2(" Implementation:"));
745
803
  const implLines = wrapText(item.implementation, 54);
746
804
  for (const line of implLines) {
747
- console.log(color(" \u2551") + chalk3.white(` ${line}`));
805
+ console.log(BORDER2(" \u2551") + chalk3.white(` ${line}`));
748
806
  }
749
807
  }
750
- console.log(color(" \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"));
808
+ 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"));
751
809
  console.log("");
752
810
  const { action } = await inquirer.prompt([
753
811
  {
754
812
  type: "select",
755
813
  name: "action",
756
- message: "What do you want to do?",
814
+ message: BRAND_SECONDARY2("What do you want to do?"),
757
815
  choices: [
758
- { name: chalk3.green(" \u{1F527} Implement this fix"), value: "implement" },
759
- { name: chalk3.red(" \u{1F5D1}\uFE0F Dismiss"), value: "dismiss" },
760
- { name: chalk3.gray(" \u2190 Back to list"), value: "back" }
816
+ { name: SUCCESS2(" \u{1F527} Implement this fix"), value: "implement" },
817
+ { name: ERROR2(" \u{1F5D1}\uFE0F Dismiss"), value: "dismiss" },
818
+ { name: MUTED2(" \u2190 Back to list"), value: "back" }
761
819
  ]
762
820
  }
763
821
  ]);
@@ -765,12 +823,12 @@ async function showExpandedView(item, category) {
765
823
  }
766
824
  async function handleImplement(item, config, category) {
767
825
  console.log("");
768
- console.log(chalk3.cyan(" \u{1F527} Implementing fix..."));
826
+ console.log(INFO2(" \u{1F527} Implementing fix..."));
769
827
  console.log("");
770
828
  if (config.provider === "local" && config.localEndpoint) {
771
829
  const fileContent = readFileContent(item.filePath);
772
830
  if (!fileContent) {
773
- console.log(chalk3.red(` \u274C Could not read file: ${item.filePath}`));
831
+ console.log(ERROR2(` \u274C Could not read file: ${item.filePath}`));
774
832
  return;
775
833
  }
776
834
  const prompt = `You are MiniBob \u2014 a junior engineer making SURGICAL code fixes under strict supervision.
@@ -802,7 +860,8 @@ Return the complete file content now:`;
802
860
  { role: "system", content: "You are MiniBob, a junior engineer making SURGICAL fixes. Return ONLY valid source code. NO markdown. NO code fences. NO explanation. Start with // File: comment. Make the ABSOLUTE MINIMUM change needed. Do NOT restructure, refactor, or touch ANYTHING beyond the specific fix. If unsure, return the file unchanged." },
803
861
  { role: "user", content: prompt }
804
862
  ];
805
- const response = await callLocalModel(config.localEndpoint, messages);
863
+ const localResult = await callLocalModel(config.localEndpoint, messages);
864
+ const response = typeof localResult === "object" && localResult.text ? localResult.text : localResult;
806
865
  const lines = response.split("\n");
807
866
  const firstLine = lines[0].trim();
808
867
  let newContent;
@@ -812,25 +871,26 @@ Return the complete file content now:`;
812
871
  newContent = response.trim();
813
872
  }
814
873
  if (newContent.includes("```") || newContent.includes("## ") || newContent.startsWith("Here") || newContent.startsWith("I have") || newContent.startsWith("Sure")) {
815
- console.log(chalk3.yellow(" \u26A0\uFE0F MiniBob returned explanation instead of code. Fix rejected."));
874
+ console.log(WARNING2(" \u26A0\uFE0F MiniBob returned explanation instead of code. Fix rejected."));
816
875
  return;
817
876
  }
818
877
  if (newContent.length < fileContent.length * 0.5) {
819
- console.log(chalk3.yellow(` \u26A0\uFE0F MiniBob's output is ${Math.round(newContent.length / fileContent.length * 100)}% of original size. Rejecting.`));
878
+ console.log(WARNING2(` \u26A0\uFE0F MiniBob's output is ${Math.round(newContent.length / fileContent.length * 100)}% of original size. Rejecting.`));
820
879
  return;
821
880
  }
822
881
  const originalExports = fileContent.match(/export\s+(function|class|const|interface|type|async\s+function)\s+\w+/g) || [];
823
882
  for (const exp of originalExports) {
824
883
  const exportName = exp.split(/\s+/).pop();
825
884
  if (!newContent.includes(exportName)) {
826
- console.log(chalk3.yellow(` \u26A0\uFE0F MiniBob removed export "${exportName}". Rejecting.`));
885
+ console.log(WARNING2(` \u26A0\uFE0F MiniBob removed export "${exportName}". Rejecting.`));
827
886
  return;
828
887
  }
829
888
  }
830
889
  await proposeAndWriteFile({
831
890
  filePath: item.filePath,
832
891
  content: newContent,
833
- isNew: false
892
+ isNew: false,
893
+ isLocal: true
834
894
  });
835
895
  if (item.id) {
836
896
  markSuggestionById(item.id, category, "implemented", {
@@ -839,7 +899,7 @@ Return the complete file content now:`;
839
899
  });
840
900
  }
841
901
  } catch (error) {
842
- console.log(chalk3.red(` \u274C Implementation failed: ${error.message}`));
902
+ console.log(ERROR2(` \u274C Implementation failed: ${error.message}`));
843
903
  }
844
904
  } else if (config.loggedIn && config.conversationId) {
845
905
  try {
@@ -851,7 +911,7 @@ Return the complete file content now:`;
851
911
  jobId: `cli_impl_${Date.now()}`
852
912
  });
853
913
  if (result?.success) {
854
- console.log(chalk3.green(` \u2705 ${result.message}`));
914
+ console.log(SUCCESS2(` \u2705 ${result.message}`));
855
915
  if (item.id) {
856
916
  markSuggestionById(item.id, category, "implemented", {
857
917
  reason: "Platform implementation",
@@ -859,13 +919,13 @@ Return the complete file content now:`;
859
919
  });
860
920
  }
861
921
  } else {
862
- console.log(chalk3.red(" \u274C Implementation failed on platform."));
922
+ console.log(ERROR2(" \u274C Implementation failed on platform."));
863
923
  }
864
924
  } catch (error) {
865
- console.log(chalk3.red(` \u274C ${error.message}`));
925
+ console.log(ERROR2(` \u274C ${error.message}`));
866
926
  }
867
927
  } else {
868
- console.log(chalk3.red(" \u274C No provider configured for implementation."));
928
+ console.log(ERROR2(" \u274C No provider configured for implementation."));
869
929
  }
870
930
  console.log("");
871
931
  }
@@ -929,6 +989,7 @@ export {
929
989
  callLocalModel,
930
990
  buildLocalContext,
931
991
  readFileContent,
992
+ extractAllProposedFiles,
932
993
  extractProposedFile,
933
994
  stripCodeBlockFromResponse,
934
995
  processAllProposedFiles,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobsworkshop/cli",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Bob's CLI — AI coding assistant and Forge orchestrator",
5
5
  "type": "module",
6
6
  "files": [
@@ -38,6 +38,7 @@
38
38
  "cli-table3": "^0.6.5",
39
39
  "commander": "^15.0.0",
40
40
  "conf": "^15.1.0",
41
+ "diff": "^9.0.0",
41
42
  "inquirer": "^14.0.2",
42
43
  "marked": "^18.0.4",
43
44
  "marked-terminal": "^7.3.0",
@@ -47,6 +48,7 @@
47
48
  "ws": "^8.21.0"
48
49
  },
49
50
  "devDependencies": {
51
+ "@types/diff": "^8.0.0",
50
52
  "@types/node": "^25.9.1",
51
53
  "tsup": "^8.5.1",
52
54
  "typescript": "^6.0.3",
@@ -1,8 +0,0 @@
1
- import {
2
- loadLocalSuggestions,
3
- showInteractiveResults
4
- } from "./chunk-LHWBSCJ4.js";
5
- export {
6
- loadLocalSuggestions,
7
- showInteractiveResults
8
- };