@caliber-ai/cli 0.14.0 → 0.15.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
@@ -85,6 +85,7 @@ import ora2 from "ora";
85
85
  import readline from "readline";
86
86
  import select from "@inquirer/select";
87
87
  import fs16 from "fs";
88
+ import { createTwoFilesPatch } from "diff";
88
89
 
89
90
  // src/auth/token-store.ts
90
91
  init_constants();
@@ -1185,20 +1186,8 @@ function writeClaudeConfig(config) {
1185
1186
  const written = [];
1186
1187
  fs9.writeFileSync("CLAUDE.md", config.claudeMd);
1187
1188
  written.push("CLAUDE.md");
1188
- const claudeDir = ".claude";
1189
- if (!fs9.existsSync(claudeDir)) fs9.mkdirSync(claudeDir, { recursive: true });
1190
- fs9.writeFileSync(
1191
- path10.join(claudeDir, "settings.json"),
1192
- JSON.stringify(config.settings, null, 2)
1193
- );
1194
- written.push(path10.join(claudeDir, "settings.json"));
1195
- fs9.writeFileSync(
1196
- path10.join(claudeDir, "settings.local.json"),
1197
- JSON.stringify(config.settingsLocal, null, 2)
1198
- );
1199
- written.push(path10.join(claudeDir, "settings.local.json"));
1200
1189
  if (config.skills?.length) {
1201
- const skillsDir = path10.join(claudeDir, "skills");
1190
+ const skillsDir = path10.join(".claude", "skills");
1202
1191
  if (!fs9.existsSync(skillsDir)) fs9.mkdirSync(skillsDir, { recursive: true });
1203
1192
  for (const skill of config.skills) {
1204
1193
  const filename = `${skill.name.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}.md`;
@@ -1212,9 +1201,7 @@ function writeClaudeConfig(config) {
1212
1201
  try {
1213
1202
  if (fs9.existsSync(".mcp.json")) {
1214
1203
  const existing = JSON.parse(fs9.readFileSync(".mcp.json", "utf-8"));
1215
- if (existing.mcpServers) {
1216
- existingServers = existing.mcpServers;
1217
- }
1204
+ if (existing.mcpServers) existingServers = existing.mcpServers;
1218
1205
  }
1219
1206
  } catch {
1220
1207
  }
@@ -1319,7 +1306,11 @@ function fileChecksum(filePath) {
1319
1306
  // src/writers/index.ts
1320
1307
  function writeSetup(setup) {
1321
1308
  const filesToWrite = getFilesToWrite(setup);
1322
- const existingFiles = filesToWrite.filter((f) => fs13.existsSync(f));
1309
+ const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs13.existsSync(f));
1310
+ const existingFiles = [
1311
+ ...filesToWrite.filter((f) => fs13.existsSync(f)),
1312
+ ...filesToDelete
1313
+ ];
1323
1314
  const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
1324
1315
  const written = [];
1325
1316
  if ((setup.targetAgent === "claude" || setup.targetAgent === "both") && setup.claude) {
@@ -1328,15 +1319,28 @@ function writeSetup(setup) {
1328
1319
  if ((setup.targetAgent === "cursor" || setup.targetAgent === "both") && setup.cursor) {
1329
1320
  written.push(...writeCursorConfig(setup.cursor));
1330
1321
  }
1322
+ const deleted = [];
1323
+ for (const filePath of filesToDelete) {
1324
+ fs13.unlinkSync(filePath);
1325
+ deleted.push(filePath);
1326
+ }
1331
1327
  ensureGitignore();
1332
- const entries = written.map((file) => ({
1333
- path: file,
1334
- action: existingFiles.includes(file) ? "modified" : "created",
1335
- checksum: fileChecksum(file),
1336
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1337
- }));
1328
+ const entries = [
1329
+ ...written.map((file) => ({
1330
+ path: file,
1331
+ action: existingFiles.includes(file) ? "modified" : "created",
1332
+ checksum: fileChecksum(file),
1333
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1334
+ })),
1335
+ ...deleted.map((file) => ({
1336
+ path: file,
1337
+ action: "deleted",
1338
+ checksum: "",
1339
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1340
+ }))
1341
+ ];
1338
1342
  writeManifest({ version: 1, backupDir, entries });
1339
- return { written, backupDir };
1343
+ return { written, deleted, backupDir };
1340
1344
  }
1341
1345
  function undoSetup() {
1342
1346
  const manifest = readManifest();
@@ -1351,7 +1355,7 @@ function undoSetup() {
1351
1355
  fs13.unlinkSync(entry.path);
1352
1356
  removed.push(entry.path);
1353
1357
  }
1354
- } else if (entry.action === "modified" && manifest.backupDir) {
1358
+ } else if ((entry.action === "modified" || entry.action === "deleted") && manifest.backupDir) {
1355
1359
  if (restoreBackup(manifest.backupDir, entry.path)) {
1356
1360
  restored.push(entry.path);
1357
1361
  }
@@ -1366,7 +1370,7 @@ function undoSetup() {
1366
1370
  function getFilesToWrite(setup) {
1367
1371
  const files = [];
1368
1372
  if ((setup.targetAgent === "claude" || setup.targetAgent === "both") && setup.claude) {
1369
- files.push("CLAUDE.md", ".claude/settings.json", ".claude/settings.local.json");
1373
+ files.push("CLAUDE.md");
1370
1374
  if (setup.claude.mcpServers) files.push(".mcp.json");
1371
1375
  if (setup.claude.skills) {
1372
1376
  for (const s of setup.claude.skills) {
@@ -1582,10 +1586,10 @@ async function initCommand(options) {
1582
1586
  `));
1583
1587
  console.log(chalk3.dim(" Configure your coding agent environment\n"));
1584
1588
  console.log(chalk3.bold(" What is Caliber?\n"));
1585
- console.log(chalk3.dim(" Caliber analyzes your project and generates optimized configurations"));
1586
- console.log(chalk3.dim(" for AI coding agents (Claude Code, Cursor). It creates CLAUDE.md,"));
1587
- console.log(chalk3.dim(" .cursorrules, skills, permissions, and MCP servers tailored to your"));
1588
- console.log(chalk3.dim(" codebase \u2014 so your AI agent understands your project from day one.\n"));
1589
+ console.log(chalk3.dim(" Caliber audits your AI agent configurations and suggests targeted"));
1590
+ console.log(chalk3.dim(" improvements. It analyzes CLAUDE.md, .cursorrules, skills, and MCP"));
1591
+ console.log(chalk3.dim(" servers against your actual codebase \u2014 keeping what works, fixing"));
1592
+ console.log(chalk3.dim(" what's stale, and adding what's missing.\n"));
1589
1593
  console.log(chalk3.bold(" How it works:\n"));
1590
1594
  console.log(chalk3.dim(" 1. Scan Analyze your code, dependencies, and file structure"));
1591
1595
  console.log(chalk3.dim(" 2. Match Detect existing configs and check for teammate setups"));
@@ -1704,9 +1708,9 @@ async function initCommand(options) {
1704
1708
  if (isEmpty) {
1705
1709
  fingerprint.description = await promptInput("What will you build in this project?");
1706
1710
  }
1707
- console.log(chalk3.hex("#6366f1").bold(" Step 4/6 \u2014 Planning a better setup\n"));
1708
- console.log(chalk3.dim(" AI is building CLAUDE.md, permissions, skills, and rules based on"));
1709
- console.log(chalk3.dim(" your project's stack and conventions.\n"));
1711
+ console.log(chalk3.hex("#6366f1").bold(" Step 4/6 \u2014 Auditing your configs\n"));
1712
+ console.log(chalk3.dim(" AI is auditing your CLAUDE.md, skills, and rules against your"));
1713
+ console.log(chalk3.dim(" project's actual codebase and conventions.\n"));
1710
1714
  console.log(chalk3.dim(" This usually takes 1\u20133 minutes on first run.\n"));
1711
1715
  let generatedSetup = null;
1712
1716
  let rawOutput;
@@ -1795,11 +1799,21 @@ async function initCommand(options) {
1795
1799
  try {
1796
1800
  const result = writeSetup(generatedSetup);
1797
1801
  writeSpinner.succeed("Config files written");
1798
- trackEvent("setup_applied", { files_written: result.written.length, target_agent: targetAgent });
1802
+ trackEvent("setup_applied", {
1803
+ files_written: result.written.length,
1804
+ files_deleted: result.deleted.length,
1805
+ target_agent: targetAgent
1806
+ });
1799
1807
  console.log(chalk3.bold("\nFiles created/updated:"));
1800
1808
  for (const file of result.written) {
1801
1809
  console.log(` ${chalk3.green("\u2713")} ${file}`);
1802
1810
  }
1811
+ if (result.deleted.length > 0) {
1812
+ console.log(chalk3.bold("\nFiles removed:"));
1813
+ for (const file of result.deleted) {
1814
+ console.log(` ${chalk3.red("\u2717")} ${file}`);
1815
+ }
1816
+ }
1803
1817
  if (result.backupDir) {
1804
1818
  console.log(chalk3.dim(`
1805
1819
  Backups saved to ${result.backupDir}`));
@@ -1936,39 +1950,20 @@ async function promptAction() {
1936
1950
  function printSetupSummary(setup) {
1937
1951
  const claude = setup.claude;
1938
1952
  const cursor = setup.cursor;
1953
+ const fileDescriptions = setup.fileDescriptions;
1954
+ const deletions = setup.deletions;
1939
1955
  console.log("");
1940
- console.log(chalk3.bold(" Files to create:\n"));
1956
+ console.log(chalk3.bold(" Proposed changes:\n"));
1957
+ const getDescription = (filePath) => {
1958
+ return fileDescriptions?.[filePath];
1959
+ };
1941
1960
  if (claude) {
1942
1961
  if (claude.claudeMd) {
1943
1962
  const icon = fs16.existsSync("CLAUDE.md") ? chalk3.yellow("~") : chalk3.green("+");
1944
- const sections = extractClaudeMdSections(claude.claudeMd);
1963
+ const desc = getDescription("CLAUDE.md");
1945
1964
  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("");
1952
- }
1953
- if (claude.settings) {
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("");
1963
- }
1964
- if (claude.settingsLocal) {
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)}`));
1965
+ if (desc) {
1966
+ console.log(chalk3.dim(` ${desc}`));
1972
1967
  }
1973
1968
  console.log("");
1974
1969
  }
@@ -1977,8 +1972,9 @@ function printSetupSummary(setup) {
1977
1972
  for (const skill of skills) {
1978
1973
  const skillPath = `.claude/skills/${skill.name.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}.md`;
1979
1974
  const icon = fs16.existsSync(skillPath) ? chalk3.yellow("~") : chalk3.green("+");
1975
+ const desc = getDescription(skillPath);
1980
1976
  console.log(` ${icon} ${chalk3.bold(skillPath)}`);
1981
- console.log(chalk3.dim(` ${summarizeSkill(skill)}`));
1977
+ console.log(chalk3.dim(` ${desc || summarizeSkill(skill)}`));
1982
1978
  console.log("");
1983
1979
  }
1984
1980
  }
@@ -1986,18 +1982,18 @@ function printSetupSummary(setup) {
1986
1982
  if (mcpServers && Object.keys(mcpServers).length > 0) {
1987
1983
  const icon = fs16.existsSync(".mcp.json") ? chalk3.yellow("~") : chalk3.green("+");
1988
1984
  const serverNames = Object.keys(mcpServers);
1985
+ const desc = getDescription(".mcp.json");
1989
1986
  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(", ")}`));
1987
+ console.log(chalk3.dim(` ${desc || `servers: ${serverNames.join(", ")}`}`));
1992
1988
  console.log("");
1993
1989
  }
1994
1990
  }
1995
1991
  if (cursor) {
1996
1992
  if (cursor.cursorrules) {
1997
1993
  const icon = fs16.existsSync(".cursorrules") ? chalk3.yellow("~") : chalk3.green("+");
1994
+ const desc = getDescription(".cursorrules");
1998
1995
  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."));
1996
+ if (desc) console.log(chalk3.dim(` ${desc}`));
2001
1997
  console.log("");
2002
1998
  }
2003
1999
  const rules = cursor.rules;
@@ -2005,9 +2001,14 @@ function printSetupSummary(setup) {
2005
2001
  for (const rule of rules) {
2006
2002
  const rulePath = `.cursor/rules/${rule.filename}`;
2007
2003
  const icon = fs16.existsSync(rulePath) ? chalk3.yellow("~") : chalk3.green("+");
2004
+ const desc = getDescription(rulePath);
2008
2005
  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)}`));
2006
+ if (desc) {
2007
+ console.log(chalk3.dim(` ${desc}`));
2008
+ } else {
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
+ }
2011
2012
  console.log("");
2012
2013
  }
2013
2014
  }
@@ -2015,24 +2016,22 @@ function printSetupSummary(setup) {
2015
2016
  if (mcpServers && Object.keys(mcpServers).length > 0) {
2016
2017
  const icon = fs16.existsSync(".cursor/mcp.json") ? chalk3.yellow("~") : chalk3.green("+");
2017
2018
  const serverNames = Object.keys(mcpServers);
2019
+ const desc = getDescription(".cursor/mcp.json");
2018
2020
  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(chalk3.dim(` ${desc || `servers: ${serverNames.join(", ")}`}`));
2021
2022
  console.log("");
2022
2023
  }
2023
2024
  }
2024
- console.log(` ${chalk3.green("+")} ${chalk3.dim("new")} ${chalk3.yellow("~")} ${chalk3.dim("modified")}`);
2025
+ if (Array.isArray(deletions) && deletions.length > 0) {
2026
+ for (const del of deletions) {
2027
+ console.log(` ${chalk3.red("-")} ${chalk3.bold(del.filePath)}`);
2028
+ console.log(chalk3.dim(` ${del.reason}`));
2029
+ console.log("");
2030
+ }
2031
+ }
2032
+ console.log(` ${chalk3.green("+")} ${chalk3.dim("new")} ${chalk3.yellow("~")} ${chalk3.dim("modified")} ${chalk3.red("-")} ${chalk3.dim("removed")}`);
2025
2033
  console.log("");
2026
2034
  }
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
2035
  function summarizeSkill(skill) {
2037
2036
  const lines = skill.content.split("\n").filter((l) => l.trim() && !l.trim().startsWith("#"));
2038
2037
  return lines[0]?.trim().slice(0, 80) || skill.name;
@@ -2043,8 +2042,6 @@ function collectSetupFiles(setup) {
2043
2042
  const cursor = setup.cursor;
2044
2043
  if (claude) {
2045
2044
  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
2045
  const skills = claude.skills;
2049
2046
  if (Array.isArray(skills)) {
2050
2047
  for (const skill of skills) {
@@ -2070,26 +2067,38 @@ function collectSetupFiles(setup) {
2070
2067
  }
2071
2068
  return files;
2072
2069
  }
2073
- function previewFileContent(filePath, content, maxLines = 25) {
2070
+ function previewNewFile(filePath, content, maxLines = 40) {
2074
2071
  const lines = content.split("\n");
2075
2072
  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);
2078
2073
  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")}`);
2074
+ console.log(` ${chalk3.green("+ new")} ${chalk3.bold(filePath)}`);
2075
+ console.log(chalk3.dim(" \u2500".repeat(30)));
2081
2076
  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")}`);
2077
+ console.log(` ${chalk3.green("+")} ${line}`);
2085
2078
  }
2086
2079
  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")}`);
2080
+ console.log("");
2081
+ console.log(chalk3.dim(` ... ${lines.length - maxLines} more lines (${lines.length} total)`));
2082
+ }
2083
+ console.log("");
2084
+ }
2085
+ function previewDiff(filePath, oldContent, newContent) {
2086
+ const patch = createTwoFilesPatch(filePath, filePath, oldContent, newContent, "current", "proposed", { context: 3 });
2087
+ const patchLines = patch.split("\n").slice(2);
2088
+ console.log("");
2089
+ console.log(` ${chalk3.yellow("~ modified")} ${chalk3.bold(filePath)}`);
2090
+ console.log(chalk3.dim(" \u2500".repeat(30)));
2091
+ for (const line of patchLines) {
2092
+ if (line.startsWith("@@")) {
2093
+ console.log(` ${chalk3.cyan(line)}`);
2094
+ } else if (line.startsWith("+")) {
2095
+ console.log(` ${chalk3.green(line)}`);
2096
+ } else if (line.startsWith("-")) {
2097
+ console.log(` ${chalk3.red(line)}`);
2098
+ } else {
2099
+ console.log(` ${chalk3.dim(line)}`);
2100
+ }
2091
2101
  }
2092
- console.log(` ${chalk3.dim("\u2514" + "\u2500".repeat(width - 1) + "\u2518")}`);
2093
2102
  console.log("");
2094
2103
  }
2095
2104
  async function promptFilePreview(setup) {
@@ -2098,11 +2107,19 @@ async function promptFilePreview(setup) {
2098
2107
  console.log(chalk3.dim("\n No files to preview.\n"));
2099
2108
  return;
2100
2109
  }
2101
- const choice = await select({
2102
- message: "Which file to preview?",
2103
- choices: files.map((f, i) => ({ name: f.path, value: i }))
2110
+ const choices = files.map((f, i) => {
2111
+ const exists = fs16.existsSync(f.path);
2112
+ const icon = exists ? chalk3.yellow("~") : chalk3.green("+");
2113
+ return { name: `${icon} ${f.path}`, value: i };
2104
2114
  });
2105
- previewFileContent(files[choice].path, files[choice].content);
2115
+ const choice = await select({ message: "Which file to preview?", choices });
2116
+ const file = files[choice];
2117
+ if (fs16.existsSync(file.path)) {
2118
+ const existing = fs16.readFileSync(file.path, "utf-8");
2119
+ previewDiff(file.path, existing, file.content);
2120
+ } else {
2121
+ previewNewFile(file.path, file.content);
2122
+ }
2106
2123
  }
2107
2124
 
2108
2125
  // src/commands/undo.ts