@caliber-ai/cli 0.4.2 → 0.5.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
@@ -75,15 +75,15 @@ if (dsn) {
75
75
 
76
76
  // src/cli.ts
77
77
  import { Command } from "commander";
78
- import fs16 from "fs";
79
- import path13 from "path";
78
+ import fs19 from "fs";
79
+ import path16 from "path";
80
80
  import { fileURLToPath as fileURLToPath3 } from "url";
81
81
 
82
82
  // src/commands/init.ts
83
83
  import chalk2 from "chalk";
84
84
  import ora2 from "ora";
85
85
  import readline from "readline";
86
- import fs13 from "fs";
86
+ import fs15 from "fs";
87
87
 
88
88
  // src/auth/token-store.ts
89
89
  init_constants();
@@ -343,6 +343,9 @@ function getGitRemoteUrl() {
343
343
  return void 0;
344
344
  }
345
345
  }
346
+ function isGitRepo() {
347
+ return isInsideGitRepo();
348
+ }
346
349
 
347
350
  // src/fingerprint/package-json.ts
348
351
  import fs4 from "fs";
@@ -554,6 +557,10 @@ import fs6 from "fs";
554
557
  import path7 from "path";
555
558
  function readExistingConfigs(dir) {
556
559
  const configs = {};
560
+ const readmeMdPath = path7.join(dir, "README.md");
561
+ if (fs6.existsSync(readmeMdPath)) {
562
+ configs.readmeMd = fs6.readFileSync(readmeMdPath, "utf-8");
563
+ }
557
564
  const claudeMdPath = path7.join(dir, "CLAUDE.md");
558
565
  if (fs6.existsSync(claudeMdPath)) {
559
566
  configs.claudeMd = fs6.readFileSync(claudeMdPath, "utf-8");
@@ -928,9 +935,9 @@ async function getValidToken() {
928
935
  }
929
936
  return refreshed;
930
937
  }
931
- async function apiRequest(path15, options = {}) {
938
+ async function apiRequest(path18, options = {}) {
932
939
  let token = await getValidToken();
933
- let resp = await fetch(`${API_URL}${path15}`, {
940
+ let resp = await fetch(`${API_URL}${path18}`, {
934
941
  method: options.method || "GET",
935
942
  headers: {
936
943
  "Content-Type": "application/json",
@@ -944,7 +951,7 @@ async function apiRequest(path15, options = {}) {
944
951
  throw new Error("Session expired. Run `caliber login` to re-authenticate.");
945
952
  }
946
953
  token = refreshed;
947
- resp = await fetch(`${API_URL}${path15}`, {
954
+ resp = await fetch(`${API_URL}${path18}`, {
948
955
  method: options.method || "GET",
949
956
  headers: {
950
957
  "Content-Type": "application/json",
@@ -960,9 +967,9 @@ async function apiRequest(path15, options = {}) {
960
967
  const json = await resp.json();
961
968
  return json.data;
962
969
  }
963
- async function apiStream(path15, body, onChunk, onComplete, onError, onStatus) {
970
+ async function apiStream(path18, body, onChunk, onComplete, onError, onStatus) {
964
971
  let token = await getValidToken();
965
- let resp = await fetch(`${API_URL}${path15}`, {
972
+ let resp = await fetch(`${API_URL}${path18}`, {
966
973
  method: "POST",
967
974
  headers: {
968
975
  "Content-Type": "application/json",
@@ -976,7 +983,7 @@ async function apiStream(path15, body, onChunk, onComplete, onError, onStatus) {
976
983
  throw new Error("Session expired. Run `caliber login` to re-authenticate.");
977
984
  }
978
985
  token = refreshed;
979
- resp = await fetch(`${API_URL}${path15}`, {
986
+ resp = await fetch(`${API_URL}${path18}`, {
980
987
  method: "POST",
981
988
  headers: {
982
989
  "Content-Type": "application/json",
@@ -1215,6 +1222,101 @@ function ensureGitignore() {
1215
1222
  }
1216
1223
  }
1217
1224
 
1225
+ // src/lib/hooks.ts
1226
+ import fs13 from "fs";
1227
+ import path12 from "path";
1228
+ var SETTINGS_PATH = path12.join(".claude", "settings.json");
1229
+ var HOOK_COMMAND = "caliber refresh --quiet";
1230
+ function readSettings() {
1231
+ if (!fs13.existsSync(SETTINGS_PATH)) return {};
1232
+ try {
1233
+ return JSON.parse(fs13.readFileSync(SETTINGS_PATH, "utf-8"));
1234
+ } catch {
1235
+ return {};
1236
+ }
1237
+ }
1238
+ function writeSettings(settings) {
1239
+ const dir = path12.dirname(SETTINGS_PATH);
1240
+ if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
1241
+ fs13.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
1242
+ }
1243
+ function findHookIndex(sessionEnd) {
1244
+ return sessionEnd.findIndex(
1245
+ (entry) => entry.hooks?.some((h) => h.command === HOOK_COMMAND)
1246
+ );
1247
+ }
1248
+ function isHookInstalled() {
1249
+ const settings = readSettings();
1250
+ const sessionEnd = settings.hooks?.SessionEnd;
1251
+ if (!Array.isArray(sessionEnd)) return false;
1252
+ return findHookIndex(sessionEnd) !== -1;
1253
+ }
1254
+ function installHook() {
1255
+ const settings = readSettings();
1256
+ if (!settings.hooks) settings.hooks = {};
1257
+ if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
1258
+ if (findHookIndex(settings.hooks.SessionEnd) !== -1) {
1259
+ return { installed: false, alreadyInstalled: true };
1260
+ }
1261
+ settings.hooks.SessionEnd.push({
1262
+ matcher: "",
1263
+ hooks: [{ type: "command", command: HOOK_COMMAND }]
1264
+ });
1265
+ writeSettings(settings);
1266
+ return { installed: true, alreadyInstalled: false };
1267
+ }
1268
+ function removeHook() {
1269
+ const settings = readSettings();
1270
+ const sessionEnd = settings.hooks?.SessionEnd;
1271
+ if (!Array.isArray(sessionEnd)) {
1272
+ return { removed: false, notFound: true };
1273
+ }
1274
+ const idx = findHookIndex(sessionEnd);
1275
+ if (idx === -1) {
1276
+ return { removed: false, notFound: true };
1277
+ }
1278
+ sessionEnd.splice(idx, 1);
1279
+ if (sessionEnd.length === 0) {
1280
+ delete settings.hooks.SessionEnd;
1281
+ }
1282
+ if (settings.hooks && Object.keys(settings.hooks).length === 0) {
1283
+ delete settings.hooks;
1284
+ }
1285
+ writeSettings(settings);
1286
+ return { removed: true, notFound: false };
1287
+ }
1288
+
1289
+ // src/lib/state.ts
1290
+ init_constants();
1291
+ import fs14 from "fs";
1292
+ import path13 from "path";
1293
+ import { execSync as execSync2 } from "child_process";
1294
+ var STATE_FILE = path13.join(CALIBER_DIR, ".caliber-state.json");
1295
+ function readState() {
1296
+ try {
1297
+ if (!fs14.existsSync(STATE_FILE)) return null;
1298
+ return JSON.parse(fs14.readFileSync(STATE_FILE, "utf-8"));
1299
+ } catch {
1300
+ return null;
1301
+ }
1302
+ }
1303
+ function writeState(state) {
1304
+ if (!fs14.existsSync(CALIBER_DIR)) {
1305
+ fs14.mkdirSync(CALIBER_DIR, { recursive: true });
1306
+ }
1307
+ fs14.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
1308
+ }
1309
+ function getCurrentHeadSha() {
1310
+ try {
1311
+ return execSync2("git rev-parse HEAD", {
1312
+ encoding: "utf-8",
1313
+ stdio: ["pipe", "pipe", "pipe"]
1314
+ }).trim();
1315
+ } catch {
1316
+ return null;
1317
+ }
1318
+ }
1319
+
1218
1320
  // src/utils/spinner-messages.ts
1219
1321
  var GENERATION_MESSAGES = [
1220
1322
  "Analyzing your project structure and dependencies...",
@@ -1441,6 +1543,21 @@ async function initCommand(options) {
1441
1543
  console.error(chalk2.red(err instanceof Error ? err.message : "Unknown error"));
1442
1544
  throw new Error("__exit__");
1443
1545
  }
1546
+ const hookAnswer = await promptInput(chalk2.cyan("Enable auto-refresh on Claude Code session end? (Y/n):"));
1547
+ if (!hookAnswer || hookAnswer.toLowerCase() !== "n") {
1548
+ const hookResult = installHook();
1549
+ if (hookResult.installed) {
1550
+ console.log(` ${chalk2.green("\u2713")} Auto-refresh hook installed`);
1551
+ const sha = getCurrentHeadSha();
1552
+ if (sha) {
1553
+ writeState({ lastRefreshSha: sha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
1554
+ }
1555
+ } else if (hookResult.alreadyInstalled) {
1556
+ console.log(chalk2.dim(" Auto-refresh hook already installed"));
1557
+ }
1558
+ } else {
1559
+ console.log(chalk2.dim(" Skipped. Run `caliber hooks install` later to enable."));
1560
+ }
1444
1561
  try {
1445
1562
  let projectId = existingProjectId;
1446
1563
  if (!projectId) {
@@ -1576,7 +1693,7 @@ function promptAction(explained) {
1576
1693
  });
1577
1694
  }
1578
1695
  function fileEntry(filePath, desc) {
1579
- const icon = fs13.existsSync(filePath) ? chalk2.yellow("~") : chalk2.green("+");
1696
+ const icon = fs15.existsSync(filePath) ? chalk2.yellow("~") : chalk2.green("+");
1580
1697
  const description = desc ? chalk2.dim(`\u2014 ${desc}`) : "";
1581
1698
  return ` ${icon} ${filePath} ${description}`;
1582
1699
  }
@@ -1689,16 +1806,16 @@ function undoCommand() {
1689
1806
 
1690
1807
  // src/commands/status.ts
1691
1808
  import chalk4 from "chalk";
1692
- import fs15 from "fs";
1809
+ import fs17 from "fs";
1693
1810
 
1694
1811
  // src/scanner/index.ts
1695
- import fs14 from "fs";
1696
- import path12 from "path";
1812
+ import fs16 from "fs";
1813
+ import path14 from "path";
1697
1814
  import crypto4 from "crypto";
1698
1815
  function scanLocalState(dir) {
1699
1816
  const items = [];
1700
- const claudeMdPath = path12.join(dir, "CLAUDE.md");
1701
- if (fs14.existsSync(claudeMdPath)) {
1817
+ const claudeMdPath = path14.join(dir, "CLAUDE.md");
1818
+ if (fs16.existsSync(claudeMdPath)) {
1702
1819
  items.push({
1703
1820
  type: "rule",
1704
1821
  platform: "claude",
@@ -1707,8 +1824,8 @@ function scanLocalState(dir) {
1707
1824
  path: claudeMdPath
1708
1825
  });
1709
1826
  }
1710
- const settingsPath = path12.join(dir, ".claude", "settings.json");
1711
- if (fs14.existsSync(settingsPath)) {
1827
+ const settingsPath = path14.join(dir, ".claude", "settings.json");
1828
+ if (fs16.existsSync(settingsPath)) {
1712
1829
  items.push({
1713
1830
  type: "config",
1714
1831
  platform: "claude",
@@ -1717,10 +1834,10 @@ function scanLocalState(dir) {
1717
1834
  path: settingsPath
1718
1835
  });
1719
1836
  }
1720
- const skillsDir = path12.join(dir, ".claude", "skills");
1721
- if (fs14.existsSync(skillsDir)) {
1722
- for (const file of fs14.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
1723
- const filePath = path12.join(skillsDir, file);
1837
+ const skillsDir = path14.join(dir, ".claude", "skills");
1838
+ if (fs16.existsSync(skillsDir)) {
1839
+ for (const file of fs16.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
1840
+ const filePath = path14.join(skillsDir, file);
1724
1841
  items.push({
1725
1842
  type: "skill",
1726
1843
  platform: "claude",
@@ -1730,10 +1847,10 @@ function scanLocalState(dir) {
1730
1847
  });
1731
1848
  }
1732
1849
  }
1733
- const mcpJsonPath = path12.join(dir, ".mcp.json");
1734
- if (fs14.existsSync(mcpJsonPath)) {
1850
+ const mcpJsonPath = path14.join(dir, ".mcp.json");
1851
+ if (fs16.existsSync(mcpJsonPath)) {
1735
1852
  try {
1736
- const mcpJson = JSON.parse(fs14.readFileSync(mcpJsonPath, "utf-8"));
1853
+ const mcpJson = JSON.parse(fs16.readFileSync(mcpJsonPath, "utf-8"));
1737
1854
  if (mcpJson.mcpServers) {
1738
1855
  for (const name of Object.keys(mcpJson.mcpServers)) {
1739
1856
  items.push({
@@ -1748,8 +1865,8 @@ function scanLocalState(dir) {
1748
1865
  } catch {
1749
1866
  }
1750
1867
  }
1751
- const cursorrulesPath = path12.join(dir, ".cursorrules");
1752
- if (fs14.existsSync(cursorrulesPath)) {
1868
+ const cursorrulesPath = path14.join(dir, ".cursorrules");
1869
+ if (fs16.existsSync(cursorrulesPath)) {
1753
1870
  items.push({
1754
1871
  type: "rule",
1755
1872
  platform: "cursor",
@@ -1758,10 +1875,10 @@ function scanLocalState(dir) {
1758
1875
  path: cursorrulesPath
1759
1876
  });
1760
1877
  }
1761
- const cursorRulesDir = path12.join(dir, ".cursor", "rules");
1762
- if (fs14.existsSync(cursorRulesDir)) {
1763
- for (const file of fs14.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
1764
- const filePath = path12.join(cursorRulesDir, file);
1878
+ const cursorRulesDir = path14.join(dir, ".cursor", "rules");
1879
+ if (fs16.existsSync(cursorRulesDir)) {
1880
+ for (const file of fs16.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
1881
+ const filePath = path14.join(cursorRulesDir, file);
1765
1882
  items.push({
1766
1883
  type: "rule",
1767
1884
  platform: "cursor",
@@ -1771,10 +1888,10 @@ function scanLocalState(dir) {
1771
1888
  });
1772
1889
  }
1773
1890
  }
1774
- const cursorMcpPath = path12.join(dir, ".cursor", "mcp.json");
1775
- if (fs14.existsSync(cursorMcpPath)) {
1891
+ const cursorMcpPath = path14.join(dir, ".cursor", "mcp.json");
1892
+ if (fs16.existsSync(cursorMcpPath)) {
1776
1893
  try {
1777
- const mcpJson = JSON.parse(fs14.readFileSync(cursorMcpPath, "utf-8"));
1894
+ const mcpJson = JSON.parse(fs16.readFileSync(cursorMcpPath, "utf-8"));
1778
1895
  if (mcpJson.mcpServers) {
1779
1896
  for (const name of Object.keys(mcpJson.mcpServers)) {
1780
1897
  items.push({
@@ -1818,7 +1935,7 @@ function compareState(serverItems, localItems) {
1818
1935
  return { installed, missing, outdated, extra };
1819
1936
  }
1820
1937
  function hashFile(filePath) {
1821
- const content = fs14.readFileSync(filePath);
1938
+ const content = fs16.readFileSync(filePath);
1822
1939
  return crypto4.createHash("sha256").update(content).digest("hex");
1823
1940
  }
1824
1941
  function hashContent(content) {
@@ -1850,7 +1967,7 @@ async function statusCommand(options) {
1850
1967
  }
1851
1968
  console.log(` Files managed: ${chalk4.cyan(manifest.entries.length.toString())}`);
1852
1969
  for (const entry of manifest.entries) {
1853
- const exists = fs15.existsSync(entry.path);
1970
+ const exists = fs17.existsSync(entry.path);
1854
1971
  const icon = exists ? chalk4.green("\u2713") : chalk4.red("\u2717");
1855
1972
  console.log(` ${icon} ${entry.path} (${entry.action})`);
1856
1973
  }
@@ -2401,10 +2518,243 @@ async function diffCommand(options) {
2401
2518
  }
2402
2519
  }
2403
2520
 
2521
+ // src/commands/refresh.ts
2522
+ import chalk11 from "chalk";
2523
+ import ora9 from "ora";
2524
+
2525
+ // src/lib/git-diff.ts
2526
+ import { execSync as execSync3 } from "child_process";
2527
+ var MAX_DIFF_BYTES = 1e5;
2528
+ var DOC_PATTERNS = [
2529
+ "CLAUDE.md",
2530
+ "README.md",
2531
+ ".cursorrules",
2532
+ ".cursor/rules/",
2533
+ ".claude/skills/"
2534
+ ];
2535
+ function excludeArgs() {
2536
+ return DOC_PATTERNS.flatMap((p) => ["--", `:!${p}`]);
2537
+ }
2538
+ function safeExec(cmd) {
2539
+ try {
2540
+ return execSync3(cmd, {
2541
+ encoding: "utf-8",
2542
+ stdio: ["pipe", "pipe", "pipe"],
2543
+ maxBuffer: 10 * 1024 * 1024
2544
+ }).trim();
2545
+ } catch {
2546
+ return "";
2547
+ }
2548
+ }
2549
+ function collectDiff(lastSha) {
2550
+ let committedDiff = "";
2551
+ let stagedDiff = "";
2552
+ let unstagedDiff = "";
2553
+ let changedFiles = [];
2554
+ if (lastSha) {
2555
+ committedDiff = safeExec(`git diff ${lastSha}..HEAD ${excludeArgs().join(" ")}`);
2556
+ const committedFiles = safeExec(`git diff --name-only ${lastSha}..HEAD`);
2557
+ if (committedFiles) {
2558
+ changedFiles.push(...committedFiles.split("\n").filter(Boolean));
2559
+ }
2560
+ } else {
2561
+ committedDiff = safeExec("git log --oneline -20");
2562
+ }
2563
+ stagedDiff = safeExec(`git diff --cached ${excludeArgs().join(" ")}`);
2564
+ unstagedDiff = safeExec(`git diff ${excludeArgs().join(" ")}`);
2565
+ const stagedFiles = safeExec("git diff --cached --name-only");
2566
+ if (stagedFiles) {
2567
+ changedFiles.push(...stagedFiles.split("\n").filter(Boolean));
2568
+ }
2569
+ const unstagedFiles = safeExec("git diff --name-only");
2570
+ if (unstagedFiles) {
2571
+ changedFiles.push(...unstagedFiles.split("\n").filter(Boolean));
2572
+ }
2573
+ changedFiles = [...new Set(changedFiles)].filter(
2574
+ (f) => !DOC_PATTERNS.some((p) => f === p || f.startsWith(p))
2575
+ );
2576
+ const totalSize = committedDiff.length + stagedDiff.length + unstagedDiff.length;
2577
+ if (totalSize > MAX_DIFF_BYTES) {
2578
+ const ratio = MAX_DIFF_BYTES / totalSize;
2579
+ committedDiff = committedDiff.slice(0, Math.floor(committedDiff.length * ratio));
2580
+ stagedDiff = stagedDiff.slice(0, Math.floor(stagedDiff.length * ratio));
2581
+ unstagedDiff = unstagedDiff.slice(0, Math.floor(unstagedDiff.length * ratio));
2582
+ }
2583
+ const hasChanges = !!(committedDiff || stagedDiff || unstagedDiff || changedFiles.length);
2584
+ const parts = [];
2585
+ if (changedFiles.length) parts.push(`${changedFiles.length} files changed`);
2586
+ if (committedDiff) parts.push("committed changes");
2587
+ if (stagedDiff) parts.push("staged changes");
2588
+ if (unstagedDiff) parts.push("unstaged changes");
2589
+ const summary = parts.join(", ") || "no changes";
2590
+ return { hasChanges, committedDiff, stagedDiff, unstagedDiff, changedFiles, summary };
2591
+ }
2592
+
2593
+ // src/writers/refresh.ts
2594
+ import fs18 from "fs";
2595
+ import path15 from "path";
2596
+ function writeRefreshDocs(docs) {
2597
+ const written = [];
2598
+ if (docs.claudeMd) {
2599
+ fs18.writeFileSync("CLAUDE.md", docs.claudeMd);
2600
+ written.push("CLAUDE.md");
2601
+ }
2602
+ if (docs.readmeMd) {
2603
+ fs18.writeFileSync("README.md", docs.readmeMd);
2604
+ written.push("README.md");
2605
+ }
2606
+ if (docs.cursorrules) {
2607
+ fs18.writeFileSync(".cursorrules", docs.cursorrules);
2608
+ written.push(".cursorrules");
2609
+ }
2610
+ if (docs.cursorRules) {
2611
+ const rulesDir = path15.join(".cursor", "rules");
2612
+ if (!fs18.existsSync(rulesDir)) fs18.mkdirSync(rulesDir, { recursive: true });
2613
+ for (const rule of docs.cursorRules) {
2614
+ const filePath = path15.join(rulesDir, rule.filename);
2615
+ fs18.writeFileSync(filePath, rule.content);
2616
+ written.push(filePath);
2617
+ }
2618
+ }
2619
+ if (docs.claudeSkills) {
2620
+ const skillsDir = path15.join(".claude", "skills");
2621
+ if (!fs18.existsSync(skillsDir)) fs18.mkdirSync(skillsDir, { recursive: true });
2622
+ for (const skill of docs.claudeSkills) {
2623
+ const filePath = path15.join(skillsDir, skill.filename);
2624
+ fs18.writeFileSync(filePath, skill.content);
2625
+ written.push(filePath);
2626
+ }
2627
+ }
2628
+ return written;
2629
+ }
2630
+
2631
+ // src/commands/refresh.ts
2632
+ function log(quiet, ...args) {
2633
+ if (!quiet) console.log(...args);
2634
+ }
2635
+ async function refreshCommand(options) {
2636
+ const quiet = !!options.quiet;
2637
+ try {
2638
+ const auth2 = getStoredAuth();
2639
+ if (!auth2) {
2640
+ if (quiet) return;
2641
+ console.log(chalk11.red("Not authenticated. Run `caliber login` first."));
2642
+ throw new Error("__exit__");
2643
+ }
2644
+ if (!isGitRepo()) {
2645
+ if (quiet) return;
2646
+ console.log(chalk11.red("Not inside a git repository."));
2647
+ throw new Error("__exit__");
2648
+ }
2649
+ const state = readState();
2650
+ const lastSha = state?.lastRefreshSha ?? null;
2651
+ const diff = collectDiff(lastSha);
2652
+ const currentSha = getCurrentHeadSha();
2653
+ if (!diff.hasChanges) {
2654
+ if (currentSha) {
2655
+ writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
2656
+ }
2657
+ log(quiet, chalk11.dim("No changes since last refresh."));
2658
+ return;
2659
+ }
2660
+ const spinner = quiet ? null : ora9("Analyzing changes...").start();
2661
+ const existingDocs = readExistingConfigs(process.cwd());
2662
+ const fingerprint = collectFingerprint(process.cwd());
2663
+ const projectContext = {
2664
+ languages: fingerprint.languages,
2665
+ frameworks: fingerprint.frameworks,
2666
+ packageName: fingerprint.packageName
2667
+ };
2668
+ const response = await apiRequest("/api/setups/refresh", {
2669
+ method: "POST",
2670
+ body: {
2671
+ diff: {
2672
+ committed: diff.committedDiff,
2673
+ staged: diff.stagedDiff,
2674
+ unstaged: diff.unstagedDiff,
2675
+ changedFiles: diff.changedFiles,
2676
+ summary: diff.summary
2677
+ },
2678
+ existingDocs,
2679
+ projectContext
2680
+ }
2681
+ });
2682
+ if (!response.docsUpdated || response.docsUpdated.length === 0) {
2683
+ spinner?.succeed("No doc updates needed");
2684
+ if (currentSha) {
2685
+ writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
2686
+ }
2687
+ return;
2688
+ }
2689
+ if (options.dryRun) {
2690
+ spinner?.info("Dry run \u2014 would update:");
2691
+ for (const doc of response.docsUpdated) {
2692
+ console.log(` ${chalk11.yellow("~")} ${doc}`);
2693
+ }
2694
+ if (response.changesSummary) {
2695
+ console.log(chalk11.dim(`
2696
+ ${response.changesSummary}`));
2697
+ }
2698
+ return;
2699
+ }
2700
+ const written = writeRefreshDocs(response.updatedDocs);
2701
+ spinner?.succeed(`Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
2702
+ for (const file of written) {
2703
+ log(quiet, ` ${chalk11.green("\u2713")} ${file}`);
2704
+ }
2705
+ if (response.changesSummary) {
2706
+ log(quiet, chalk11.dim(`
2707
+ ${response.changesSummary}`));
2708
+ }
2709
+ if (currentSha) {
2710
+ writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
2711
+ }
2712
+ trackEvent("refresh_completed", {
2713
+ docs_updated: written.length,
2714
+ changed_files: diff.changedFiles.length
2715
+ });
2716
+ } catch (err) {
2717
+ if (err instanceof Error && err.message === "__exit__") throw err;
2718
+ if (quiet) return;
2719
+ const msg = err instanceof Error ? err.message : "Unknown error";
2720
+ console.log(chalk11.red(`Refresh failed: ${msg}`));
2721
+ throw new Error("__exit__");
2722
+ }
2723
+ }
2724
+
2725
+ // src/commands/hooks.ts
2726
+ import chalk12 from "chalk";
2727
+ async function hooksInstallCommand() {
2728
+ const result = installHook();
2729
+ if (result.alreadyInstalled) {
2730
+ console.log(chalk12.dim("Hook already installed."));
2731
+ return;
2732
+ }
2733
+ console.log(chalk12.green("\u2713") + " SessionEnd hook installed in .claude/settings.json");
2734
+ console.log(chalk12.dim(" Docs will auto-refresh when Claude Code sessions end."));
2735
+ }
2736
+ async function hooksRemoveCommand() {
2737
+ const result = removeHook();
2738
+ if (result.notFound) {
2739
+ console.log(chalk12.dim("Hook not found."));
2740
+ return;
2741
+ }
2742
+ console.log(chalk12.green("\u2713") + " SessionEnd hook removed from .claude/settings.json");
2743
+ }
2744
+ async function hooksStatusCommand() {
2745
+ const installed = isHookInstalled();
2746
+ if (installed) {
2747
+ console.log(chalk12.green("\u2713") + " Auto-refresh hook is " + chalk12.green("installed"));
2748
+ } else {
2749
+ console.log(chalk12.dim("\u2717") + " Auto-refresh hook is " + chalk12.yellow("not installed"));
2750
+ console.log(chalk12.dim(" Run `caliber hooks install` to enable auto-refresh on session end."));
2751
+ }
2752
+ }
2753
+
2404
2754
  // src/cli.ts
2405
- var __dirname2 = path13.dirname(fileURLToPath3(import.meta.url));
2755
+ var __dirname2 = path16.dirname(fileURLToPath3(import.meta.url));
2406
2756
  var pkg3 = JSON.parse(
2407
- fs16.readFileSync(path13.resolve(__dirname2, "..", "package.json"), "utf-8")
2757
+ fs19.readFileSync(path16.resolve(__dirname2, "..", "package.json"), "utf-8")
2408
2758
  );
2409
2759
  var program = new Command();
2410
2760
  program.name("caliber").description("Configure your coding agent environment").version(pkg3.version);
@@ -2418,23 +2768,28 @@ program.command("recommend").description("Discover and manage skill recommendati
2418
2768
  program.command("health").description("Analyze context health and quality").option("--fix", "Generate and execute a fix plan").option("--json", "Output as JSON").action(healthCommand);
2419
2769
  program.command("sync").description("Sync local config with server state").option("--platform <platform>", "Target platform: claude, cursor, or both").option("--dry-run", "Preview changes without writing files").action(syncCommand);
2420
2770
  program.command("diff").description("Compare local config with server state").option("--platform <platform>", "Target platform: claude, cursor, or both").action(diffCommand);
2771
+ program.command("refresh").description("Update docs based on recent code changes").option("--quiet", "Suppress output (for use in hooks)").option("--dry-run", "Preview changes without writing files").action(refreshCommand);
2772
+ var hooks = program.command("hooks").description("Manage Claude Code session hooks");
2773
+ hooks.command("install").description("Install auto-refresh SessionEnd hook").action(hooksInstallCommand);
2774
+ hooks.command("remove").description("Remove auto-refresh SessionEnd hook").action(hooksRemoveCommand);
2775
+ hooks.command("status").description("Check if auto-refresh hook is installed").action(hooksStatusCommand);
2421
2776
 
2422
2777
  // src/utils/version-check.ts
2423
- import fs17 from "fs";
2424
- import path14 from "path";
2778
+ import fs20 from "fs";
2779
+ import path17 from "path";
2425
2780
  import { fileURLToPath as fileURLToPath4 } from "url";
2426
2781
  import readline3 from "readline";
2427
- import { execSync as execSync2 } from "child_process";
2428
- import chalk11 from "chalk";
2429
- import ora9 from "ora";
2430
- var __dirname_vc = path14.dirname(fileURLToPath4(import.meta.url));
2782
+ import { execSync as execSync4 } from "child_process";
2783
+ import chalk13 from "chalk";
2784
+ import ora10 from "ora";
2785
+ var __dirname_vc = path17.dirname(fileURLToPath4(import.meta.url));
2431
2786
  var pkg4 = JSON.parse(
2432
- fs17.readFileSync(path14.resolve(__dirname_vc, "..", "package.json"), "utf-8")
2787
+ fs20.readFileSync(path17.resolve(__dirname_vc, "..", "package.json"), "utf-8")
2433
2788
  );
2434
2789
  function promptYesNo(question) {
2435
2790
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
2436
2791
  return new Promise((resolve2) => {
2437
- rl.question(chalk11.cyan(`${question} `), (answer) => {
2792
+ rl.question(chalk13.cyan(`${question} `), (answer) => {
2438
2793
  rl.close();
2439
2794
  const normalized = answer.trim().toLowerCase();
2440
2795
  resolve2(normalized === "" || normalized === "y" || normalized === "yes");
@@ -2458,17 +2813,17 @@ async function checkForUpdates() {
2458
2813
  const isInteractive = process.stdin.isTTY === true;
2459
2814
  if (!isInteractive) {
2460
2815
  console.log(
2461
- chalk11.yellow(
2816
+ chalk13.yellow(
2462
2817
  `
2463
2818
  Update available: ${current} -> ${latest}
2464
- Run ${chalk11.bold("npm install -g @caliber-ai/cli")} to upgrade.
2819
+ Run ${chalk13.bold("npm install -g @caliber-ai/cli")} to upgrade.
2465
2820
  `
2466
2821
  )
2467
2822
  );
2468
2823
  return;
2469
2824
  }
2470
2825
  console.log(
2471
- chalk11.yellow(`
2826
+ chalk13.yellow(`
2472
2827
  Update available: ${current} -> ${latest}`)
2473
2828
  );
2474
2829
  const shouldUpdate = await promptYesNo("Would you like to update now? (Y/n)");
@@ -2476,23 +2831,23 @@ Update available: ${current} -> ${latest}`)
2476
2831
  console.log();
2477
2832
  return;
2478
2833
  }
2479
- const spinner = ora9("Updating @caliber-ai/cli...").start();
2834
+ const spinner = ora10("Updating @caliber-ai/cli...").start();
2480
2835
  try {
2481
- execSync2("npm install -g @caliber-ai/cli", { stdio: "pipe" });
2482
- spinner.succeed(chalk11.green(`Updated to ${latest}`));
2836
+ execSync4("npm install -g @caliber-ai/cli", { stdio: "pipe" });
2837
+ spinner.succeed(chalk13.green(`Updated to ${latest}`));
2483
2838
  const args = process.argv.slice(2);
2484
- console.log(chalk11.dim(`
2839
+ console.log(chalk13.dim(`
2485
2840
  Restarting: caliber ${args.join(" ")}
2486
2841
  `));
2487
- execSync2(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
2842
+ execSync4(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
2488
2843
  stdio: "inherit"
2489
2844
  });
2490
2845
  process.exit(0);
2491
2846
  } catch {
2492
2847
  spinner.fail("Update failed");
2493
2848
  console.log(
2494
- chalk11.yellow(
2495
- `Run ${chalk11.bold("npm install -g @caliber-ai/cli")} manually to upgrade.
2849
+ chalk13.yellow(
2850
+ `Run ${chalk13.bold("npm install -g @caliber-ai/cli")} manually to upgrade.
2496
2851
  `
2497
2852
  )
2498
2853
  );