@caliber-ai/cli 0.12.0 → 0.14.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.
package/dist/bin.js CHANGED
@@ -1709,10 +1709,10 @@ async function initCommand(options) {
1709
1709
  console.log(chalk3.dim(" your project's stack and conventions.\n"));
1710
1710
  console.log(chalk3.dim(" This usually takes 1\u20133 minutes on first run.\n"));
1711
1711
  let generatedSetup = null;
1712
- let setupExplanation;
1713
1712
  let rawOutput;
1714
1713
  trackEvent("generation_started", { target_agent: targetAgent });
1715
1714
  const hasExistingConfig = !!(ec.claudeMd || ec.claudeSettings || ec.claudeSkills?.length || ec.cursorrules || ec.cursorRules?.length || ec.claudeMcpServers || ec.cursorMcpServers);
1715
+ const genStartTime = Date.now();
1716
1716
  const genSpinner = ora2("Generating setup...").start();
1717
1717
  const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
1718
1718
  genMessages.start();
@@ -1729,7 +1729,6 @@ async function initCommand(options) {
1729
1729
  },
1730
1730
  (payload) => {
1731
1731
  generatedSetup = payload.setup;
1732
- setupExplanation = payload.explanation;
1733
1732
  rawOutput = payload.raw;
1734
1733
  },
1735
1734
  (error) => {
@@ -1757,21 +1756,19 @@ async function initCommand(options) {
1757
1756
  }
1758
1757
  throw new Error("__exit__");
1759
1758
  }
1760
- genSpinner.succeed("Setup generated");
1759
+ const elapsedMs = Date.now() - genStartTime;
1760
+ const mins = Math.floor(elapsedMs / 6e4);
1761
+ const secs = Math.floor(elapsedMs % 6e4 / 1e3);
1762
+ const timeStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
1763
+ genSpinner.succeed(`Setup generated ${chalk3.dim(`in ${timeStr}`)}`);
1761
1764
  printSetupSummary(generatedSetup);
1762
1765
  console.log(chalk3.hex("#6366f1").bold(" Step 5/6 \u2014 Review\n"));
1763
- console.log(chalk3.dim(" Review the proposed files below. You can accept, refine via chat,"));
1764
- console.log(chalk3.dim(" or see an explanation of why each item was recommended.\n"));
1765
- let explained = false;
1766
- let action = await promptAction(explained);
1767
- while (action === "explain") {
1768
- if (setupExplanation) {
1769
- printExplanation(setupExplanation);
1770
- } else {
1771
- console.log(chalk3.dim("\nNo explanation available for this setup.\n"));
1772
- }
1773
- explained = true;
1774
- action = await promptAction(explained);
1766
+ console.log(chalk3.dim(" Review the proposed files. You can accept, refine via chat,"));
1767
+ console.log(chalk3.dim(" or preview any file to see exactly what will be written.\n"));
1768
+ let action = await promptAction();
1769
+ while (action === "preview") {
1770
+ await promptFilePreview(generatedSetup);
1771
+ action = await promptAction();
1775
1772
  }
1776
1773
  if (action === "decline") {
1777
1774
  trackEvent("setup_declined");
@@ -1856,6 +1853,11 @@ async function initCommand(options) {
1856
1853
  }
1857
1854
  console.log(chalk3.bold.green("\nSetup complete! Your coding agent is now configured."));
1858
1855
  console.log(chalk3.dim("Run `caliber undo` to revert changes.\n"));
1856
+ console.log(chalk3.bold(" Next steps:\n"));
1857
+ console.log(` ${chalk3.hex("#6366f1")("caliber health")} Check your config quality and fix issues`);
1858
+ console.log(` ${chalk3.hex("#6366f1")("caliber recommend")} Discover additional skills for your stack`);
1859
+ console.log(` ${chalk3.hex("#6366f1")("caliber diff")} See what changed since last setup`);
1860
+ console.log("");
1859
1861
  }
1860
1862
  async function refineLoop(currentSetup, _targetAgent) {
1861
1863
  const history = [];
@@ -1920,93 +1922,187 @@ async function promptAgent() {
1920
1922
  ]
1921
1923
  });
1922
1924
  }
1923
- async function promptAction(explained) {
1924
- const choices = [
1925
- { name: "Accept and apply", value: "accept" },
1926
- { name: "Refine via chat", value: "refine" },
1927
- ...!explained ? [{ name: "Explain recommendations", value: "explain" }] : [],
1928
- { name: "Decline", value: "decline" }
1929
- ];
1930
- return select({ message: "What would you like to do?", choices });
1931
- }
1932
- function fileEntry(filePath, desc) {
1933
- const icon = fs16.existsSync(filePath) ? chalk3.yellow("~") : chalk3.green("+");
1934
- const description = desc ? chalk3.dim(`\u2014 ${desc}`) : "";
1935
- return ` ${icon} ${filePath} ${description}`;
1925
+ async function promptAction() {
1926
+ return select({
1927
+ message: "What would you like to do?",
1928
+ choices: [
1929
+ { name: "Accept and apply", value: "accept" },
1930
+ { name: "Refine via chat", value: "refine" },
1931
+ { name: "Preview a file", value: "preview" },
1932
+ { name: "Decline", value: "decline" }
1933
+ ]
1934
+ });
1936
1935
  }
1937
1936
  function printSetupSummary(setup) {
1938
1937
  const claude = setup.claude;
1939
1938
  const cursor = setup.cursor;
1940
- const descriptions = setup.fileDescriptions || {};
1941
1939
  console.log("");
1940
+ console.log(chalk3.bold(" Files to create:\n"));
1942
1941
  if (claude) {
1943
1942
  if (claude.claudeMd) {
1944
- console.log(fileEntry("CLAUDE.md", descriptions["CLAUDE.md"]));
1943
+ const icon = fs16.existsSync("CLAUDE.md") ? chalk3.yellow("~") : chalk3.green("+");
1944
+ const sections = extractClaudeMdSections(claude.claudeMd);
1945
+ console.log(` ${icon} ${chalk3.bold("CLAUDE.md")}`);
1946
+ console.log(chalk3.dim(" Your project's knowledge base for the AI agent \u2014 architecture, build"));
1947
+ console.log(chalk3.dim(" commands, conventions. Claude reads this at the start of every session."));
1948
+ if (sections.length > 0) {
1949
+ console.log(chalk3.dim(` Sections: ${sections.join(", ")}`));
1950
+ }
1951
+ console.log("");
1945
1952
  }
1946
1953
  if (claude.settings) {
1947
- console.log(fileEntry(".claude/settings.json", descriptions[".claude/settings.json"]));
1954
+ const icon = fs16.existsSync(".claude/settings.json") ? chalk3.yellow("~") : chalk3.green("+");
1955
+ const settings = claude.settings;
1956
+ const perms = settings.permissions?.allow || [];
1957
+ console.log(` ${icon} ${chalk3.bold(".claude/settings.json")}`);
1958
+ console.log(chalk3.dim(" Pre-approved shell commands so Claude doesn't ask permission each time."));
1959
+ if (perms.length > 0) {
1960
+ console.log(chalk3.dim(` Allows: ${summarizePermissions(perms)}`));
1961
+ }
1962
+ console.log("");
1948
1963
  }
1949
1964
  if (claude.settingsLocal) {
1950
- console.log(fileEntry(".claude/settings.local.json", descriptions[".claude/settings.local.json"]));
1965
+ const icon = fs16.existsSync(".claude/settings.local.json") ? chalk3.yellow("~") : chalk3.green("+");
1966
+ const settings = claude.settingsLocal;
1967
+ const perms = settings.permissions?.allow || [];
1968
+ console.log(` ${icon} ${chalk3.bold(".claude/settings.local.json")}`);
1969
+ console.log(chalk3.dim(" Your personal permission overrides (not shared with teammates)."));
1970
+ if (perms.length > 0) {
1971
+ console.log(chalk3.dim(` Allows: ${summarizePermissions(perms)}`));
1972
+ }
1973
+ console.log("");
1951
1974
  }
1952
1975
  const skills = claude.skills;
1953
1976
  if (Array.isArray(skills) && skills.length > 0) {
1954
1977
  for (const skill of skills) {
1955
1978
  const skillPath = `.claude/skills/${skill.name.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}.md`;
1956
- console.log(fileEntry(skillPath, descriptions[skillPath] || skill.name));
1979
+ const icon = fs16.existsSync(skillPath) ? chalk3.yellow("~") : chalk3.green("+");
1980
+ console.log(` ${icon} ${chalk3.bold(skillPath)}`);
1981
+ console.log(chalk3.dim(` ${summarizeSkill(skill)}`));
1982
+ console.log("");
1957
1983
  }
1958
1984
  }
1959
1985
  const mcpServers = claude.mcpServers;
1960
1986
  if (mcpServers && Object.keys(mcpServers).length > 0) {
1961
- console.log(fileEntry(".mcp.json", descriptions[".mcp.json"] || Object.keys(mcpServers).join(", ")));
1987
+ const icon = fs16.existsSync(".mcp.json") ? chalk3.yellow("~") : chalk3.green("+");
1988
+ const serverNames = Object.keys(mcpServers);
1989
+ console.log(` ${icon} ${chalk3.bold(".mcp.json")}`);
1990
+ console.log(chalk3.dim(" Connects Claude to external tools it can call directly."));
1991
+ console.log(chalk3.dim(` Servers: ${serverNames.join(", ")}`));
1992
+ console.log("");
1962
1993
  }
1963
1994
  }
1964
1995
  if (cursor) {
1965
1996
  if (cursor.cursorrules) {
1966
- console.log(fileEntry(".cursorrules", descriptions[".cursorrules"]));
1997
+ const icon = fs16.existsSync(".cursorrules") ? chalk3.yellow("~") : chalk3.green("+");
1998
+ console.log(` ${icon} ${chalk3.bold(".cursorrules")}`);
1999
+ console.log(chalk3.dim(" Project-wide instructions for Cursor \u2014 coding style, architecture,"));
2000
+ console.log(chalk3.dim(" and conventions. Cursor reads this at the start of every session."));
2001
+ console.log("");
1967
2002
  }
1968
2003
  const rules = cursor.rules;
1969
2004
  if (Array.isArray(rules) && rules.length > 0) {
1970
2005
  for (const rule of rules) {
1971
2006
  const rulePath = `.cursor/rules/${rule.filename}`;
1972
- console.log(fileEntry(rulePath, descriptions[rulePath]));
2007
+ const icon = fs16.existsSync(rulePath) ? chalk3.yellow("~") : chalk3.green("+");
2008
+ console.log(` ${icon} ${chalk3.bold(rulePath)}`);
2009
+ const firstLine = rule.content.split("\n").filter((l) => l.trim() && !l.trim().startsWith("#"))[0];
2010
+ if (firstLine) console.log(chalk3.dim(` ${firstLine.trim().slice(0, 80)}`));
2011
+ console.log("");
1973
2012
  }
1974
2013
  }
1975
2014
  const mcpServers = cursor.mcpServers;
1976
2015
  if (mcpServers && Object.keys(mcpServers).length > 0) {
1977
- console.log(fileEntry(".cursor/mcp.json", descriptions[".cursor/mcp.json"] || Object.keys(mcpServers).join(", ")));
2016
+ const icon = fs16.existsSync(".cursor/mcp.json") ? chalk3.yellow("~") : chalk3.green("+");
2017
+ const serverNames = Object.keys(mcpServers);
2018
+ console.log(` ${icon} ${chalk3.bold(".cursor/mcp.json")}`);
2019
+ console.log(chalk3.dim(" Connects Cursor to external tools it can call directly."));
2020
+ console.log(chalk3.dim(` Servers: ${serverNames.join(", ")}`));
2021
+ console.log("");
1978
2022
  }
1979
2023
  }
1980
- console.log("");
1981
2024
  console.log(` ${chalk3.green("+")} ${chalk3.dim("new")} ${chalk3.yellow("~")} ${chalk3.dim("modified")}`);
1982
2025
  console.log("");
1983
2026
  }
1984
- function printExplanation(explanation) {
1985
- console.log(chalk3.bold("\n Why this setup?\n"));
1986
- const lines = explanation.split("\n");
1987
- for (const line of lines) {
1988
- const trimmed = line.trim();
1989
- if (!trimmed) continue;
1990
- const headerMatch = trimmed.match(/^\[(.+)\]$/);
1991
- if (headerMatch) {
1992
- console.log(` ${chalk3.bold.hex("#6366f1")(headerMatch[1])}`);
1993
- continue;
2027
+ function extractClaudeMdSections(md) {
2028
+ return md.split("\n").filter((line) => /^##\s+/.test(line)).map((line) => line.replace(/^##\s+/, "").trim());
2029
+ }
2030
+ function summarizePermissions(permissions, maxShow = 3) {
2031
+ if (permissions.length === 0) return "None";
2032
+ const shown = permissions.slice(0, maxShow).join(", ");
2033
+ const remaining = permissions.length - maxShow;
2034
+ return remaining > 0 ? `${shown} (+${remaining} more)` : shown;
2035
+ }
2036
+ function summarizeSkill(skill) {
2037
+ const lines = skill.content.split("\n").filter((l) => l.trim() && !l.trim().startsWith("#"));
2038
+ return lines[0]?.trim().slice(0, 80) || skill.name;
2039
+ }
2040
+ function collectSetupFiles(setup) {
2041
+ const files = [];
2042
+ const claude = setup.claude;
2043
+ const cursor = setup.cursor;
2044
+ if (claude) {
2045
+ if (claude.claudeMd) files.push({ path: "CLAUDE.md", content: claude.claudeMd });
2046
+ if (claude.settings) files.push({ path: ".claude/settings.json", content: JSON.stringify(claude.settings, null, 2) });
2047
+ if (claude.settingsLocal) files.push({ path: ".claude/settings.local.json", content: JSON.stringify(claude.settingsLocal, null, 2) });
2048
+ const skills = claude.skills;
2049
+ if (Array.isArray(skills)) {
2050
+ for (const skill of skills) {
2051
+ const skillPath = `.claude/skills/${skill.name.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}.md`;
2052
+ files.push({ path: skillPath, content: skill.content });
2053
+ }
1994
2054
  }
1995
- const itemMatch = trimmed.match(/^-\s+\*\*(.+?)\*\*[:\s]*(.*)$/);
1996
- if (itemMatch) {
1997
- const name = itemMatch[1];
1998
- const desc = itemMatch[2].replace(/^\s*[-—:]\s*/, "");
1999
- console.log(` ${chalk3.dim("\u25B8")} ${chalk3.white(name)} ${chalk3.dim(desc)}`);
2000
- continue;
2055
+ if (claude.mcpServers && Object.keys(claude.mcpServers).length > 0) {
2056
+ files.push({ path: ".mcp.json", content: JSON.stringify({ mcpServers: claude.mcpServers }, null, 2) });
2001
2057
  }
2002
- const plainMatch = trimmed.match(/^-\s+(.*)$/);
2003
- if (plainMatch) {
2004
- console.log(` ${chalk3.dim("\u25B8")} ${chalk3.dim(plainMatch[1])}`);
2005
- continue;
2058
+ }
2059
+ if (cursor) {
2060
+ if (cursor.cursorrules) files.push({ path: ".cursorrules", content: cursor.cursorrules });
2061
+ const rules = cursor.rules;
2062
+ if (Array.isArray(rules)) {
2063
+ for (const rule of rules) {
2064
+ files.push({ path: `.cursor/rules/${rule.filename}`, content: rule.content });
2065
+ }
2066
+ }
2067
+ if (cursor.mcpServers && Object.keys(cursor.mcpServers).length > 0) {
2068
+ files.push({ path: ".cursor/mcp.json", content: JSON.stringify({ mcpServers: cursor.mcpServers }, null, 2) });
2006
2069
  }
2007
- console.log(` ${chalk3.dim(trimmed)}`);
2008
2070
  }
2071
+ return files;
2072
+ }
2073
+ function previewFileContent(filePath, content, maxLines = 25) {
2074
+ const lines = content.split("\n");
2075
+ const displayLines = lines.slice(0, maxLines);
2076
+ const maxWidth = Math.max(60, ...displayLines.map((l) => l.length + 4));
2077
+ const width = Math.min(maxWidth, 80);
2009
2078
  console.log("");
2079
+ console.log(` ${chalk3.dim("\u250C\u2500")} ${chalk3.bold(filePath)} ${chalk3.dim("\u2500".repeat(Math.max(0, width - filePath.length - 5)) + "\u2510")}`);
2080
+ console.log(` ${chalk3.dim("\u2502")}${" ".repeat(width - 1)}${chalk3.dim("\u2502")}`);
2081
+ for (const line of displayLines) {
2082
+ const truncated = line.length > width - 5 ? line.slice(0, width - 8) + "..." : line;
2083
+ const padding = " ".repeat(Math.max(0, width - truncated.length - 3));
2084
+ console.log(` ${chalk3.dim("\u2502")} ${truncated}${padding}${chalk3.dim("\u2502")}`);
2085
+ }
2086
+ if (lines.length > maxLines) {
2087
+ console.log(` ${chalk3.dim("\u2502")}${" ".repeat(width - 1)}${chalk3.dim("\u2502")}`);
2088
+ const note = `(showing first ${maxLines} lines, full file is ${lines.length} lines)`;
2089
+ const notePadding = " ".repeat(Math.max(0, width - note.length - 3));
2090
+ console.log(` ${chalk3.dim("\u2502")} ${chalk3.dim(note)}${notePadding}${chalk3.dim("\u2502")}`);
2091
+ }
2092
+ console.log(` ${chalk3.dim("\u2514" + "\u2500".repeat(width - 1) + "\u2518")}`);
2093
+ console.log("");
2094
+ }
2095
+ async function promptFilePreview(setup) {
2096
+ const files = collectSetupFiles(setup);
2097
+ if (files.length === 0) {
2098
+ console.log(chalk3.dim("\n No files to preview.\n"));
2099
+ return;
2100
+ }
2101
+ const choice = await select({
2102
+ message: "Which file to preview?",
2103
+ choices: files.map((f, i) => ({ name: f.path, value: i }))
2104
+ });
2105
+ previewFileContent(files[choice].path, files[choice].content);
2010
2106
  }
2011
2107
 
2012
2108
  // src/commands/undo.ts
@@ -2099,11 +2195,11 @@ async function statusCommand(options) {
2099
2195
  console.log("");
2100
2196
  }
2101
2197
 
2102
- // src/commands/update.ts
2198
+ // src/commands/regenerate.ts
2103
2199
  import chalk6 from "chalk";
2104
2200
  import ora4 from "ora";
2105
2201
  import confirm from "@inquirer/confirm";
2106
- async function updateCommand(options) {
2202
+ async function regenerateCommand(options) {
2107
2203
  const auth2 = getStoredAuth();
2108
2204
  if (!auth2) {
2109
2205
  console.log(chalk6.red("Not logged in. Run `caliber login` first."));
@@ -2147,7 +2243,7 @@ async function updateCommand(options) {
2147
2243
  },
2148
2244
  (error) => {
2149
2245
  genMessages.stop();
2150
- trackEvent("error_occurred", { error_type: "generation_failed", error_message: error, command: "update" });
2246
+ trackEvent("error_occurred", { error_type: "generation_failed", error_message: error, command: "regenerate" });
2151
2247
  genSpinner.fail(`Generation error: ${error}`);
2152
2248
  },
2153
2249
  (status) => {
@@ -2157,7 +2253,7 @@ async function updateCommand(options) {
2157
2253
  } catch (err) {
2158
2254
  genMessages.stop();
2159
2255
  const msg = err instanceof Error ? err.message : "Unknown error";
2160
- trackEvent("error_occurred", { error_type: "generation_request_failed", error_message: msg, command: "update" });
2256
+ trackEvent("error_occurred", { error_type: "generation_request_failed", error_message: msg, command: "regenerate" });
2161
2257
  genSpinner.fail(`Regeneration failed: ${msg}`);
2162
2258
  throw new Error("__exit__");
2163
2259
  }
@@ -2172,16 +2268,16 @@ async function updateCommand(options) {
2172
2268
  console.log(JSON.stringify(generatedSetup, null, 2));
2173
2269
  return;
2174
2270
  }
2175
- const shouldApply = await confirm({ message: "Apply updated setup?", default: true });
2271
+ const shouldApply = await confirm({ message: "Apply regenerated setup?", default: true });
2176
2272
  if (!shouldApply) {
2177
- trackEvent("update_declined");
2178
- console.log(chalk6.dim("Update cancelled."));
2273
+ trackEvent("regenerate_declined");
2274
+ console.log(chalk6.dim("Regeneration cancelled."));
2179
2275
  return;
2180
2276
  }
2181
2277
  const writeSpinner = ora4("Updating config files...").start();
2182
2278
  const result = writeSetup(generatedSetup);
2183
2279
  writeSpinner.succeed("Config files updated");
2184
- trackEvent("setup_updated", { files_written: result.written.length });
2280
+ trackEvent("setup_regenerated", { files_written: result.written.length });
2185
2281
  for (const file of result.written) {
2186
2282
  console.log(` ${chalk6.green("\u2713")} ${file}`);
2187
2283
  }
@@ -2416,6 +2512,7 @@ function printRecommendations(recs) {
2416
2512
  // src/commands/health.ts
2417
2513
  import chalk9 from "chalk";
2418
2514
  import ora6 from "ora";
2515
+ import confirm2 from "@inquirer/confirm";
2419
2516
  async function healthCommand(options) {
2420
2517
  const auth2 = getStoredAuth();
2421
2518
  if (!auth2) {
@@ -2450,30 +2547,44 @@ async function healthCommand(options) {
2450
2547
  return;
2451
2548
  }
2452
2549
  printReport(report);
2453
- if (options.fix) {
2454
- console.log(chalk9.bold("\nGenerating fix plan..."));
2550
+ const shouldFix = options.fix || report.recommendations.length > 0 && !options.json && await confirm2({ message: "Would you like to fix these issues?", default: true });
2551
+ if (shouldFix) {
2552
+ const planSpinner = ora6("Generating fix plan...").start();
2455
2553
  const plan = await apiRequest(
2456
2554
  `/api/context/reports/${report.id}/fix-plan`,
2457
2555
  { method: "POST", body: { projectId } }
2458
2556
  );
2557
+ planSpinner.succeed("Fix plan ready");
2459
2558
  if (!plan?.actions.length) {
2460
- console.log(chalk9.dim("No fixes needed."));
2559
+ console.log(chalk9.dim(" No fixes needed."));
2461
2560
  return;
2462
2561
  }
2463
- console.log(chalk9.bold("\nProposed actions:"));
2562
+ console.log(chalk9.bold("\n Fix Plan:\n"));
2464
2563
  for (const action of plan.actions) {
2465
- const icon = action.type === "remove" ? chalk9.red("- ") : action.type === "add" ? chalk9.green("+ ") : chalk9.yellow("~ ");
2466
- console.log(` ${icon}${action.type}: ${action.items.join(", ")}`);
2564
+ if (action.type === "remove") {
2565
+ console.log(` ${chalk9.red("\u2717 Remove")} ${action.items.join(", ")}`);
2566
+ if (action.reason) console.log(chalk9.dim(` ${action.reason}`));
2567
+ } else if (action.type === "add") {
2568
+ console.log(` ${chalk9.green("+ Add")} ${action.items.join(", ")}`);
2569
+ if (action.reason) console.log(chalk9.dim(` ${action.reason}`));
2570
+ }
2571
+ console.log("");
2467
2572
  }
2468
- console.log(chalk9.dim(`
2469
- Estimated improvement: +${plan.estimatedScoreImprovement} points`));
2470
- const fixSpinner = ora6("Executing fix plan...").start();
2573
+ const scoreAfterStr = plan.estimatedScoreAfter != null ? String(plan.estimatedScoreAfter) : `${report.score + plan.estimatedScoreImprovement}`;
2574
+ console.log(` Expected: ${report.score} \u2192 ${chalk9.green(scoreAfterStr)}/100
2575
+ `);
2576
+ const shouldExecute = options.fix || await confirm2({ message: "Apply this fix plan?", default: true });
2577
+ if (!shouldExecute) {
2578
+ console.log(chalk9.dim(" Fix cancelled."));
2579
+ return;
2580
+ }
2581
+ const fixSpinner = ora6("Applying fixes...").start();
2471
2582
  try {
2472
2583
  const result = await apiRequest(
2473
2584
  `/api/context/reports/${report.id}/fix-execute`,
2474
2585
  { method: "POST", body: { projectId } }
2475
2586
  );
2476
- fixSpinner.succeed("Fix applied");
2587
+ fixSpinner.succeed("Fixes applied");
2477
2588
  console.log("");
2478
2589
  console.log(` Score: ${result.scoreBefore} \u2192 ${chalk9.green(String(result.scoreAfter))} (${chalk9.green(`+${result.improvement}`)})`);
2479
2590
  if (result.itemsRemoved) console.log(` Removed: ${result.itemsRemoved} items`);
@@ -2486,6 +2597,13 @@ Estimated improvement: +${plan.estimatedScoreImprovement} points`));
2486
2597
  }
2487
2598
  }
2488
2599
  }
2600
+ var CATEGORY_DESCRIPTIONS = {
2601
+ tooling: "build/test/lint commands (helps agents the most)",
2602
+ essential: "constraints agents can't discover from code",
2603
+ convention: "style/naming rules (linters handle this; low value)",
2604
+ overview: "project summaries (agents discover this by reading code)",
2605
+ workflow: "development workflows and processes"
2606
+ };
2489
2607
  function printReport(report) {
2490
2608
  const gradeColors = {
2491
2609
  A: chalk9.green,
@@ -2498,24 +2616,43 @@ function printReport(report) {
2498
2616
  console.log(chalk9.bold("\n Context Health Report\n"));
2499
2617
  console.log(` Grade: ${gradeColor(report.grade)} Score: ${gradeColor(String(report.score))}/100
2500
2618
  `);
2501
- if (Object.keys(report.category_breakdown).length) {
2502
- console.log(chalk9.dim(" Category Breakdown:"));
2503
- const categories = Object.entries(report.category_breakdown);
2504
- const maxCount = Math.max(...categories.map(([, v]) => v.count));
2619
+ const categories = Object.entries(report.category_breakdown);
2620
+ if (categories.length) {
2621
+ console.log(chalk9.bold(" Category Breakdown:\n"));
2622
+ const itemsByCategory = /* @__PURE__ */ new Map();
2623
+ if (report.item_classifications) {
2624
+ for (const item of report.item_classifications) {
2625
+ const cat = item.category.toLowerCase();
2626
+ if (!itemsByCategory.has(cat)) itemsByCategory.set(cat, []);
2627
+ itemsByCategory.get(cat).push(item);
2628
+ }
2629
+ }
2505
2630
  for (const [name, data] of categories) {
2506
- const barLen = maxCount > 0 ? Math.round(data.count / maxCount * 20) : 0;
2507
- const bar = "\u2588".repeat(barLen) + "\u2591".repeat(20 - barLen);
2508
- console.log(` ${name.padEnd(12)} ${bar} ${data.count} items (${data.tokens} tokens)`);
2631
+ const desc = CATEGORY_DESCRIPTIONS[name] || "";
2632
+ const header = desc ? `${chalk9.bold(name)} ${chalk9.dim(`\u2014 ${desc}`)}` : chalk9.bold(name);
2633
+ console.log(` ${header}`);
2634
+ const items = itemsByCategory.get(name);
2635
+ if (items?.length) {
2636
+ for (const item of items) {
2637
+ const icon = item.impactScore >= 0 ? chalk9.green("\u2713") : chalk9.red("\u2717");
2638
+ const snippet = item.recommendation.length > 70 ? item.recommendation.slice(0, 67) + "..." : item.recommendation;
2639
+ console.log(` ${icon} ${item.itemName.padEnd(22)} ${chalk9.dim(`"${snippet}"`)}`);
2640
+ }
2641
+ }
2642
+ console.log(chalk9.dim(` ${data.count} items \xB7 ${data.tokens} tokens
2643
+ `));
2509
2644
  }
2510
- console.log("");
2511
2645
  }
2512
2646
  if (report.recommendations.length) {
2513
- console.log(chalk9.dim(" Recommendations:"));
2647
+ console.log(chalk9.bold(" Recommendations:\n"));
2514
2648
  for (const rec of report.recommendations) {
2515
2649
  const icon = rec.priority === "high" ? chalk9.red("!") : rec.priority === "medium" ? chalk9.yellow("~") : chalk9.dim("-");
2516
2650
  console.log(` ${icon} ${rec.description}`);
2651
+ if (rec.reasoning) {
2652
+ console.log(chalk9.dim(` ${rec.reasoning}`));
2653
+ }
2654
+ console.log("");
2517
2655
  }
2518
- console.log("");
2519
2656
  }
2520
2657
  }
2521
2658
 
@@ -3041,7 +3178,7 @@ import chalk14 from "chalk";
3041
3178
  import readline2 from "readline";
3042
3179
  import ora10 from "ora";
3043
3180
  import select2 from "@inquirer/select";
3044
- import confirm2 from "@inquirer/confirm";
3181
+ import confirm3 from "@inquirer/confirm";
3045
3182
  function prompt(question) {
3046
3183
  const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
3047
3184
  return new Promise((resolve2) => {
@@ -3121,7 +3258,7 @@ async function reviewCommand(message, options) {
3121
3258
  console.log(` Most useful: ${bestPart || chalk14.dim("(skipped)")}`);
3122
3259
  console.log(` Could be better: ${biggestGap || chalk14.dim("(skipped)")}`);
3123
3260
  console.log(` Would recommend: ${wouldRecommend}`);
3124
- const shouldSubmit = await confirm2({ message: "Submit this review?", default: true });
3261
+ const shouldSubmit = await confirm3({ message: "Submit this review?", default: true });
3125
3262
  if (!shouldSubmit) {
3126
3263
  console.log(chalk14.dim("\n Review cancelled.\n"));
3127
3264
  return;
@@ -3135,11 +3272,12 @@ var pkg3 = JSON.parse(
3135
3272
  fs20.readFileSync(path17.resolve(__dirname2, "..", "package.json"), "utf-8")
3136
3273
  );
3137
3274
  var program = new Command();
3138
- program.name("caliber").description("Configure your coding agent environment").version(pkg3.version);
3275
+ var displayVersion = process.env.CALIBER_LOCAL ? `${pkg3.version}-local` : pkg3.version;
3276
+ program.name(process.env.CALIBER_LOCAL ? "caloc" : "caliber").description("Configure your coding agent environment").version(displayVersion);
3139
3277
  program.command("init").description("Initialize coding agent setup for this project").option("--agent <type>", "Target agent: claude, cursor, or both").option("--dry-run", "Preview changes without writing files").option("--force", "Overwrite existing setup without prompting").action(initCommand);
3140
3278
  program.command("undo").description("Revert all config changes made by Caliber").action(undoCommand);
3141
3279
  program.command("status").description("Show current Caliber setup status").option("--json", "Output as JSON").action(statusCommand);
3142
- program.command("update").description("Re-analyze project and update setup").option("--dry-run", "Preview changes without writing files").action(updateCommand);
3280
+ program.command("regenerate").alias("regen").alias("re").alias("update").description("Re-analyze project and regenerate setup").option("--dry-run", "Preview changes without writing files").action(regenerateCommand);
3143
3281
  program.command("login").description("Authenticate with Caliber").action(loginCommand);
3144
3282
  program.command("logout").description("Clear stored credentials").action(logoutCommand);
3145
3283
  program.command("recommend").description("Discover and manage skill recommendations").option("--generate", "Generate new recommendations").option("--status <status>", "Filter by status: pending, accepted, dismissed").action(recommendCommand);
@@ -3160,13 +3298,13 @@ import { fileURLToPath as fileURLToPath4 } from "url";
3160
3298
  import { execSync as execSync4 } from "child_process";
3161
3299
  import chalk15 from "chalk";
3162
3300
  import ora11 from "ora";
3163
- import confirm3 from "@inquirer/confirm";
3301
+ import confirm4 from "@inquirer/confirm";
3164
3302
  var __dirname_vc = path18.dirname(fileURLToPath4(import.meta.url));
3165
3303
  var pkg4 = JSON.parse(
3166
3304
  fs21.readFileSync(path18.resolve(__dirname_vc, "..", "package.json"), "utf-8")
3167
3305
  );
3168
3306
  async function promptYesNo(question) {
3169
- return confirm3({ message: question, default: true });
3307
+ return confirm4({ message: question, default: true });
3170
3308
  }
3171
3309
  async function checkForUpdates() {
3172
3310
  if (process.env.CALIBER_SKIP_UPDATE_CHECK) return;
@@ -3231,6 +3369,9 @@ Restarting: caliber ${args.join(" ")}
3231
3369
  }
3232
3370
 
3233
3371
  // src/bin.ts
3372
+ if (process.env.CALIBER_LOCAL) {
3373
+ process.env.CALIBER_SKIP_UPDATE_CHECK = "1";
3374
+ }
3234
3375
  var firstRun = isFirstRun();
3235
3376
  getDeviceId();
3236
3377
  if (firstRun) {