@caliber-ai/cli 0.4.2 → 0.5.1
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 +410 -55
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
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
|
|
79
|
-
import
|
|
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
|
|
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(
|
|
938
|
+
async function apiRequest(path18, options = {}) {
|
|
932
939
|
let token = await getValidToken();
|
|
933
|
-
let resp = await fetch(`${API_URL}${
|
|
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}${
|
|
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(
|
|
970
|
+
async function apiStream(path18, body, onChunk, onComplete, onError, onStatus) {
|
|
964
971
|
let token = await getValidToken();
|
|
965
|
-
let resp = await fetch(`${API_URL}${
|
|
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}${
|
|
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 =
|
|
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
|
|
1809
|
+
import fs17 from "fs";
|
|
1693
1810
|
|
|
1694
1811
|
// src/scanner/index.ts
|
|
1695
|
-
import
|
|
1696
|
-
import
|
|
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 =
|
|
1701
|
-
if (
|
|
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 =
|
|
1711
|
-
if (
|
|
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 =
|
|
1721
|
-
if (
|
|
1722
|
-
for (const file of
|
|
1723
|
-
const filePath =
|
|
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 =
|
|
1734
|
-
if (
|
|
1850
|
+
const mcpJsonPath = path14.join(dir, ".mcp.json");
|
|
1851
|
+
if (fs16.existsSync(mcpJsonPath)) {
|
|
1735
1852
|
try {
|
|
1736
|
-
const mcpJson = JSON.parse(
|
|
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 =
|
|
1752
|
-
if (
|
|
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 =
|
|
1762
|
-
if (
|
|
1763
|
-
for (const file of
|
|
1764
|
-
const filePath =
|
|
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 =
|
|
1775
|
-
if (
|
|
1891
|
+
const cursorMcpPath = path14.join(dir, ".cursor", "mcp.json");
|
|
1892
|
+
if (fs16.existsSync(cursorMcpPath)) {
|
|
1776
1893
|
try {
|
|
1777
|
-
const mcpJson = JSON.parse(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
2755
|
+
var __dirname2 = path16.dirname(fileURLToPath3(import.meta.url));
|
|
2406
2756
|
var pkg3 = JSON.parse(
|
|
2407
|
-
|
|
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
|
|
2424
|
-
import
|
|
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
|
|
2428
|
-
import
|
|
2429
|
-
import
|
|
2430
|
-
var __dirname_vc =
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
2816
|
+
chalk13.yellow(
|
|
2462
2817
|
`
|
|
2463
2818
|
Update available: ${current} -> ${latest}
|
|
2464
|
-
Run ${
|
|
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
|
-
|
|
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 =
|
|
2834
|
+
const spinner = ora10("Updating @caliber-ai/cli...").start();
|
|
2480
2835
|
try {
|
|
2481
|
-
|
|
2482
|
-
spinner.succeed(
|
|
2836
|
+
execSync4("npm install -g @caliber-ai/cli --force", { stdio: "pipe" });
|
|
2837
|
+
spinner.succeed(chalk13.green(`Updated to ${latest}`));
|
|
2483
2838
|
const args = process.argv.slice(2);
|
|
2484
|
-
console.log(
|
|
2839
|
+
console.log(chalk13.dim(`
|
|
2485
2840
|
Restarting: caliber ${args.join(" ")}
|
|
2486
2841
|
`));
|
|
2487
|
-
|
|
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
|
-
|
|
2495
|
-
`Run ${
|
|
2849
|
+
chalk13.yellow(
|
|
2850
|
+
`Run ${chalk13.bold("npm install -g @caliber-ai/cli")} manually to upgrade.
|
|
2496
2851
|
`
|
|
2497
2852
|
)
|
|
2498
2853
|
);
|