@caliber-ai/cli 0.11.3 → 0.12.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 +582 -519
- package/dist/bin.js.map +1 -1
- package/package.json +3 -1
package/dist/bin.js
CHANGED
|
@@ -80,10 +80,11 @@ import path17 from "path";
|
|
|
80
80
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
81
81
|
|
|
82
82
|
// src/commands/init.ts
|
|
83
|
-
import
|
|
83
|
+
import chalk3 from "chalk";
|
|
84
84
|
import ora2 from "ora";
|
|
85
85
|
import readline from "readline";
|
|
86
|
-
import
|
|
86
|
+
import select from "@inquirer/select";
|
|
87
|
+
import fs16 from "fs";
|
|
87
88
|
|
|
88
89
|
// src/auth/token-store.ts
|
|
89
90
|
init_constants();
|
|
@@ -598,6 +599,26 @@ function readExistingConfigs(dir) {
|
|
|
598
599
|
} catch {
|
|
599
600
|
}
|
|
600
601
|
}
|
|
602
|
+
const mcpJsonPath = path7.join(dir, ".mcp.json");
|
|
603
|
+
if (fs6.existsSync(mcpJsonPath)) {
|
|
604
|
+
try {
|
|
605
|
+
const mcpJson = JSON.parse(fs6.readFileSync(mcpJsonPath, "utf-8"));
|
|
606
|
+
if (mcpJson.mcpServers) {
|
|
607
|
+
configs.claudeMcpServers = mcpJson.mcpServers;
|
|
608
|
+
}
|
|
609
|
+
} catch {
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const cursorMcpPath = path7.join(dir, ".cursor", "mcp.json");
|
|
613
|
+
if (fs6.existsSync(cursorMcpPath)) {
|
|
614
|
+
try {
|
|
615
|
+
const cursorMcpJson = JSON.parse(fs6.readFileSync(cursorMcpPath, "utf-8"));
|
|
616
|
+
if (cursorMcpJson.mcpServers) {
|
|
617
|
+
configs.cursorMcpServers = cursorMcpJson.mcpServers;
|
|
618
|
+
}
|
|
619
|
+
} catch {
|
|
620
|
+
}
|
|
621
|
+
}
|
|
601
622
|
return configs;
|
|
602
623
|
}
|
|
603
624
|
|
|
@@ -899,6 +920,140 @@ function computeFingerprintHash(fingerprint) {
|
|
|
899
920
|
return crypto2.createHash("sha256").update(key).digest("hex");
|
|
900
921
|
}
|
|
901
922
|
|
|
923
|
+
// src/scanner/index.ts
|
|
924
|
+
import fs8 from "fs";
|
|
925
|
+
import path9 from "path";
|
|
926
|
+
import crypto3 from "crypto";
|
|
927
|
+
function scanLocalState(dir) {
|
|
928
|
+
const items = [];
|
|
929
|
+
const claudeMdPath = path9.join(dir, "CLAUDE.md");
|
|
930
|
+
if (fs8.existsSync(claudeMdPath)) {
|
|
931
|
+
items.push({
|
|
932
|
+
type: "rule",
|
|
933
|
+
platform: "claude",
|
|
934
|
+
name: "CLAUDE.md",
|
|
935
|
+
contentHash: hashFile(claudeMdPath),
|
|
936
|
+
path: claudeMdPath
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
const settingsPath = path9.join(dir, ".claude", "settings.json");
|
|
940
|
+
if (fs8.existsSync(settingsPath)) {
|
|
941
|
+
items.push({
|
|
942
|
+
type: "config",
|
|
943
|
+
platform: "claude",
|
|
944
|
+
name: "settings.json",
|
|
945
|
+
contentHash: hashFile(settingsPath),
|
|
946
|
+
path: settingsPath
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
const skillsDir = path9.join(dir, ".claude", "skills");
|
|
950
|
+
if (fs8.existsSync(skillsDir)) {
|
|
951
|
+
for (const file of fs8.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
952
|
+
const filePath = path9.join(skillsDir, file);
|
|
953
|
+
items.push({
|
|
954
|
+
type: "skill",
|
|
955
|
+
platform: "claude",
|
|
956
|
+
name: file,
|
|
957
|
+
contentHash: hashFile(filePath),
|
|
958
|
+
path: filePath
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
const mcpJsonPath = path9.join(dir, ".mcp.json");
|
|
963
|
+
if (fs8.existsSync(mcpJsonPath)) {
|
|
964
|
+
try {
|
|
965
|
+
const mcpJson = JSON.parse(fs8.readFileSync(mcpJsonPath, "utf-8"));
|
|
966
|
+
if (mcpJson.mcpServers) {
|
|
967
|
+
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
968
|
+
items.push({
|
|
969
|
+
type: "mcp",
|
|
970
|
+
platform: "claude",
|
|
971
|
+
name,
|
|
972
|
+
contentHash: hashContent(JSON.stringify(mcpJson.mcpServers[name])),
|
|
973
|
+
path: mcpJsonPath
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
} catch {
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
const cursorrulesPath = path9.join(dir, ".cursorrules");
|
|
981
|
+
if (fs8.existsSync(cursorrulesPath)) {
|
|
982
|
+
items.push({
|
|
983
|
+
type: "rule",
|
|
984
|
+
platform: "cursor",
|
|
985
|
+
name: ".cursorrules",
|
|
986
|
+
contentHash: hashFile(cursorrulesPath),
|
|
987
|
+
path: cursorrulesPath
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
const cursorRulesDir = path9.join(dir, ".cursor", "rules");
|
|
991
|
+
if (fs8.existsSync(cursorRulesDir)) {
|
|
992
|
+
for (const file of fs8.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
993
|
+
const filePath = path9.join(cursorRulesDir, file);
|
|
994
|
+
items.push({
|
|
995
|
+
type: "rule",
|
|
996
|
+
platform: "cursor",
|
|
997
|
+
name: file,
|
|
998
|
+
contentHash: hashFile(filePath),
|
|
999
|
+
path: filePath
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
const cursorMcpPath = path9.join(dir, ".cursor", "mcp.json");
|
|
1004
|
+
if (fs8.existsSync(cursorMcpPath)) {
|
|
1005
|
+
try {
|
|
1006
|
+
const mcpJson = JSON.parse(fs8.readFileSync(cursorMcpPath, "utf-8"));
|
|
1007
|
+
if (mcpJson.mcpServers) {
|
|
1008
|
+
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
1009
|
+
items.push({
|
|
1010
|
+
type: "mcp",
|
|
1011
|
+
platform: "cursor",
|
|
1012
|
+
name,
|
|
1013
|
+
contentHash: hashContent(JSON.stringify(mcpJson.mcpServers[name])),
|
|
1014
|
+
path: cursorMcpPath
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
} catch {
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
return items;
|
|
1022
|
+
}
|
|
1023
|
+
function compareState(serverItems, localItems) {
|
|
1024
|
+
const installed = [];
|
|
1025
|
+
const missing = [];
|
|
1026
|
+
const outdated = [];
|
|
1027
|
+
const extra = [];
|
|
1028
|
+
const localMap = /* @__PURE__ */ new Map();
|
|
1029
|
+
for (const item of localItems) {
|
|
1030
|
+
localMap.set(`${item.type}:${item.platform}:${item.name}`, item);
|
|
1031
|
+
}
|
|
1032
|
+
for (const server of serverItems) {
|
|
1033
|
+
const key = `${server.type}:${server.platform}:${server.name}`;
|
|
1034
|
+
const local = localMap.get(key);
|
|
1035
|
+
localMap.delete(key);
|
|
1036
|
+
if (!local) {
|
|
1037
|
+
missing.push(server);
|
|
1038
|
+
} else if (local.contentHash !== server.content_hash) {
|
|
1039
|
+
outdated.push({ server, local });
|
|
1040
|
+
} else {
|
|
1041
|
+
installed.push({ server, local });
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
for (const local of localMap.values()) {
|
|
1045
|
+
extra.push(local);
|
|
1046
|
+
}
|
|
1047
|
+
return { installed, missing, outdated, extra };
|
|
1048
|
+
}
|
|
1049
|
+
function hashFile(filePath) {
|
|
1050
|
+
const content = fs8.readFileSync(filePath);
|
|
1051
|
+
return crypto3.createHash("sha256").update(content).digest("hex");
|
|
1052
|
+
}
|
|
1053
|
+
function hashContent(content) {
|
|
1054
|
+
return crypto3.createHash("sha256").update(JSON.stringify({ text: content })).digest("hex");
|
|
1055
|
+
}
|
|
1056
|
+
|
|
902
1057
|
// src/api/client.ts
|
|
903
1058
|
init_constants();
|
|
904
1059
|
async function forceRefreshToken() {
|
|
@@ -1021,132 +1176,150 @@ async function apiStream(path19, body, onChunk, onComplete, onError, onStatus) {
|
|
|
1021
1176
|
}
|
|
1022
1177
|
|
|
1023
1178
|
// src/writers/index.ts
|
|
1024
|
-
import
|
|
1179
|
+
import fs13 from "fs";
|
|
1025
1180
|
|
|
1026
1181
|
// src/writers/claude/index.ts
|
|
1027
|
-
import
|
|
1028
|
-
import
|
|
1182
|
+
import fs9 from "fs";
|
|
1183
|
+
import path10 from "path";
|
|
1029
1184
|
function writeClaudeConfig(config) {
|
|
1030
1185
|
const written = [];
|
|
1031
|
-
|
|
1186
|
+
fs9.writeFileSync("CLAUDE.md", config.claudeMd);
|
|
1032
1187
|
written.push("CLAUDE.md");
|
|
1033
1188
|
const claudeDir = ".claude";
|
|
1034
|
-
if (!
|
|
1035
|
-
|
|
1036
|
-
|
|
1189
|
+
if (!fs9.existsSync(claudeDir)) fs9.mkdirSync(claudeDir, { recursive: true });
|
|
1190
|
+
fs9.writeFileSync(
|
|
1191
|
+
path10.join(claudeDir, "settings.json"),
|
|
1037
1192
|
JSON.stringify(config.settings, null, 2)
|
|
1038
1193
|
);
|
|
1039
|
-
written.push(
|
|
1040
|
-
|
|
1041
|
-
|
|
1194
|
+
written.push(path10.join(claudeDir, "settings.json"));
|
|
1195
|
+
fs9.writeFileSync(
|
|
1196
|
+
path10.join(claudeDir, "settings.local.json"),
|
|
1042
1197
|
JSON.stringify(config.settingsLocal, null, 2)
|
|
1043
1198
|
);
|
|
1044
|
-
written.push(
|
|
1199
|
+
written.push(path10.join(claudeDir, "settings.local.json"));
|
|
1045
1200
|
if (config.skills?.length) {
|
|
1046
|
-
const skillsDir =
|
|
1047
|
-
if (!
|
|
1201
|
+
const skillsDir = path10.join(claudeDir, "skills");
|
|
1202
|
+
if (!fs9.existsSync(skillsDir)) fs9.mkdirSync(skillsDir, { recursive: true });
|
|
1048
1203
|
for (const skill of config.skills) {
|
|
1049
1204
|
const filename = `${skill.name.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}.md`;
|
|
1050
|
-
const skillPath =
|
|
1051
|
-
|
|
1205
|
+
const skillPath = path10.join(skillsDir, filename);
|
|
1206
|
+
fs9.writeFileSync(skillPath, skill.content);
|
|
1052
1207
|
written.push(skillPath);
|
|
1053
1208
|
}
|
|
1054
1209
|
}
|
|
1055
1210
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
1056
|
-
|
|
1057
|
-
|
|
1211
|
+
let existingServers = {};
|
|
1212
|
+
try {
|
|
1213
|
+
if (fs9.existsSync(".mcp.json")) {
|
|
1214
|
+
const existing = JSON.parse(fs9.readFileSync(".mcp.json", "utf-8"));
|
|
1215
|
+
if (existing.mcpServers) {
|
|
1216
|
+
existingServers = existing.mcpServers;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
} catch {
|
|
1220
|
+
}
|
|
1221
|
+
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
1222
|
+
fs9.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
1058
1223
|
written.push(".mcp.json");
|
|
1059
1224
|
}
|
|
1060
1225
|
return written;
|
|
1061
1226
|
}
|
|
1062
1227
|
|
|
1063
1228
|
// src/writers/cursor/index.ts
|
|
1064
|
-
import
|
|
1065
|
-
import
|
|
1229
|
+
import fs10 from "fs";
|
|
1230
|
+
import path11 from "path";
|
|
1066
1231
|
function writeCursorConfig(config) {
|
|
1067
1232
|
const written = [];
|
|
1068
1233
|
if (config.cursorrules) {
|
|
1069
|
-
|
|
1234
|
+
fs10.writeFileSync(".cursorrules", config.cursorrules);
|
|
1070
1235
|
written.push(".cursorrules");
|
|
1071
1236
|
}
|
|
1072
1237
|
if (config.rules?.length) {
|
|
1073
|
-
const rulesDir =
|
|
1074
|
-
if (!
|
|
1238
|
+
const rulesDir = path11.join(".cursor", "rules");
|
|
1239
|
+
if (!fs10.existsSync(rulesDir)) fs10.mkdirSync(rulesDir, { recursive: true });
|
|
1075
1240
|
for (const rule of config.rules) {
|
|
1076
|
-
const rulePath =
|
|
1077
|
-
|
|
1241
|
+
const rulePath = path11.join(rulesDir, rule.filename);
|
|
1242
|
+
fs10.writeFileSync(rulePath, rule.content);
|
|
1078
1243
|
written.push(rulePath);
|
|
1079
1244
|
}
|
|
1080
1245
|
}
|
|
1081
1246
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
1082
1247
|
const cursorDir = ".cursor";
|
|
1083
|
-
if (!
|
|
1084
|
-
const
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1248
|
+
if (!fs10.existsSync(cursorDir)) fs10.mkdirSync(cursorDir, { recursive: true });
|
|
1249
|
+
const mcpPath = path11.join(cursorDir, "mcp.json");
|
|
1250
|
+
let existingServers = {};
|
|
1251
|
+
try {
|
|
1252
|
+
if (fs10.existsSync(mcpPath)) {
|
|
1253
|
+
const existing = JSON.parse(fs10.readFileSync(mcpPath, "utf-8"));
|
|
1254
|
+
if (existing.mcpServers) {
|
|
1255
|
+
existingServers = existing.mcpServers;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
} catch {
|
|
1259
|
+
}
|
|
1260
|
+
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
1261
|
+
fs10.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
1262
|
+
written.push(mcpPath);
|
|
1090
1263
|
}
|
|
1091
1264
|
return written;
|
|
1092
1265
|
}
|
|
1093
1266
|
|
|
1094
1267
|
// src/writers/backup.ts
|
|
1095
1268
|
init_constants();
|
|
1096
|
-
import
|
|
1097
|
-
import
|
|
1269
|
+
import fs11 from "fs";
|
|
1270
|
+
import path12 from "path";
|
|
1098
1271
|
function createBackup(files) {
|
|
1099
1272
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1100
|
-
const backupDir =
|
|
1273
|
+
const backupDir = path12.join(BACKUPS_DIR, timestamp);
|
|
1101
1274
|
for (const file of files) {
|
|
1102
|
-
if (!
|
|
1103
|
-
const dest =
|
|
1104
|
-
const destDir =
|
|
1105
|
-
if (!
|
|
1106
|
-
|
|
1275
|
+
if (!fs11.existsSync(file)) continue;
|
|
1276
|
+
const dest = path12.join(backupDir, file);
|
|
1277
|
+
const destDir = path12.dirname(dest);
|
|
1278
|
+
if (!fs11.existsSync(destDir)) {
|
|
1279
|
+
fs11.mkdirSync(destDir, { recursive: true });
|
|
1107
1280
|
}
|
|
1108
|
-
|
|
1281
|
+
fs11.copyFileSync(file, dest);
|
|
1109
1282
|
}
|
|
1110
1283
|
return backupDir;
|
|
1111
1284
|
}
|
|
1112
1285
|
function restoreBackup(backupDir, file) {
|
|
1113
|
-
const backupFile =
|
|
1114
|
-
if (!
|
|
1115
|
-
const destDir =
|
|
1116
|
-
if (!
|
|
1117
|
-
|
|
1286
|
+
const backupFile = path12.join(backupDir, file);
|
|
1287
|
+
if (!fs11.existsSync(backupFile)) return false;
|
|
1288
|
+
const destDir = path12.dirname(file);
|
|
1289
|
+
if (!fs11.existsSync(destDir)) {
|
|
1290
|
+
fs11.mkdirSync(destDir, { recursive: true });
|
|
1118
1291
|
}
|
|
1119
|
-
|
|
1292
|
+
fs11.copyFileSync(backupFile, file);
|
|
1120
1293
|
return true;
|
|
1121
1294
|
}
|
|
1122
1295
|
|
|
1123
1296
|
// src/writers/manifest.ts
|
|
1124
1297
|
init_constants();
|
|
1125
|
-
import
|
|
1126
|
-
import
|
|
1298
|
+
import fs12 from "fs";
|
|
1299
|
+
import crypto4 from "crypto";
|
|
1127
1300
|
function readManifest() {
|
|
1128
1301
|
try {
|
|
1129
|
-
if (!
|
|
1130
|
-
return JSON.parse(
|
|
1302
|
+
if (!fs12.existsSync(MANIFEST_FILE)) return null;
|
|
1303
|
+
return JSON.parse(fs12.readFileSync(MANIFEST_FILE, "utf-8"));
|
|
1131
1304
|
} catch {
|
|
1132
1305
|
return null;
|
|
1133
1306
|
}
|
|
1134
1307
|
}
|
|
1135
1308
|
function writeManifest(manifest) {
|
|
1136
|
-
if (!
|
|
1137
|
-
|
|
1309
|
+
if (!fs12.existsSync(CALIBER_DIR)) {
|
|
1310
|
+
fs12.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
1138
1311
|
}
|
|
1139
|
-
|
|
1312
|
+
fs12.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
|
|
1140
1313
|
}
|
|
1141
1314
|
function fileChecksum(filePath) {
|
|
1142
|
-
const content =
|
|
1143
|
-
return
|
|
1315
|
+
const content = fs12.readFileSync(filePath);
|
|
1316
|
+
return crypto4.createHash("sha256").update(content).digest("hex");
|
|
1144
1317
|
}
|
|
1145
1318
|
|
|
1146
1319
|
// src/writers/index.ts
|
|
1147
1320
|
function writeSetup(setup) {
|
|
1148
1321
|
const filesToWrite = getFilesToWrite(setup);
|
|
1149
|
-
const existingFiles = filesToWrite.filter((f) =>
|
|
1322
|
+
const existingFiles = filesToWrite.filter((f) => fs13.existsSync(f));
|
|
1150
1323
|
const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
|
|
1151
1324
|
const written = [];
|
|
1152
1325
|
if ((setup.targetAgent === "claude" || setup.targetAgent === "both") && setup.claude) {
|
|
@@ -1174,8 +1347,8 @@ function undoSetup() {
|
|
|
1174
1347
|
const removed = [];
|
|
1175
1348
|
for (const entry of manifest.entries) {
|
|
1176
1349
|
if (entry.action === "created") {
|
|
1177
|
-
if (
|
|
1178
|
-
|
|
1350
|
+
if (fs13.existsSync(entry.path)) {
|
|
1351
|
+
fs13.unlinkSync(entry.path);
|
|
1179
1352
|
removed.push(entry.path);
|
|
1180
1353
|
}
|
|
1181
1354
|
} else if (entry.action === "modified" && manifest.backupDir) {
|
|
@@ -1185,8 +1358,8 @@ function undoSetup() {
|
|
|
1185
1358
|
}
|
|
1186
1359
|
}
|
|
1187
1360
|
const { MANIFEST_FILE: MANIFEST_FILE2 } = (init_constants(), __toCommonJS(constants_exports));
|
|
1188
|
-
if (
|
|
1189
|
-
|
|
1361
|
+
if (fs13.existsSync(MANIFEST_FILE2)) {
|
|
1362
|
+
fs13.unlinkSync(MANIFEST_FILE2);
|
|
1190
1363
|
}
|
|
1191
1364
|
return { restored, removed };
|
|
1192
1365
|
}
|
|
@@ -1212,34 +1385,34 @@ function getFilesToWrite(setup) {
|
|
|
1212
1385
|
}
|
|
1213
1386
|
function ensureGitignore() {
|
|
1214
1387
|
const gitignorePath = ".gitignore";
|
|
1215
|
-
if (
|
|
1216
|
-
const content =
|
|
1388
|
+
if (fs13.existsSync(gitignorePath)) {
|
|
1389
|
+
const content = fs13.readFileSync(gitignorePath, "utf-8");
|
|
1217
1390
|
if (!content.includes(".caliber/")) {
|
|
1218
|
-
|
|
1391
|
+
fs13.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
|
|
1219
1392
|
}
|
|
1220
1393
|
} else {
|
|
1221
|
-
|
|
1394
|
+
fs13.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
|
|
1222
1395
|
}
|
|
1223
1396
|
}
|
|
1224
1397
|
|
|
1225
1398
|
// src/lib/hooks.ts
|
|
1226
|
-
import
|
|
1227
|
-
import
|
|
1228
|
-
var SETTINGS_PATH =
|
|
1399
|
+
import fs14 from "fs";
|
|
1400
|
+
import path13 from "path";
|
|
1401
|
+
var SETTINGS_PATH = path13.join(".claude", "settings.json");
|
|
1229
1402
|
var HOOK_COMMAND = "caliber refresh --quiet";
|
|
1230
1403
|
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
1231
1404
|
function readSettings() {
|
|
1232
|
-
if (!
|
|
1405
|
+
if (!fs14.existsSync(SETTINGS_PATH)) return {};
|
|
1233
1406
|
try {
|
|
1234
|
-
return JSON.parse(
|
|
1407
|
+
return JSON.parse(fs14.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
1235
1408
|
} catch {
|
|
1236
1409
|
return {};
|
|
1237
1410
|
}
|
|
1238
1411
|
}
|
|
1239
1412
|
function writeSettings(settings) {
|
|
1240
|
-
const dir =
|
|
1241
|
-
if (!
|
|
1242
|
-
|
|
1413
|
+
const dir = path13.dirname(SETTINGS_PATH);
|
|
1414
|
+
if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
|
|
1415
|
+
fs14.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
1243
1416
|
}
|
|
1244
1417
|
function findHookIndex(sessionEnd) {
|
|
1245
1418
|
return sessionEnd.findIndex(
|
|
@@ -1289,23 +1462,23 @@ function removeHook() {
|
|
|
1289
1462
|
|
|
1290
1463
|
// src/lib/state.ts
|
|
1291
1464
|
init_constants();
|
|
1292
|
-
import
|
|
1293
|
-
import
|
|
1465
|
+
import fs15 from "fs";
|
|
1466
|
+
import path14 from "path";
|
|
1294
1467
|
import { execSync as execSync2 } from "child_process";
|
|
1295
|
-
var STATE_FILE =
|
|
1468
|
+
var STATE_FILE = path14.join(CALIBER_DIR, ".caliber-state.json");
|
|
1296
1469
|
function readState() {
|
|
1297
1470
|
try {
|
|
1298
|
-
if (!
|
|
1299
|
-
return JSON.parse(
|
|
1471
|
+
if (!fs15.existsSync(STATE_FILE)) return null;
|
|
1472
|
+
return JSON.parse(fs15.readFileSync(STATE_FILE, "utf-8"));
|
|
1300
1473
|
} catch {
|
|
1301
1474
|
return null;
|
|
1302
1475
|
}
|
|
1303
1476
|
}
|
|
1304
1477
|
function writeState(state) {
|
|
1305
|
-
if (!
|
|
1306
|
-
|
|
1478
|
+
if (!fs15.existsSync(CALIBER_DIR)) {
|
|
1479
|
+
fs15.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
1307
1480
|
}
|
|
1308
|
-
|
|
1481
|
+
fs15.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
1309
1482
|
}
|
|
1310
1483
|
function getCurrentHeadSha() {
|
|
1311
1484
|
try {
|
|
@@ -1319,6 +1492,7 @@ function getCurrentHeadSha() {
|
|
|
1319
1492
|
}
|
|
1320
1493
|
|
|
1321
1494
|
// src/utils/spinner-messages.ts
|
|
1495
|
+
import chalk2 from "chalk";
|
|
1322
1496
|
var GENERATION_MESSAGES = [
|
|
1323
1497
|
"Analyzing your project structure and dependencies...",
|
|
1324
1498
|
"Mapping out build commands and test workflows...",
|
|
@@ -1344,25 +1518,46 @@ var SpinnerMessages = class {
|
|
|
1344
1518
|
messages;
|
|
1345
1519
|
index = 0;
|
|
1346
1520
|
timer = null;
|
|
1347
|
-
|
|
1521
|
+
startTime = 0;
|
|
1522
|
+
showElapsedTime;
|
|
1523
|
+
currentBaseMessage = "";
|
|
1524
|
+
constructor(spinner, messages, options) {
|
|
1348
1525
|
this.spinner = spinner;
|
|
1349
1526
|
this.messages = messages;
|
|
1527
|
+
this.showElapsedTime = options?.showElapsedTime ?? false;
|
|
1528
|
+
}
|
|
1529
|
+
formatElapsed() {
|
|
1530
|
+
const seconds = Math.floor((Date.now() - this.startTime) / 1e3);
|
|
1531
|
+
const mins = Math.floor(seconds / 60);
|
|
1532
|
+
const secs = seconds % 60;
|
|
1533
|
+
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
1534
|
+
}
|
|
1535
|
+
updateSpinnerText() {
|
|
1536
|
+
this.spinner.text = this.currentBaseMessage;
|
|
1350
1537
|
}
|
|
1351
1538
|
start() {
|
|
1352
1539
|
this.index = 0;
|
|
1353
|
-
this.
|
|
1540
|
+
this.startTime = Date.now();
|
|
1541
|
+
this.currentBaseMessage = this.messages[0];
|
|
1542
|
+
this.updateSpinnerText();
|
|
1543
|
+
if (this.showElapsedTime) {
|
|
1544
|
+
this.spinner.suffixText = () => chalk2.dim(`(${this.formatElapsed()})`);
|
|
1545
|
+
}
|
|
1354
1546
|
this.timer = setInterval(() => {
|
|
1355
1547
|
this.index = (this.index + 1) % this.messages.length;
|
|
1356
|
-
this.
|
|
1548
|
+
this.currentBaseMessage = this.messages[this.index];
|
|
1549
|
+
this.updateSpinnerText();
|
|
1357
1550
|
}, 3e3);
|
|
1358
1551
|
}
|
|
1359
1552
|
handleServerStatus(status) {
|
|
1360
|
-
this.
|
|
1553
|
+
this.currentBaseMessage = status;
|
|
1554
|
+
this.updateSpinnerText();
|
|
1361
1555
|
if (this.timer) {
|
|
1362
1556
|
clearInterval(this.timer);
|
|
1363
1557
|
this.timer = setInterval(() => {
|
|
1364
1558
|
this.index = (this.index + 1) % this.messages.length;
|
|
1365
|
-
this.
|
|
1559
|
+
this.currentBaseMessage = this.messages[this.index];
|
|
1560
|
+
this.updateSpinnerText();
|
|
1366
1561
|
}, 3e3);
|
|
1367
1562
|
}
|
|
1368
1563
|
}
|
|
@@ -1371,12 +1566,13 @@ var SpinnerMessages = class {
|
|
|
1371
1566
|
clearInterval(this.timer);
|
|
1372
1567
|
this.timer = null;
|
|
1373
1568
|
}
|
|
1569
|
+
this.spinner.suffixText = "";
|
|
1374
1570
|
}
|
|
1375
1571
|
};
|
|
1376
1572
|
|
|
1377
1573
|
// src/commands/init.ts
|
|
1378
1574
|
async function initCommand(options) {
|
|
1379
|
-
console.log(
|
|
1575
|
+
console.log(chalk3.bold.hex("#6366f1")(`
|
|
1380
1576
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
1381
1577
|
\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
1382
1578
|
\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
@@ -1384,34 +1580,34 @@ async function initCommand(options) {
|
|
|
1384
1580
|
\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
1385
1581
|
\u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
|
|
1386
1582
|
`));
|
|
1387
|
-
console.log(
|
|
1388
|
-
console.log(
|
|
1389
|
-
console.log(
|
|
1390
|
-
console.log(
|
|
1391
|
-
console.log(
|
|
1392
|
-
console.log(
|
|
1393
|
-
console.log(
|
|
1394
|
-
console.log(
|
|
1395
|
-
console.log(
|
|
1396
|
-
console.log(
|
|
1397
|
-
console.log(
|
|
1398
|
-
console.log(
|
|
1399
|
-
console.log(
|
|
1400
|
-
console.log(
|
|
1583
|
+
console.log(chalk3.dim(" Configure your coding agent environment\n"));
|
|
1584
|
+
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.bold(" How it works:\n"));
|
|
1590
|
+
console.log(chalk3.dim(" 1. Scan Analyze your code, dependencies, and file structure"));
|
|
1591
|
+
console.log(chalk3.dim(" 2. Match Detect existing configs and check for teammate setups"));
|
|
1592
|
+
console.log(chalk3.dim(" 3. Generate AI creates config files tailored to your project"));
|
|
1593
|
+
console.log(chalk3.dim(" 4. Review You accept, refine, or decline the generated setup"));
|
|
1594
|
+
console.log(chalk3.dim(" 5. Apply Config files are written to your project"));
|
|
1595
|
+
console.log(chalk3.dim(" 6. Sync Setup is saved so teammates get the same config\n"));
|
|
1596
|
+
console.log(chalk3.hex("#6366f1").bold(" Step 1/6 \u2014 Authenticate\n"));
|
|
1401
1597
|
let auth2 = getStoredAuth();
|
|
1402
1598
|
if (!auth2) {
|
|
1403
|
-
console.log(
|
|
1599
|
+
console.log(chalk3.yellow("Not logged in. Starting authentication...\n"));
|
|
1404
1600
|
await loginCommand();
|
|
1405
1601
|
auth2 = getStoredAuth();
|
|
1406
1602
|
if (!auth2) {
|
|
1407
|
-
console.log(
|
|
1603
|
+
console.log(chalk3.red("Authentication required. Exiting."));
|
|
1408
1604
|
throw new Error("__exit__");
|
|
1409
1605
|
}
|
|
1410
1606
|
}
|
|
1411
|
-
console.log(
|
|
1607
|
+
console.log(chalk3.dim(`Authenticated as ${auth2.email}
|
|
1412
1608
|
`));
|
|
1413
|
-
console.log(
|
|
1414
|
-
console.log(
|
|
1609
|
+
console.log(chalk3.hex("#6366f1").bold(" Step 2/6 \u2014 Scan project\n"));
|
|
1610
|
+
console.log(chalk3.dim(" Detecting languages, frameworks, file structure, and existing configs.\n"));
|
|
1415
1611
|
const spinner = ora2("Analyzing project...").start();
|
|
1416
1612
|
const fingerprint = collectFingerprint(process.cwd());
|
|
1417
1613
|
const hash = computeFingerprintHash(fingerprint);
|
|
@@ -1425,17 +1621,46 @@ async function initCommand(options) {
|
|
|
1425
1621
|
has_cursorrules: !!fingerprint.existingConfigs.cursorrules,
|
|
1426
1622
|
cursor_rules_count: fingerprint.existingConfigs.cursorRules?.length ?? 0,
|
|
1427
1623
|
skills_count: fingerprint.existingConfigs.claudeSkills?.length ?? 0,
|
|
1624
|
+
has_claude_mcp_servers: !!fingerprint.existingConfigs.claudeMcpServers,
|
|
1625
|
+
has_cursor_mcp_servers: !!fingerprint.existingConfigs.cursorMcpServers,
|
|
1626
|
+
claude_mcp_server_count: fingerprint.existingConfigs.claudeMcpServers ? Object.keys(fingerprint.existingConfigs.claudeMcpServers).length : 0,
|
|
1627
|
+
cursor_mcp_server_count: fingerprint.existingConfigs.cursorMcpServers ? Object.keys(fingerprint.existingConfigs.cursorMcpServers).length : 0,
|
|
1428
1628
|
file_count: fingerprint.fileTree.length
|
|
1429
1629
|
});
|
|
1430
|
-
console.log(
|
|
1431
|
-
console.log(
|
|
1432
|
-
console.log(
|
|
1630
|
+
console.log(chalk3.dim(` Languages: ${fingerprint.languages.join(", ") || "none detected"}`));
|
|
1631
|
+
console.log(chalk3.dim(` Frameworks: ${fingerprint.frameworks.join(", ") || "none detected"}`));
|
|
1632
|
+
console.log(chalk3.dim(` Files: ${fingerprint.fileTree.length} found
|
|
1433
1633
|
`));
|
|
1434
|
-
console.log(
|
|
1435
|
-
console.log(
|
|
1436
|
-
const matchSpinner = ora2("
|
|
1634
|
+
console.log(chalk3.hex("#6366f1").bold(" Step 3/6 \u2014 Match project\n"));
|
|
1635
|
+
console.log(chalk3.dim(" Scanning for existing rules, skills, MCP servers, and teammate setups.\n"));
|
|
1636
|
+
const matchSpinner = ora2("Scanning for existing setup...").start();
|
|
1637
|
+
const localConfigs = [];
|
|
1638
|
+
const ec = fingerprint.existingConfigs;
|
|
1639
|
+
if (ec.claudeMd) localConfigs.push("CLAUDE.md");
|
|
1640
|
+
if (ec.claudeSettings) localConfigs.push(".claude/settings.json");
|
|
1641
|
+
if (Array.isArray(ec.claudeSkills)) {
|
|
1642
|
+
for (const skill of ec.claudeSkills) {
|
|
1643
|
+
localConfigs.push(`.claude/skills/${skill.filename}`);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
if (ec.cursorrules) localConfigs.push(".cursorrules");
|
|
1647
|
+
if (Array.isArray(ec.cursorRules)) {
|
|
1648
|
+
for (const rule of ec.cursorRules) {
|
|
1649
|
+
localConfigs.push(`.cursor/rules/${rule.filename}`);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
const localState = scanLocalState(process.cwd());
|
|
1653
|
+
const claudeMcpServers = localState.filter((i) => i.type === "mcp" && i.platform === "claude").map((i) => i.name);
|
|
1654
|
+
const cursorMcpServers = localState.filter((i) => i.type === "mcp" && i.platform === "cursor").map((i) => i.name);
|
|
1655
|
+
if (claudeMcpServers.length > 0) {
|
|
1656
|
+
localConfigs.push(`.mcp.json (${claudeMcpServers.join(", ")} \u2014 will merge)`);
|
|
1657
|
+
}
|
|
1658
|
+
if (cursorMcpServers.length > 0) {
|
|
1659
|
+
localConfigs.push(`.cursor/mcp.json (${cursorMcpServers.join(", ")} \u2014 will merge)`);
|
|
1660
|
+
}
|
|
1437
1661
|
let existingSetup = null;
|
|
1438
1662
|
let existingProjectId = null;
|
|
1663
|
+
let hasTeammateSetup = false;
|
|
1439
1664
|
try {
|
|
1440
1665
|
const match = await apiRequest("/api/projects/match", {
|
|
1441
1666
|
method: "POST",
|
|
@@ -1446,34 +1671,50 @@ async function initCommand(options) {
|
|
|
1446
1671
|
}
|
|
1447
1672
|
if (match.setup) {
|
|
1448
1673
|
existingSetup = match.setup;
|
|
1674
|
+
hasTeammateSetup = true;
|
|
1449
1675
|
trackEvent("existing_config_detected", {
|
|
1450
1676
|
has_claude_md: !!fingerprint.existingConfigs.claudeMd,
|
|
1451
1677
|
has_cursorrules: !!fingerprint.existingConfigs.cursorrules,
|
|
1452
1678
|
has_cursor_rules: (fingerprint.existingConfigs.cursorRules?.length ?? 0) > 0,
|
|
1453
1679
|
has_skills: (fingerprint.existingConfigs.claudeSkills?.length ?? 0) > 0
|
|
1454
1680
|
});
|
|
1455
|
-
matchSpinner.succeed("Found existing setup");
|
|
1456
|
-
} else {
|
|
1457
|
-
matchSpinner.info("No existing setup found");
|
|
1458
1681
|
}
|
|
1459
1682
|
} catch {
|
|
1460
|
-
matchSpinner.info("No existing setup found");
|
|
1461
1683
|
}
|
|
1684
|
+
matchSpinner.stop();
|
|
1685
|
+
console.log(chalk3.dim(" Local configs:"));
|
|
1686
|
+
if (localConfigs.length > 0) {
|
|
1687
|
+
for (const config of localConfigs) {
|
|
1688
|
+
console.log(` ${chalk3.green("\u2713")} ${config}`);
|
|
1689
|
+
}
|
|
1690
|
+
} else {
|
|
1691
|
+
console.log(` ${chalk3.dim("\u2139 None found")}`);
|
|
1692
|
+
}
|
|
1693
|
+
console.log("");
|
|
1694
|
+
console.log(chalk3.dim(" Teammate setup:"));
|
|
1695
|
+
if (hasTeammateSetup) {
|
|
1696
|
+
console.log(` ${chalk3.green("\u2713")} Found on Caliber`);
|
|
1697
|
+
} else {
|
|
1698
|
+
console.log(` ${chalk3.dim("\u2139 None found")}`);
|
|
1699
|
+
}
|
|
1700
|
+
console.log("");
|
|
1462
1701
|
const targetAgent = options.agent || await promptAgent();
|
|
1463
1702
|
trackEvent("target_agent_selected", { target_agent: targetAgent });
|
|
1464
1703
|
const isEmpty = fingerprint.fileTree.length < 3;
|
|
1465
1704
|
if (isEmpty) {
|
|
1466
1705
|
fingerprint.description = await promptInput("What will you build in this project?");
|
|
1467
1706
|
}
|
|
1468
|
-
console.log(
|
|
1469
|
-
console.log(
|
|
1470
|
-
console.log(
|
|
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"));
|
|
1710
|
+
console.log(chalk3.dim(" This usually takes 1\u20133 minutes on first run.\n"));
|
|
1471
1711
|
let generatedSetup = null;
|
|
1472
1712
|
let setupExplanation;
|
|
1473
1713
|
let rawOutput;
|
|
1474
1714
|
trackEvent("generation_started", { target_agent: targetAgent });
|
|
1715
|
+
const hasExistingConfig = !!(ec.claudeMd || ec.claudeSettings || ec.claudeSkills?.length || ec.cursorrules || ec.cursorRules?.length || ec.claudeMcpServers || ec.cursorMcpServers);
|
|
1475
1716
|
const genSpinner = ora2("Generating setup...").start();
|
|
1476
|
-
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES);
|
|
1717
|
+
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
|
|
1477
1718
|
genMessages.start();
|
|
1478
1719
|
try {
|
|
1479
1720
|
await apiStream(
|
|
@@ -1481,7 +1722,8 @@ async function initCommand(options) {
|
|
|
1481
1722
|
{
|
|
1482
1723
|
fingerprint,
|
|
1483
1724
|
targetAgent,
|
|
1484
|
-
prompt: fingerprint.description
|
|
1725
|
+
prompt: fingerprint.description,
|
|
1726
|
+
mode: hasExistingConfig ? "improve" : "create"
|
|
1485
1727
|
},
|
|
1486
1728
|
() => {
|
|
1487
1729
|
},
|
|
@@ -1510,45 +1752,45 @@ async function initCommand(options) {
|
|
|
1510
1752
|
if (!generatedSetup) {
|
|
1511
1753
|
genSpinner.fail("Failed to generate setup.");
|
|
1512
1754
|
if (rawOutput) {
|
|
1513
|
-
console.log(
|
|
1514
|
-
console.log(
|
|
1755
|
+
console.log(chalk3.dim("\nRaw LLM output (JSON parse failed):"));
|
|
1756
|
+
console.log(chalk3.dim(rawOutput.slice(0, 500)));
|
|
1515
1757
|
}
|
|
1516
1758
|
throw new Error("__exit__");
|
|
1517
1759
|
}
|
|
1518
1760
|
genSpinner.succeed("Setup generated");
|
|
1519
1761
|
printSetupSummary(generatedSetup);
|
|
1520
|
-
console.log(
|
|
1521
|
-
console.log(
|
|
1522
|
-
console.log(
|
|
1762
|
+
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"));
|
|
1523
1765
|
let explained = false;
|
|
1524
1766
|
let action = await promptAction(explained);
|
|
1525
1767
|
while (action === "explain") {
|
|
1526
1768
|
if (setupExplanation) {
|
|
1527
1769
|
printExplanation(setupExplanation);
|
|
1528
1770
|
} else {
|
|
1529
|
-
console.log(
|
|
1771
|
+
console.log(chalk3.dim("\nNo explanation available for this setup.\n"));
|
|
1530
1772
|
}
|
|
1531
1773
|
explained = true;
|
|
1532
1774
|
action = await promptAction(explained);
|
|
1533
1775
|
}
|
|
1534
1776
|
if (action === "decline") {
|
|
1535
1777
|
trackEvent("setup_declined");
|
|
1536
|
-
console.log(
|
|
1778
|
+
console.log(chalk3.dim("Setup declined. No files were modified."));
|
|
1537
1779
|
return;
|
|
1538
1780
|
}
|
|
1539
1781
|
if (action === "refine") {
|
|
1540
1782
|
generatedSetup = await refineLoop(generatedSetup, targetAgent);
|
|
1541
1783
|
if (!generatedSetup) {
|
|
1542
1784
|
trackEvent("refinement_cancelled");
|
|
1543
|
-
console.log(
|
|
1785
|
+
console.log(chalk3.dim("Refinement cancelled. No files were modified."));
|
|
1544
1786
|
return;
|
|
1545
1787
|
}
|
|
1546
1788
|
}
|
|
1547
|
-
console.log(
|
|
1548
|
-
console.log(
|
|
1549
|
-
console.log(
|
|
1789
|
+
console.log(chalk3.hex("#6366f1").bold(" Step 6/6 \u2014 Apply & sync\n"));
|
|
1790
|
+
console.log(chalk3.dim(" Writing config files to your project and syncing to Caliber so"));
|
|
1791
|
+
console.log(chalk3.dim(" teammates get the same setup when they run `caliber init`.\n"));
|
|
1550
1792
|
if (options.dryRun) {
|
|
1551
|
-
console.log(
|
|
1793
|
+
console.log(chalk3.yellow("\n[Dry run] Would write the following files:"));
|
|
1552
1794
|
console.log(JSON.stringify(generatedSetup, null, 2));
|
|
1553
1795
|
return;
|
|
1554
1796
|
}
|
|
@@ -1557,30 +1799,30 @@ async function initCommand(options) {
|
|
|
1557
1799
|
const result = writeSetup(generatedSetup);
|
|
1558
1800
|
writeSpinner.succeed("Config files written");
|
|
1559
1801
|
trackEvent("setup_applied", { files_written: result.written.length, target_agent: targetAgent });
|
|
1560
|
-
console.log(
|
|
1802
|
+
console.log(chalk3.bold("\nFiles created/updated:"));
|
|
1561
1803
|
for (const file of result.written) {
|
|
1562
|
-
console.log(` ${
|
|
1804
|
+
console.log(` ${chalk3.green("\u2713")} ${file}`);
|
|
1563
1805
|
}
|
|
1564
1806
|
if (result.backupDir) {
|
|
1565
|
-
console.log(
|
|
1807
|
+
console.log(chalk3.dim(`
|
|
1566
1808
|
Backups saved to ${result.backupDir}`));
|
|
1567
1809
|
}
|
|
1568
1810
|
} catch (err) {
|
|
1569
1811
|
writeSpinner.fail("Failed to write files");
|
|
1570
|
-
console.error(
|
|
1812
|
+
console.error(chalk3.red(err instanceof Error ? err.message : "Unknown error"));
|
|
1571
1813
|
throw new Error("__exit__");
|
|
1572
1814
|
}
|
|
1573
1815
|
if (targetAgent === "claude" || targetAgent === "both") {
|
|
1574
1816
|
const hookResult = installHook();
|
|
1575
1817
|
if (hookResult.installed) {
|
|
1576
|
-
console.log(` ${
|
|
1577
|
-
console.log(
|
|
1818
|
+
console.log(` ${chalk3.green("\u2713")} Auto-refresh hook installed \u2014 docs update on Claude Code session end`);
|
|
1819
|
+
console.log(chalk3.dim(" Run `caliber hooks remove` to disable"));
|
|
1578
1820
|
const sha = getCurrentHeadSha();
|
|
1579
1821
|
if (sha) {
|
|
1580
1822
|
writeState({ lastRefreshSha: sha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1581
1823
|
}
|
|
1582
1824
|
} else if (hookResult.alreadyInstalled) {
|
|
1583
|
-
console.log(
|
|
1825
|
+
console.log(chalk3.dim(" Auto-refresh hook already installed"));
|
|
1584
1826
|
}
|
|
1585
1827
|
}
|
|
1586
1828
|
try {
|
|
@@ -1606,14 +1848,14 @@ async function initCommand(options) {
|
|
|
1606
1848
|
}
|
|
1607
1849
|
});
|
|
1608
1850
|
} catch (err) {
|
|
1609
|
-
console.log(
|
|
1851
|
+
console.log(chalk3.yellow(`
|
|
1610
1852
|
Warning: Could not save project to server.`));
|
|
1611
|
-
console.log(
|
|
1612
|
-
console.log(
|
|
1853
|
+
console.log(chalk3.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
1854
|
+
console.log(chalk3.dim(` Your local setup is unaffected.
|
|
1613
1855
|
`));
|
|
1614
1856
|
}
|
|
1615
|
-
console.log(
|
|
1616
|
-
console.log(
|
|
1857
|
+
console.log(chalk3.bold.green("\nSetup complete! Your coding agent is now configured."));
|
|
1858
|
+
console.log(chalk3.dim("Run `caliber undo` to revert changes.\n"));
|
|
1617
1859
|
}
|
|
1618
1860
|
async function refineLoop(currentSetup, _targetAgent) {
|
|
1619
1861
|
const history = [];
|
|
@@ -1655,71 +1897,41 @@ async function refineLoop(currentSetup, _targetAgent) {
|
|
|
1655
1897
|
history.push({ role: "assistant", content: JSON.stringify(refined) });
|
|
1656
1898
|
refineSpinner.succeed("Setup updated");
|
|
1657
1899
|
printSetupSummary(refined);
|
|
1658
|
-
console.log(
|
|
1900
|
+
console.log(chalk3.dim('Type "done" to accept, or describe more changes.'));
|
|
1659
1901
|
}
|
|
1660
1902
|
}
|
|
1661
1903
|
}
|
|
1662
1904
|
function promptInput(question) {
|
|
1663
1905
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1664
1906
|
return new Promise((resolve2) => {
|
|
1665
|
-
rl.question(
|
|
1907
|
+
rl.question(chalk3.cyan(`${question} `), (answer) => {
|
|
1666
1908
|
rl.close();
|
|
1667
1909
|
resolve2(answer.trim());
|
|
1668
1910
|
});
|
|
1669
1911
|
});
|
|
1670
1912
|
}
|
|
1671
|
-
function promptAgent() {
|
|
1672
|
-
return
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
rl.close();
|
|
1680
|
-
const map = { "1": "claude", "2": "cursor", "3": "both" };
|
|
1681
|
-
resolve2(map[answer.trim()] || "claude");
|
|
1682
|
-
});
|
|
1913
|
+
async function promptAgent() {
|
|
1914
|
+
return select({
|
|
1915
|
+
message: "Which coding agent are you using?",
|
|
1916
|
+
choices: [
|
|
1917
|
+
{ name: "Claude Code", value: "claude" },
|
|
1918
|
+
{ name: "Cursor", value: "cursor" },
|
|
1919
|
+
{ name: "Both", value: "both" }
|
|
1920
|
+
]
|
|
1683
1921
|
});
|
|
1684
1922
|
}
|
|
1685
|
-
function promptAction(explained) {
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
rl.question(chalk2.cyan("\nChoose (1-3): "), (answer) => {
|
|
1694
|
-
rl.close();
|
|
1695
|
-
const map = {
|
|
1696
|
-
"1": "accept",
|
|
1697
|
-
"2": "refine",
|
|
1698
|
-
"3": "decline"
|
|
1699
|
-
};
|
|
1700
|
-
resolve2(map[answer.trim()] || "accept");
|
|
1701
|
-
});
|
|
1702
|
-
} else {
|
|
1703
|
-
console.log(" 1. Accept and apply");
|
|
1704
|
-
console.log(" 2. Refine via chat");
|
|
1705
|
-
console.log(" 3. Explain recommendations");
|
|
1706
|
-
console.log(" 4. Decline");
|
|
1707
|
-
rl.question(chalk2.cyan("\nChoose (1-4): "), (answer) => {
|
|
1708
|
-
rl.close();
|
|
1709
|
-
const map = {
|
|
1710
|
-
"1": "accept",
|
|
1711
|
-
"2": "refine",
|
|
1712
|
-
"3": "explain",
|
|
1713
|
-
"4": "decline"
|
|
1714
|
-
};
|
|
1715
|
-
resolve2(map[answer.trim()] || "accept");
|
|
1716
|
-
});
|
|
1717
|
-
}
|
|
1718
|
-
});
|
|
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 });
|
|
1719
1931
|
}
|
|
1720
1932
|
function fileEntry(filePath, desc) {
|
|
1721
|
-
const icon =
|
|
1722
|
-
const description = desc ?
|
|
1933
|
+
const icon = fs16.existsSync(filePath) ? chalk3.yellow("~") : chalk3.green("+");
|
|
1934
|
+
const description = desc ? chalk3.dim(`\u2014 ${desc}`) : "";
|
|
1723
1935
|
return ` ${icon} ${filePath} ${description}`;
|
|
1724
1936
|
}
|
|
1725
1937
|
function printSetupSummary(setup) {
|
|
@@ -1766,39 +1978,39 @@ function printSetupSummary(setup) {
|
|
|
1766
1978
|
}
|
|
1767
1979
|
}
|
|
1768
1980
|
console.log("");
|
|
1769
|
-
console.log(` ${
|
|
1981
|
+
console.log(` ${chalk3.green("+")} ${chalk3.dim("new")} ${chalk3.yellow("~")} ${chalk3.dim("modified")}`);
|
|
1770
1982
|
console.log("");
|
|
1771
1983
|
}
|
|
1772
1984
|
function printExplanation(explanation) {
|
|
1773
|
-
console.log(
|
|
1985
|
+
console.log(chalk3.bold("\n Why this setup?\n"));
|
|
1774
1986
|
const lines = explanation.split("\n");
|
|
1775
1987
|
for (const line of lines) {
|
|
1776
1988
|
const trimmed = line.trim();
|
|
1777
1989
|
if (!trimmed) continue;
|
|
1778
1990
|
const headerMatch = trimmed.match(/^\[(.+)\]$/);
|
|
1779
1991
|
if (headerMatch) {
|
|
1780
|
-
console.log(` ${
|
|
1992
|
+
console.log(` ${chalk3.bold.hex("#6366f1")(headerMatch[1])}`);
|
|
1781
1993
|
continue;
|
|
1782
1994
|
}
|
|
1783
1995
|
const itemMatch = trimmed.match(/^-\s+\*\*(.+?)\*\*[:\s]*(.*)$/);
|
|
1784
1996
|
if (itemMatch) {
|
|
1785
1997
|
const name = itemMatch[1];
|
|
1786
1998
|
const desc = itemMatch[2].replace(/^\s*[-—:]\s*/, "");
|
|
1787
|
-
console.log(` ${
|
|
1999
|
+
console.log(` ${chalk3.dim("\u25B8")} ${chalk3.white(name)} ${chalk3.dim(desc)}`);
|
|
1788
2000
|
continue;
|
|
1789
2001
|
}
|
|
1790
2002
|
const plainMatch = trimmed.match(/^-\s+(.*)$/);
|
|
1791
2003
|
if (plainMatch) {
|
|
1792
|
-
console.log(` ${
|
|
2004
|
+
console.log(` ${chalk3.dim("\u25B8")} ${chalk3.dim(plainMatch[1])}`);
|
|
1793
2005
|
continue;
|
|
1794
2006
|
}
|
|
1795
|
-
console.log(` ${
|
|
2007
|
+
console.log(` ${chalk3.dim(trimmed)}`);
|
|
1796
2008
|
}
|
|
1797
2009
|
console.log("");
|
|
1798
2010
|
}
|
|
1799
2011
|
|
|
1800
2012
|
// src/commands/undo.ts
|
|
1801
|
-
import
|
|
2013
|
+
import chalk4 from "chalk";
|
|
1802
2014
|
import ora3 from "ora";
|
|
1803
2015
|
function undoCommand() {
|
|
1804
2016
|
const spinner = ora3("Reverting setup...").start();
|
|
@@ -1811,163 +2023,27 @@ function undoCommand() {
|
|
|
1811
2023
|
spinner.succeed("Setup reverted successfully.\n");
|
|
1812
2024
|
trackEvent("setup_undone", { restored: restored.length, removed: removed.length });
|
|
1813
2025
|
if (restored.length > 0) {
|
|
1814
|
-
console.log(
|
|
2026
|
+
console.log(chalk4.cyan(" Restored from backup:"));
|
|
1815
2027
|
for (const file of restored) {
|
|
1816
|
-
console.log(` ${
|
|
2028
|
+
console.log(` ${chalk4.green("\u21A9")} ${file}`);
|
|
1817
2029
|
}
|
|
1818
2030
|
}
|
|
1819
2031
|
if (removed.length > 0) {
|
|
1820
|
-
console.log(
|
|
2032
|
+
console.log(chalk4.cyan(" Removed:"));
|
|
1821
2033
|
for (const file of removed) {
|
|
1822
|
-
console.log(` ${
|
|
2034
|
+
console.log(` ${chalk4.red("\u2717")} ${file}`);
|
|
1823
2035
|
}
|
|
1824
2036
|
}
|
|
1825
2037
|
console.log("");
|
|
1826
2038
|
} catch (err) {
|
|
1827
|
-
spinner.fail(
|
|
2039
|
+
spinner.fail(chalk4.red(err instanceof Error ? err.message : "Undo failed"));
|
|
1828
2040
|
throw new Error("__exit__");
|
|
1829
2041
|
}
|
|
1830
2042
|
}
|
|
1831
2043
|
|
|
1832
2044
|
// src/commands/status.ts
|
|
1833
|
-
import
|
|
2045
|
+
import chalk5 from "chalk";
|
|
1834
2046
|
import fs17 from "fs";
|
|
1835
|
-
|
|
1836
|
-
// src/scanner/index.ts
|
|
1837
|
-
import fs16 from "fs";
|
|
1838
|
-
import path14 from "path";
|
|
1839
|
-
import crypto4 from "crypto";
|
|
1840
|
-
function scanLocalState(dir) {
|
|
1841
|
-
const items = [];
|
|
1842
|
-
const claudeMdPath = path14.join(dir, "CLAUDE.md");
|
|
1843
|
-
if (fs16.existsSync(claudeMdPath)) {
|
|
1844
|
-
items.push({
|
|
1845
|
-
type: "rule",
|
|
1846
|
-
platform: "claude",
|
|
1847
|
-
name: "CLAUDE.md",
|
|
1848
|
-
contentHash: hashFile(claudeMdPath),
|
|
1849
|
-
path: claudeMdPath
|
|
1850
|
-
});
|
|
1851
|
-
}
|
|
1852
|
-
const settingsPath = path14.join(dir, ".claude", "settings.json");
|
|
1853
|
-
if (fs16.existsSync(settingsPath)) {
|
|
1854
|
-
items.push({
|
|
1855
|
-
type: "config",
|
|
1856
|
-
platform: "claude",
|
|
1857
|
-
name: "settings.json",
|
|
1858
|
-
contentHash: hashFile(settingsPath),
|
|
1859
|
-
path: settingsPath
|
|
1860
|
-
});
|
|
1861
|
-
}
|
|
1862
|
-
const skillsDir = path14.join(dir, ".claude", "skills");
|
|
1863
|
-
if (fs16.existsSync(skillsDir)) {
|
|
1864
|
-
for (const file of fs16.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
1865
|
-
const filePath = path14.join(skillsDir, file);
|
|
1866
|
-
items.push({
|
|
1867
|
-
type: "skill",
|
|
1868
|
-
platform: "claude",
|
|
1869
|
-
name: file,
|
|
1870
|
-
contentHash: hashFile(filePath),
|
|
1871
|
-
path: filePath
|
|
1872
|
-
});
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1875
|
-
const mcpJsonPath = path14.join(dir, ".mcp.json");
|
|
1876
|
-
if (fs16.existsSync(mcpJsonPath)) {
|
|
1877
|
-
try {
|
|
1878
|
-
const mcpJson = JSON.parse(fs16.readFileSync(mcpJsonPath, "utf-8"));
|
|
1879
|
-
if (mcpJson.mcpServers) {
|
|
1880
|
-
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
1881
|
-
items.push({
|
|
1882
|
-
type: "mcp",
|
|
1883
|
-
platform: "claude",
|
|
1884
|
-
name,
|
|
1885
|
-
contentHash: hashContent(JSON.stringify(mcpJson.mcpServers[name])),
|
|
1886
|
-
path: mcpJsonPath
|
|
1887
|
-
});
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
} catch {
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
const cursorrulesPath = path14.join(dir, ".cursorrules");
|
|
1894
|
-
if (fs16.existsSync(cursorrulesPath)) {
|
|
1895
|
-
items.push({
|
|
1896
|
-
type: "rule",
|
|
1897
|
-
platform: "cursor",
|
|
1898
|
-
name: ".cursorrules",
|
|
1899
|
-
contentHash: hashFile(cursorrulesPath),
|
|
1900
|
-
path: cursorrulesPath
|
|
1901
|
-
});
|
|
1902
|
-
}
|
|
1903
|
-
const cursorRulesDir = path14.join(dir, ".cursor", "rules");
|
|
1904
|
-
if (fs16.existsSync(cursorRulesDir)) {
|
|
1905
|
-
for (const file of fs16.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
1906
|
-
const filePath = path14.join(cursorRulesDir, file);
|
|
1907
|
-
items.push({
|
|
1908
|
-
type: "rule",
|
|
1909
|
-
platform: "cursor",
|
|
1910
|
-
name: file,
|
|
1911
|
-
contentHash: hashFile(filePath),
|
|
1912
|
-
path: filePath
|
|
1913
|
-
});
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
const cursorMcpPath = path14.join(dir, ".cursor", "mcp.json");
|
|
1917
|
-
if (fs16.existsSync(cursorMcpPath)) {
|
|
1918
|
-
try {
|
|
1919
|
-
const mcpJson = JSON.parse(fs16.readFileSync(cursorMcpPath, "utf-8"));
|
|
1920
|
-
if (mcpJson.mcpServers) {
|
|
1921
|
-
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
1922
|
-
items.push({
|
|
1923
|
-
type: "mcp",
|
|
1924
|
-
platform: "cursor",
|
|
1925
|
-
name,
|
|
1926
|
-
contentHash: hashContent(JSON.stringify(mcpJson.mcpServers[name])),
|
|
1927
|
-
path: cursorMcpPath
|
|
1928
|
-
});
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
|
-
} catch {
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
return items;
|
|
1935
|
-
}
|
|
1936
|
-
function compareState(serverItems, localItems) {
|
|
1937
|
-
const installed = [];
|
|
1938
|
-
const missing = [];
|
|
1939
|
-
const outdated = [];
|
|
1940
|
-
const extra = [];
|
|
1941
|
-
const localMap = /* @__PURE__ */ new Map();
|
|
1942
|
-
for (const item of localItems) {
|
|
1943
|
-
localMap.set(`${item.type}:${item.platform}:${item.name}`, item);
|
|
1944
|
-
}
|
|
1945
|
-
for (const server of serverItems) {
|
|
1946
|
-
const key = `${server.type}:${server.platform}:${server.name}`;
|
|
1947
|
-
const local = localMap.get(key);
|
|
1948
|
-
localMap.delete(key);
|
|
1949
|
-
if (!local) {
|
|
1950
|
-
missing.push(server);
|
|
1951
|
-
} else if (local.contentHash !== server.content_hash) {
|
|
1952
|
-
outdated.push({ server, local });
|
|
1953
|
-
} else {
|
|
1954
|
-
installed.push({ server, local });
|
|
1955
|
-
}
|
|
1956
|
-
}
|
|
1957
|
-
for (const local of localMap.values()) {
|
|
1958
|
-
extra.push(local);
|
|
1959
|
-
}
|
|
1960
|
-
return { installed, missing, outdated, extra };
|
|
1961
|
-
}
|
|
1962
|
-
function hashFile(filePath) {
|
|
1963
|
-
const content = fs16.readFileSync(filePath);
|
|
1964
|
-
return crypto4.createHash("sha256").update(content).digest("hex");
|
|
1965
|
-
}
|
|
1966
|
-
function hashContent(content) {
|
|
1967
|
-
return crypto4.createHash("sha256").update(JSON.stringify({ text: content })).digest("hex");
|
|
1968
|
-
}
|
|
1969
|
-
|
|
1970
|
-
// src/commands/status.ts
|
|
1971
2047
|
async function statusCommand(options) {
|
|
1972
2048
|
const auth2 = getStoredAuth();
|
|
1973
2049
|
const manifest = readManifest();
|
|
@@ -1979,21 +2055,21 @@ async function statusCommand(options) {
|
|
|
1979
2055
|
}, null, 2));
|
|
1980
2056
|
return;
|
|
1981
2057
|
}
|
|
1982
|
-
console.log(
|
|
2058
|
+
console.log(chalk5.bold("\nCaliber Status\n"));
|
|
1983
2059
|
if (auth2) {
|
|
1984
|
-
console.log(` Auth: ${
|
|
2060
|
+
console.log(` Auth: ${chalk5.green("Logged in")} as ${auth2.email}`);
|
|
1985
2061
|
} else {
|
|
1986
|
-
console.log(` Auth: ${
|
|
2062
|
+
console.log(` Auth: ${chalk5.yellow("Not logged in")}`);
|
|
1987
2063
|
}
|
|
1988
2064
|
if (!manifest) {
|
|
1989
|
-
console.log(` Setup: ${
|
|
1990
|
-
console.log(
|
|
2065
|
+
console.log(` Setup: ${chalk5.dim("No setup applied")}`);
|
|
2066
|
+
console.log(chalk5.dim("\n Run `caliber init` to get started.\n"));
|
|
1991
2067
|
return;
|
|
1992
2068
|
}
|
|
1993
|
-
console.log(` Files managed: ${
|
|
2069
|
+
console.log(` Files managed: ${chalk5.cyan(manifest.entries.length.toString())}`);
|
|
1994
2070
|
for (const entry of manifest.entries) {
|
|
1995
2071
|
const exists = fs17.existsSync(entry.path);
|
|
1996
|
-
const icon = exists ?
|
|
2072
|
+
const icon = exists ? chalk5.green("\u2713") : chalk5.red("\u2717");
|
|
1997
2073
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
1998
2074
|
}
|
|
1999
2075
|
if (auth2) {
|
|
@@ -2011,10 +2087,10 @@ async function statusCommand(options) {
|
|
|
2011
2087
|
if (serverItems?.length) {
|
|
2012
2088
|
const localItems = scanLocalState(process.cwd());
|
|
2013
2089
|
const diff = compareState(serverItems, localItems);
|
|
2014
|
-
console.log(
|
|
2015
|
-
console.log(` ${
|
|
2016
|
-
if (diff.missing.length) console.log(` ${
|
|
2017
|
-
if (diff.outdated.length) console.log(` ${
|
|
2090
|
+
console.log(chalk5.bold("\n Sync"));
|
|
2091
|
+
console.log(` ${chalk5.green("\u2713")} Installed: ${diff.installed.length}`);
|
|
2092
|
+
if (diff.missing.length) console.log(` ${chalk5.red("\u2717")} Missing: ${diff.missing.length}`);
|
|
2093
|
+
if (diff.outdated.length) console.log(` ${chalk5.yellow("~")} Outdated: ${diff.outdated.length}`);
|
|
2018
2094
|
}
|
|
2019
2095
|
}
|
|
2020
2096
|
} catch {
|
|
@@ -2024,18 +2100,18 @@ async function statusCommand(options) {
|
|
|
2024
2100
|
}
|
|
2025
2101
|
|
|
2026
2102
|
// src/commands/update.ts
|
|
2027
|
-
import
|
|
2103
|
+
import chalk6 from "chalk";
|
|
2028
2104
|
import ora4 from "ora";
|
|
2029
|
-
import
|
|
2105
|
+
import confirm from "@inquirer/confirm";
|
|
2030
2106
|
async function updateCommand(options) {
|
|
2031
2107
|
const auth2 = getStoredAuth();
|
|
2032
2108
|
if (!auth2) {
|
|
2033
|
-
console.log(
|
|
2109
|
+
console.log(chalk6.red("Not logged in. Run `caliber login` first."));
|
|
2034
2110
|
throw new Error("__exit__");
|
|
2035
2111
|
}
|
|
2036
2112
|
const manifest = readManifest();
|
|
2037
2113
|
if (!manifest) {
|
|
2038
|
-
console.log(
|
|
2114
|
+
console.log(chalk6.yellow("No existing setup found. Run `caliber init` first."));
|
|
2039
2115
|
throw new Error("__exit__");
|
|
2040
2116
|
}
|
|
2041
2117
|
const spinner = ora4("Re-analyzing project...").start();
|
|
@@ -2092,18 +2168,14 @@ async function updateCommand(options) {
|
|
|
2092
2168
|
}
|
|
2093
2169
|
genSpinner.succeed("Setup regenerated");
|
|
2094
2170
|
if (options.dryRun) {
|
|
2095
|
-
console.log(
|
|
2171
|
+
console.log(chalk6.yellow("\n[Dry run] Would write:"));
|
|
2096
2172
|
console.log(JSON.stringify(generatedSetup, null, 2));
|
|
2097
2173
|
return;
|
|
2098
2174
|
}
|
|
2099
|
-
const
|
|
2100
|
-
|
|
2101
|
-
rl.question(chalk5.cyan("\n\nApply updated setup? (y/n): "), resolve2);
|
|
2102
|
-
});
|
|
2103
|
-
rl.close();
|
|
2104
|
-
if (answer.trim().toLowerCase() !== "y") {
|
|
2175
|
+
const shouldApply = await confirm({ message: "Apply updated setup?", default: true });
|
|
2176
|
+
if (!shouldApply) {
|
|
2105
2177
|
trackEvent("update_declined");
|
|
2106
|
-
console.log(
|
|
2178
|
+
console.log(chalk6.dim("Update cancelled."));
|
|
2107
2179
|
return;
|
|
2108
2180
|
}
|
|
2109
2181
|
const writeSpinner = ora4("Updating config files...").start();
|
|
@@ -2111,33 +2183,33 @@ async function updateCommand(options) {
|
|
|
2111
2183
|
writeSpinner.succeed("Config files updated");
|
|
2112
2184
|
trackEvent("setup_updated", { files_written: result.written.length });
|
|
2113
2185
|
for (const file of result.written) {
|
|
2114
|
-
console.log(` ${
|
|
2186
|
+
console.log(` ${chalk6.green("\u2713")} ${file}`);
|
|
2115
2187
|
}
|
|
2116
2188
|
console.log("");
|
|
2117
2189
|
}
|
|
2118
2190
|
|
|
2119
2191
|
// src/commands/logout.ts
|
|
2120
|
-
import
|
|
2192
|
+
import chalk7 from "chalk";
|
|
2121
2193
|
function logoutCommand() {
|
|
2122
2194
|
const auth2 = getStoredAuth();
|
|
2123
2195
|
if (!auth2) {
|
|
2124
|
-
console.log(
|
|
2196
|
+
console.log(chalk7.dim("Not currently logged in."));
|
|
2125
2197
|
return;
|
|
2126
2198
|
}
|
|
2127
2199
|
clearAuth();
|
|
2128
2200
|
trackEvent("logout");
|
|
2129
|
-
console.log(
|
|
2201
|
+
console.log(chalk7.green("Logged out successfully."));
|
|
2130
2202
|
}
|
|
2131
2203
|
|
|
2132
2204
|
// src/commands/recommend.ts
|
|
2133
|
-
import
|
|
2205
|
+
import chalk8 from "chalk";
|
|
2134
2206
|
import ora5 from "ora";
|
|
2135
2207
|
import { mkdirSync, writeFileSync } from "fs";
|
|
2136
2208
|
import { join } from "path";
|
|
2137
2209
|
async function recommendCommand(options) {
|
|
2138
2210
|
const auth2 = getStoredAuth();
|
|
2139
2211
|
if (!auth2) {
|
|
2140
|
-
console.log(
|
|
2212
|
+
console.log(chalk8.red("Not authenticated. Run `caliber login` first."));
|
|
2141
2213
|
throw new Error("__exit__");
|
|
2142
2214
|
}
|
|
2143
2215
|
const fingerprint = collectFingerprint(process.cwd());
|
|
@@ -2147,7 +2219,7 @@ async function recommendCommand(options) {
|
|
|
2147
2219
|
{ method: "POST", body: { fingerprintHash: hash } }
|
|
2148
2220
|
);
|
|
2149
2221
|
if (!match?.project) {
|
|
2150
|
-
console.log(
|
|
2222
|
+
console.log(chalk8.yellow("No project found. Run `caliber init` first."));
|
|
2151
2223
|
throw new Error("__exit__");
|
|
2152
2224
|
}
|
|
2153
2225
|
const projectId = match.project.id;
|
|
@@ -2177,7 +2249,7 @@ async function recommendCommand(options) {
|
|
|
2177
2249
|
`/api/recommendations/project/${projectId}?status=${statusFilter}`
|
|
2178
2250
|
);
|
|
2179
2251
|
if (!recs?.length) {
|
|
2180
|
-
console.log(
|
|
2252
|
+
console.log(chalk8.dim(`
|
|
2181
2253
|
No ${statusFilter} recommendations. Run \`caliber recommend --generate\` to discover skills.
|
|
2182
2254
|
`));
|
|
2183
2255
|
return;
|
|
@@ -2202,19 +2274,19 @@ async function interactiveSelect(recs) {
|
|
|
2202
2274
|
let lineCount = 0;
|
|
2203
2275
|
function render() {
|
|
2204
2276
|
const lines = [];
|
|
2205
|
-
lines.push(
|
|
2277
|
+
lines.push(chalk8.bold(" Skill Recommendations"));
|
|
2206
2278
|
lines.push("");
|
|
2207
|
-
lines.push(` ${
|
|
2208
|
-
lines.push(
|
|
2279
|
+
lines.push(` ${chalk8.dim("Name".padEnd(30))} ${chalk8.dim("Score".padEnd(8))} ${chalk8.dim("Technology")}`);
|
|
2280
|
+
lines.push(chalk8.dim(" " + "\u2500".repeat(55)));
|
|
2209
2281
|
for (let i = 0; i < recs.length; i++) {
|
|
2210
2282
|
const rec = recs[i];
|
|
2211
|
-
const check = selected.has(i) ?
|
|
2212
|
-
const ptr = i === cursor ?
|
|
2213
|
-
const scoreColor = rec.score >= 90 ?
|
|
2283
|
+
const check = selected.has(i) ? chalk8.green("[x]") : "[ ]";
|
|
2284
|
+
const ptr = i === cursor ? chalk8.cyan("\u276F") : " ";
|
|
2285
|
+
const scoreColor = rec.score >= 90 ? chalk8.green : rec.score >= 70 ? chalk8.blue : chalk8.yellow;
|
|
2214
2286
|
lines.push(` ${ptr} ${check} ${rec.skill_name.padEnd(26)} ${scoreColor(`${rec.score}%`.padEnd(8))} ${rec.detected_technology}`);
|
|
2215
2287
|
}
|
|
2216
2288
|
lines.push("");
|
|
2217
|
-
lines.push(
|
|
2289
|
+
lines.push(chalk8.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q cancel"));
|
|
2218
2290
|
return lines.join("\n");
|
|
2219
2291
|
}
|
|
2220
2292
|
function draw(initial) {
|
|
@@ -2263,7 +2335,7 @@ async function interactiveSelect(recs) {
|
|
|
2263
2335
|
case "\n":
|
|
2264
2336
|
cleanup();
|
|
2265
2337
|
if (selected.size === 0) {
|
|
2266
|
-
console.log(
|
|
2338
|
+
console.log(chalk8.dim("\n No skills selected.\n"));
|
|
2267
2339
|
resolve2(null);
|
|
2268
2340
|
} else {
|
|
2269
2341
|
resolve2(Array.from(selected).sort().map((i) => recs[i]));
|
|
@@ -2273,7 +2345,7 @@ async function interactiveSelect(recs) {
|
|
|
2273
2345
|
case "\x1B":
|
|
2274
2346
|
case "":
|
|
2275
2347
|
cleanup();
|
|
2276
|
-
console.log(
|
|
2348
|
+
console.log(chalk8.dim("\n Cancelled.\n"));
|
|
2277
2349
|
resolve2(null);
|
|
2278
2350
|
break;
|
|
2279
2351
|
}
|
|
@@ -2313,41 +2385,41 @@ async function installSkills(recs) {
|
|
|
2313
2385
|
if (installed.length > 0) {
|
|
2314
2386
|
spinner.succeed(`Installed ${installed.length} skill${installed.length > 1 ? "s" : ""}`);
|
|
2315
2387
|
for (const p of installed) {
|
|
2316
|
-
console.log(
|
|
2388
|
+
console.log(chalk8.green(` \u2713 ${p}`));
|
|
2317
2389
|
}
|
|
2318
2390
|
} else {
|
|
2319
2391
|
spinner.fail("No skills were installed");
|
|
2320
2392
|
}
|
|
2321
2393
|
for (const w of warnings) {
|
|
2322
|
-
console.log(
|
|
2394
|
+
console.log(chalk8.yellow(` \u26A0 ${w}`));
|
|
2323
2395
|
}
|
|
2324
2396
|
console.log("");
|
|
2325
2397
|
}
|
|
2326
2398
|
function printRecommendations(recs) {
|
|
2327
|
-
console.log(
|
|
2399
|
+
console.log(chalk8.bold("\n Skill Recommendations\n"));
|
|
2328
2400
|
console.log(
|
|
2329
|
-
` ${
|
|
2401
|
+
` ${chalk8.dim("Name".padEnd(30))} ${chalk8.dim("Score".padEnd(8))} ${chalk8.dim("Technology".padEnd(15))} ${chalk8.dim("Status")}`
|
|
2330
2402
|
);
|
|
2331
|
-
console.log(
|
|
2403
|
+
console.log(chalk8.dim(" " + "\u2500".repeat(70)));
|
|
2332
2404
|
for (const rec of recs) {
|
|
2333
|
-
const scoreColor = rec.score >= 90 ?
|
|
2405
|
+
const scoreColor = rec.score >= 90 ? chalk8.green : rec.score >= 70 ? chalk8.blue : chalk8.yellow;
|
|
2334
2406
|
console.log(
|
|
2335
2407
|
` ${rec.skill_name.padEnd(30)} ${scoreColor(`${rec.score}%`.padEnd(8))} ${rec.detected_technology.padEnd(15)} ${rec.status}`
|
|
2336
2408
|
);
|
|
2337
2409
|
if (rec.reason) {
|
|
2338
|
-
console.log(` ${
|
|
2410
|
+
console.log(` ${chalk8.dim(rec.reason)}`);
|
|
2339
2411
|
}
|
|
2340
2412
|
}
|
|
2341
2413
|
console.log("");
|
|
2342
2414
|
}
|
|
2343
2415
|
|
|
2344
2416
|
// src/commands/health.ts
|
|
2345
|
-
import
|
|
2417
|
+
import chalk9 from "chalk";
|
|
2346
2418
|
import ora6 from "ora";
|
|
2347
2419
|
async function healthCommand(options) {
|
|
2348
2420
|
const auth2 = getStoredAuth();
|
|
2349
2421
|
if (!auth2) {
|
|
2350
|
-
console.log(
|
|
2422
|
+
console.log(chalk9.red("Not authenticated. Run `caliber login` first."));
|
|
2351
2423
|
throw new Error("__exit__");
|
|
2352
2424
|
}
|
|
2353
2425
|
const fingerprint = collectFingerprint(process.cwd());
|
|
@@ -2357,7 +2429,7 @@ async function healthCommand(options) {
|
|
|
2357
2429
|
{ method: "POST", body: { fingerprintHash: hash } }
|
|
2358
2430
|
);
|
|
2359
2431
|
if (!match?.project) {
|
|
2360
|
-
console.log(
|
|
2432
|
+
console.log(chalk9.yellow("No project found. Run `caliber init` first."));
|
|
2361
2433
|
throw new Error("__exit__");
|
|
2362
2434
|
}
|
|
2363
2435
|
const projectId = match.project.id;
|
|
@@ -2379,21 +2451,21 @@ async function healthCommand(options) {
|
|
|
2379
2451
|
}
|
|
2380
2452
|
printReport(report);
|
|
2381
2453
|
if (options.fix) {
|
|
2382
|
-
console.log(
|
|
2454
|
+
console.log(chalk9.bold("\nGenerating fix plan..."));
|
|
2383
2455
|
const plan = await apiRequest(
|
|
2384
2456
|
`/api/context/reports/${report.id}/fix-plan`,
|
|
2385
2457
|
{ method: "POST", body: { projectId } }
|
|
2386
2458
|
);
|
|
2387
2459
|
if (!plan?.actions.length) {
|
|
2388
|
-
console.log(
|
|
2460
|
+
console.log(chalk9.dim("No fixes needed."));
|
|
2389
2461
|
return;
|
|
2390
2462
|
}
|
|
2391
|
-
console.log(
|
|
2463
|
+
console.log(chalk9.bold("\nProposed actions:"));
|
|
2392
2464
|
for (const action of plan.actions) {
|
|
2393
|
-
const icon = action.type === "remove" ?
|
|
2465
|
+
const icon = action.type === "remove" ? chalk9.red("- ") : action.type === "add" ? chalk9.green("+ ") : chalk9.yellow("~ ");
|
|
2394
2466
|
console.log(` ${icon}${action.type}: ${action.items.join(", ")}`);
|
|
2395
2467
|
}
|
|
2396
|
-
console.log(
|
|
2468
|
+
console.log(chalk9.dim(`
|
|
2397
2469
|
Estimated improvement: +${plan.estimatedScoreImprovement} points`));
|
|
2398
2470
|
const fixSpinner = ora6("Executing fix plan...").start();
|
|
2399
2471
|
try {
|
|
@@ -2403,7 +2475,7 @@ Estimated improvement: +${plan.estimatedScoreImprovement} points`));
|
|
|
2403
2475
|
);
|
|
2404
2476
|
fixSpinner.succeed("Fix applied");
|
|
2405
2477
|
console.log("");
|
|
2406
|
-
console.log(` Score: ${result.scoreBefore} \u2192 ${
|
|
2478
|
+
console.log(` Score: ${result.scoreBefore} \u2192 ${chalk9.green(String(result.scoreAfter))} (${chalk9.green(`+${result.improvement}`)})`);
|
|
2407
2479
|
if (result.itemsRemoved) console.log(` Removed: ${result.itemsRemoved} items`);
|
|
2408
2480
|
if (result.itemsConsolidated) console.log(` Consolidated: ${result.itemsConsolidated} items`);
|
|
2409
2481
|
if (result.itemsAdded) console.log(` Added: ${result.itemsAdded} items`);
|
|
@@ -2416,18 +2488,18 @@ Estimated improvement: +${plan.estimatedScoreImprovement} points`));
|
|
|
2416
2488
|
}
|
|
2417
2489
|
function printReport(report) {
|
|
2418
2490
|
const gradeColors = {
|
|
2419
|
-
A:
|
|
2420
|
-
B:
|
|
2421
|
-
C:
|
|
2422
|
-
D:
|
|
2423
|
-
F:
|
|
2491
|
+
A: chalk9.green,
|
|
2492
|
+
B: chalk9.blue,
|
|
2493
|
+
C: chalk9.yellow,
|
|
2494
|
+
D: chalk9.hex("#FFA500"),
|
|
2495
|
+
F: chalk9.red
|
|
2424
2496
|
};
|
|
2425
|
-
const gradeColor = gradeColors[report.grade] ||
|
|
2426
|
-
console.log(
|
|
2497
|
+
const gradeColor = gradeColors[report.grade] || chalk9.white;
|
|
2498
|
+
console.log(chalk9.bold("\n Context Health Report\n"));
|
|
2427
2499
|
console.log(` Grade: ${gradeColor(report.grade)} Score: ${gradeColor(String(report.score))}/100
|
|
2428
2500
|
`);
|
|
2429
2501
|
if (Object.keys(report.category_breakdown).length) {
|
|
2430
|
-
console.log(
|
|
2502
|
+
console.log(chalk9.dim(" Category Breakdown:"));
|
|
2431
2503
|
const categories = Object.entries(report.category_breakdown);
|
|
2432
2504
|
const maxCount = Math.max(...categories.map(([, v]) => v.count));
|
|
2433
2505
|
for (const [name, data] of categories) {
|
|
@@ -2438,9 +2510,9 @@ function printReport(report) {
|
|
|
2438
2510
|
console.log("");
|
|
2439
2511
|
}
|
|
2440
2512
|
if (report.recommendations.length) {
|
|
2441
|
-
console.log(
|
|
2513
|
+
console.log(chalk9.dim(" Recommendations:"));
|
|
2442
2514
|
for (const rec of report.recommendations) {
|
|
2443
|
-
const icon = rec.priority === "high" ?
|
|
2515
|
+
const icon = rec.priority === "high" ? chalk9.red("!") : rec.priority === "medium" ? chalk9.yellow("~") : chalk9.dim("-");
|
|
2444
2516
|
console.log(` ${icon} ${rec.description}`);
|
|
2445
2517
|
}
|
|
2446
2518
|
console.log("");
|
|
@@ -2448,12 +2520,12 @@ function printReport(report) {
|
|
|
2448
2520
|
}
|
|
2449
2521
|
|
|
2450
2522
|
// src/commands/sync.ts
|
|
2451
|
-
import
|
|
2523
|
+
import chalk10 from "chalk";
|
|
2452
2524
|
import ora7 from "ora";
|
|
2453
2525
|
async function syncCommand(options) {
|
|
2454
2526
|
const auth2 = getStoredAuth();
|
|
2455
2527
|
if (!auth2) {
|
|
2456
|
-
console.log(
|
|
2528
|
+
console.log(chalk10.red("Not authenticated. Run `caliber login` first."));
|
|
2457
2529
|
throw new Error("__exit__");
|
|
2458
2530
|
}
|
|
2459
2531
|
const fingerprint = collectFingerprint(process.cwd());
|
|
@@ -2463,7 +2535,7 @@ async function syncCommand(options) {
|
|
|
2463
2535
|
{ method: "POST", body: { fingerprintHash: hash } }
|
|
2464
2536
|
);
|
|
2465
2537
|
if (!match?.project) {
|
|
2466
|
-
console.log(
|
|
2538
|
+
console.log(chalk10.yellow("No project found. Run `caliber init` first."));
|
|
2467
2539
|
throw new Error("__exit__");
|
|
2468
2540
|
}
|
|
2469
2541
|
const projectId = match.project.id;
|
|
@@ -2475,7 +2547,7 @@ async function syncCommand(options) {
|
|
|
2475
2547
|
);
|
|
2476
2548
|
spinner.succeed(`Found ${localItems.length} local items, ${serverItems?.length || 0} server items`);
|
|
2477
2549
|
if (!serverItems?.length) {
|
|
2478
|
-
console.log(
|
|
2550
|
+
console.log(chalk10.dim("\nNo items configured on server. Run `caliber init` to set up your project.\n"));
|
|
2479
2551
|
return;
|
|
2480
2552
|
}
|
|
2481
2553
|
const platformFilter = options.platform;
|
|
@@ -2484,12 +2556,12 @@ async function syncCommand(options) {
|
|
|
2484
2556
|
const diff = compareState(filteredServer, filteredLocal);
|
|
2485
2557
|
printDiff(diff);
|
|
2486
2558
|
if (diff.missing.length === 0 && diff.outdated.length === 0) {
|
|
2487
|
-
console.log(
|
|
2559
|
+
console.log(chalk10.green("\nAll items synced.\n"));
|
|
2488
2560
|
await reportToServer(projectId, filteredServer, diff);
|
|
2489
2561
|
return;
|
|
2490
2562
|
}
|
|
2491
2563
|
if (options.dryRun) {
|
|
2492
|
-
console.log(
|
|
2564
|
+
console.log(chalk10.dim("\nDry run \u2014 no changes made.\n"));
|
|
2493
2565
|
return;
|
|
2494
2566
|
}
|
|
2495
2567
|
const installSpinner = ora7("Installing missing and outdated items...").start();
|
|
@@ -2545,26 +2617,26 @@ async function reportToServer(projectId, serverItems, diff) {
|
|
|
2545
2617
|
}
|
|
2546
2618
|
}
|
|
2547
2619
|
function printDiff(diff) {
|
|
2548
|
-
console.log(
|
|
2620
|
+
console.log(chalk10.bold("\n Sync Status\n"));
|
|
2549
2621
|
if (diff.installed.length) {
|
|
2550
|
-
console.log(` ${
|
|
2622
|
+
console.log(` ${chalk10.green("\u2713")} Installed: ${diff.installed.length}`);
|
|
2551
2623
|
}
|
|
2552
2624
|
if (diff.missing.length) {
|
|
2553
|
-
console.log(` ${
|
|
2625
|
+
console.log(` ${chalk10.red("\u2717")} Missing: ${diff.missing.length}`);
|
|
2554
2626
|
for (const item of diff.missing) {
|
|
2555
|
-
console.log(` ${
|
|
2627
|
+
console.log(` ${chalk10.red("-")} ${item.name} (${item.type}/${item.platform})`);
|
|
2556
2628
|
}
|
|
2557
2629
|
}
|
|
2558
2630
|
if (diff.outdated.length) {
|
|
2559
|
-
console.log(` ${
|
|
2631
|
+
console.log(` ${chalk10.yellow("~")} Outdated: ${diff.outdated.length}`);
|
|
2560
2632
|
for (const item of diff.outdated) {
|
|
2561
|
-
console.log(` ${
|
|
2633
|
+
console.log(` ${chalk10.yellow("~")} ${item.server.name} (${item.server.type}/${item.server.platform})`);
|
|
2562
2634
|
}
|
|
2563
2635
|
}
|
|
2564
2636
|
if (diff.extra.length) {
|
|
2565
|
-
console.log(` ${
|
|
2637
|
+
console.log(` ${chalk10.dim("+")} Extra (local only): ${diff.extra.length}`);
|
|
2566
2638
|
for (const item of diff.extra) {
|
|
2567
|
-
console.log(` ${
|
|
2639
|
+
console.log(` ${chalk10.dim("+")} ${item.name} (${item.type}/${item.platform})`);
|
|
2568
2640
|
}
|
|
2569
2641
|
}
|
|
2570
2642
|
console.log("");
|
|
@@ -2625,12 +2697,12 @@ function buildSetupFromItems(items) {
|
|
|
2625
2697
|
}
|
|
2626
2698
|
|
|
2627
2699
|
// src/commands/diff.ts
|
|
2628
|
-
import
|
|
2700
|
+
import chalk11 from "chalk";
|
|
2629
2701
|
import ora8 from "ora";
|
|
2630
2702
|
async function diffCommand(options) {
|
|
2631
2703
|
const auth2 = getStoredAuth();
|
|
2632
2704
|
if (!auth2) {
|
|
2633
|
-
console.log(
|
|
2705
|
+
console.log(chalk11.red("Not authenticated. Run `caliber login` first."));
|
|
2634
2706
|
throw new Error("__exit__");
|
|
2635
2707
|
}
|
|
2636
2708
|
const fingerprint = collectFingerprint(process.cwd());
|
|
@@ -2640,7 +2712,7 @@ async function diffCommand(options) {
|
|
|
2640
2712
|
{ method: "POST", body: { fingerprintHash: hash } }
|
|
2641
2713
|
);
|
|
2642
2714
|
if (!match?.project) {
|
|
2643
|
-
console.log(
|
|
2715
|
+
console.log(chalk11.yellow("No project found. Run `caliber init` first."));
|
|
2644
2716
|
throw new Error("__exit__");
|
|
2645
2717
|
}
|
|
2646
2718
|
const projectId = match.project.id;
|
|
@@ -2651,46 +2723,46 @@ async function diffCommand(options) {
|
|
|
2651
2723
|
);
|
|
2652
2724
|
spinner.stop();
|
|
2653
2725
|
if (!serverItems?.length) {
|
|
2654
|
-
console.log(
|
|
2726
|
+
console.log(chalk11.dim("\nNo items configured on server.\n"));
|
|
2655
2727
|
return;
|
|
2656
2728
|
}
|
|
2657
2729
|
const platformFilter = options.platform;
|
|
2658
2730
|
const filteredServer = platformFilter ? serverItems.filter((i) => i.platform === platformFilter || i.platform === "both") : serverItems;
|
|
2659
2731
|
const filteredLocal = platformFilter ? localItems.filter((i) => i.platform === platformFilter) : localItems;
|
|
2660
2732
|
const diff = compareState(filteredServer, filteredLocal);
|
|
2661
|
-
console.log(
|
|
2733
|
+
console.log(chalk11.bold("\n Config Diff\n"));
|
|
2662
2734
|
if (diff.installed.length) {
|
|
2663
|
-
console.log(` ${
|
|
2735
|
+
console.log(` ${chalk11.green("\u2713")} In sync: ${diff.installed.length}`);
|
|
2664
2736
|
}
|
|
2665
2737
|
if (diff.missing.length) {
|
|
2666
|
-
console.log(` ${
|
|
2738
|
+
console.log(` ${chalk11.red("\u2717")} Missing locally: ${diff.missing.length}`);
|
|
2667
2739
|
for (const item of diff.missing) {
|
|
2668
|
-
console.log(` ${
|
|
2740
|
+
console.log(` ${chalk11.red("-")} ${item.name} (${item.type}/${item.platform})`);
|
|
2669
2741
|
}
|
|
2670
2742
|
}
|
|
2671
2743
|
if (diff.outdated.length) {
|
|
2672
|
-
console.log(` ${
|
|
2744
|
+
console.log(` ${chalk11.yellow("~")} Outdated: ${diff.outdated.length}`);
|
|
2673
2745
|
for (const item of diff.outdated) {
|
|
2674
|
-
console.log(` ${
|
|
2746
|
+
console.log(` ${chalk11.yellow("~")} ${item.server.name} (${item.server.type}/${item.server.platform})`);
|
|
2675
2747
|
}
|
|
2676
2748
|
}
|
|
2677
2749
|
if (diff.extra.length) {
|
|
2678
|
-
console.log(` ${
|
|
2750
|
+
console.log(` ${chalk11.dim("+")} Local only: ${diff.extra.length}`);
|
|
2679
2751
|
for (const item of diff.extra) {
|
|
2680
|
-
console.log(` ${
|
|
2752
|
+
console.log(` ${chalk11.dim("+")} ${item.name} (${item.type}/${item.platform})`);
|
|
2681
2753
|
}
|
|
2682
2754
|
}
|
|
2683
2755
|
if (diff.missing.length === 0 && diff.outdated.length === 0) {
|
|
2684
|
-
console.log(
|
|
2756
|
+
console.log(chalk11.green("\n Everything is in sync.\n"));
|
|
2685
2757
|
} else {
|
|
2686
|
-
console.log(
|
|
2758
|
+
console.log(chalk11.dim("\n Run `caliber sync` to apply changes.\n"));
|
|
2687
2759
|
}
|
|
2688
2760
|
}
|
|
2689
2761
|
|
|
2690
2762
|
// src/commands/refresh.ts
|
|
2691
2763
|
import fs19 from "fs";
|
|
2692
2764
|
import path16 from "path";
|
|
2693
|
-
import
|
|
2765
|
+
import chalk12 from "chalk";
|
|
2694
2766
|
import ora9 from "ora";
|
|
2695
2767
|
|
|
2696
2768
|
// src/lib/git-diff.ts
|
|
@@ -2824,7 +2896,7 @@ function discoverGitRepos(parentDir) {
|
|
|
2824
2896
|
}
|
|
2825
2897
|
async function refreshSingleRepo(repoDir, options) {
|
|
2826
2898
|
const quiet = !!options.quiet;
|
|
2827
|
-
const prefix = options.label ? `${
|
|
2899
|
+
const prefix = options.label ? `${chalk12.bold(options.label)} ` : "";
|
|
2828
2900
|
const state = readState();
|
|
2829
2901
|
const lastSha = state?.lastRefreshSha ?? null;
|
|
2830
2902
|
const diff = collectDiff(lastSha);
|
|
@@ -2833,7 +2905,7 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
2833
2905
|
if (currentSha) {
|
|
2834
2906
|
writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2835
2907
|
}
|
|
2836
|
-
log(quiet,
|
|
2908
|
+
log(quiet, chalk12.dim(`${prefix}No changes since last refresh.`));
|
|
2837
2909
|
return;
|
|
2838
2910
|
}
|
|
2839
2911
|
const spinner = quiet ? null : ora9(`${prefix}Analyzing changes...`).start();
|
|
@@ -2868,10 +2940,10 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
2868
2940
|
if (options.dryRun) {
|
|
2869
2941
|
spinner?.info(`${prefix}Dry run \u2014 would update:`);
|
|
2870
2942
|
for (const doc of response.docsUpdated) {
|
|
2871
|
-
console.log(` ${
|
|
2943
|
+
console.log(` ${chalk12.yellow("~")} ${doc}`);
|
|
2872
2944
|
}
|
|
2873
2945
|
if (response.changesSummary) {
|
|
2874
|
-
console.log(
|
|
2946
|
+
console.log(chalk12.dim(`
|
|
2875
2947
|
${response.changesSummary}`));
|
|
2876
2948
|
}
|
|
2877
2949
|
return;
|
|
@@ -2879,10 +2951,10 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
2879
2951
|
const written = writeRefreshDocs(response.updatedDocs);
|
|
2880
2952
|
spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
|
|
2881
2953
|
for (const file of written) {
|
|
2882
|
-
log(quiet, ` ${
|
|
2954
|
+
log(quiet, ` ${chalk12.green("\u2713")} ${file}`);
|
|
2883
2955
|
}
|
|
2884
2956
|
if (response.changesSummary) {
|
|
2885
|
-
log(quiet,
|
|
2957
|
+
log(quiet, chalk12.dim(`
|
|
2886
2958
|
${response.changesSummary}`));
|
|
2887
2959
|
}
|
|
2888
2960
|
if (currentSha) {
|
|
@@ -2899,7 +2971,7 @@ async function refreshCommand(options) {
|
|
|
2899
2971
|
const auth2 = getStoredAuth();
|
|
2900
2972
|
if (!auth2) {
|
|
2901
2973
|
if (quiet) return;
|
|
2902
|
-
console.log(
|
|
2974
|
+
console.log(chalk12.red("Not authenticated. Run `caliber login` first."));
|
|
2903
2975
|
throw new Error("__exit__");
|
|
2904
2976
|
}
|
|
2905
2977
|
if (isGitRepo()) {
|
|
@@ -2909,10 +2981,10 @@ async function refreshCommand(options) {
|
|
|
2909
2981
|
const repos = discoverGitRepos(process.cwd());
|
|
2910
2982
|
if (repos.length === 0) {
|
|
2911
2983
|
if (quiet) return;
|
|
2912
|
-
console.log(
|
|
2984
|
+
console.log(chalk12.red("Not inside a git repository and no git repos found in child directories."));
|
|
2913
2985
|
throw new Error("__exit__");
|
|
2914
2986
|
}
|
|
2915
|
-
log(quiet,
|
|
2987
|
+
log(quiet, chalk12.dim(`Found ${repos.length} git repo${repos.length === 1 ? "" : "s"}
|
|
2916
2988
|
`));
|
|
2917
2989
|
const originalDir = process.cwd();
|
|
2918
2990
|
for (const repo of repos) {
|
|
@@ -2922,7 +2994,7 @@ async function refreshCommand(options) {
|
|
|
2922
2994
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
2923
2995
|
} catch (err) {
|
|
2924
2996
|
if (err instanceof Error && err.message === "__exit__") continue;
|
|
2925
|
-
log(quiet,
|
|
2997
|
+
log(quiet, chalk12.yellow(`${repoName}: refresh failed \u2014 ${err instanceof Error ? err.message : "unknown error"}`));
|
|
2926
2998
|
}
|
|
2927
2999
|
}
|
|
2928
3000
|
process.chdir(originalDir);
|
|
@@ -2930,46 +3002,48 @@ async function refreshCommand(options) {
|
|
|
2930
3002
|
if (err instanceof Error && err.message === "__exit__") throw err;
|
|
2931
3003
|
if (quiet) return;
|
|
2932
3004
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
2933
|
-
console.log(
|
|
3005
|
+
console.log(chalk12.red(`Refresh failed: ${msg}`));
|
|
2934
3006
|
throw new Error("__exit__");
|
|
2935
3007
|
}
|
|
2936
3008
|
}
|
|
2937
3009
|
|
|
2938
3010
|
// src/commands/hooks.ts
|
|
2939
|
-
import
|
|
3011
|
+
import chalk13 from "chalk";
|
|
2940
3012
|
async function hooksInstallCommand() {
|
|
2941
3013
|
const result = installHook();
|
|
2942
3014
|
if (result.alreadyInstalled) {
|
|
2943
|
-
console.log(
|
|
3015
|
+
console.log(chalk13.dim("Hook already installed."));
|
|
2944
3016
|
return;
|
|
2945
3017
|
}
|
|
2946
|
-
console.log(
|
|
2947
|
-
console.log(
|
|
3018
|
+
console.log(chalk13.green("\u2713") + " SessionEnd hook installed in .claude/settings.json");
|
|
3019
|
+
console.log(chalk13.dim(" Docs will auto-refresh when Claude Code sessions end."));
|
|
2948
3020
|
}
|
|
2949
3021
|
async function hooksRemoveCommand() {
|
|
2950
3022
|
const result = removeHook();
|
|
2951
3023
|
if (result.notFound) {
|
|
2952
|
-
console.log(
|
|
3024
|
+
console.log(chalk13.dim("Hook not found."));
|
|
2953
3025
|
return;
|
|
2954
3026
|
}
|
|
2955
|
-
console.log(
|
|
3027
|
+
console.log(chalk13.green("\u2713") + " SessionEnd hook removed from .claude/settings.json");
|
|
2956
3028
|
}
|
|
2957
3029
|
async function hooksStatusCommand() {
|
|
2958
3030
|
const installed = isHookInstalled();
|
|
2959
3031
|
if (installed) {
|
|
2960
|
-
console.log(
|
|
3032
|
+
console.log(chalk13.green("\u2713") + " Auto-refresh hook is " + chalk13.green("installed"));
|
|
2961
3033
|
} else {
|
|
2962
|
-
console.log(
|
|
2963
|
-
console.log(
|
|
3034
|
+
console.log(chalk13.dim("\u2717") + " Auto-refresh hook is " + chalk13.yellow("not installed"));
|
|
3035
|
+
console.log(chalk13.dim(" Run `caliber hooks install` to enable auto-refresh on session end."));
|
|
2964
3036
|
}
|
|
2965
3037
|
}
|
|
2966
3038
|
|
|
2967
3039
|
// src/commands/review.ts
|
|
2968
|
-
import
|
|
2969
|
-
import
|
|
3040
|
+
import chalk14 from "chalk";
|
|
3041
|
+
import readline2 from "readline";
|
|
2970
3042
|
import ora10 from "ora";
|
|
3043
|
+
import select2 from "@inquirer/select";
|
|
3044
|
+
import confirm2 from "@inquirer/confirm";
|
|
2971
3045
|
function prompt(question) {
|
|
2972
|
-
const rl =
|
|
3046
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
2973
3047
|
return new Promise((resolve2) => {
|
|
2974
3048
|
rl.question(question, (answer) => {
|
|
2975
3049
|
rl.close();
|
|
@@ -2978,30 +3052,26 @@ function prompt(question) {
|
|
|
2978
3052
|
});
|
|
2979
3053
|
}
|
|
2980
3054
|
async function promptRating() {
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
console.log(chalk13.yellow(" Please enter a number between 1 and 5."));
|
|
2992
|
-
}
|
|
3055
|
+
return select2({
|
|
3056
|
+
message: "How helpful was Caliber in setting up your coding agent?",
|
|
3057
|
+
choices: [
|
|
3058
|
+
{ name: "Not helpful at all", value: 1 },
|
|
3059
|
+
{ name: "Slightly helpful", value: 2 },
|
|
3060
|
+
{ name: "Moderately helpful", value: 3 },
|
|
3061
|
+
{ name: "Very helpful", value: 4 },
|
|
3062
|
+
{ name: "Extremely helpful", value: 5 }
|
|
3063
|
+
]
|
|
3064
|
+
});
|
|
2993
3065
|
}
|
|
2994
3066
|
async function promptRecommend() {
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
console.log(chalk13.yellow(" Please enter 1, 2, or 3."));
|
|
3004
|
-
}
|
|
3067
|
+
return select2({
|
|
3068
|
+
message: "Would you recommend Caliber to a colleague?",
|
|
3069
|
+
choices: [
|
|
3070
|
+
{ name: "Yes", value: "yes" },
|
|
3071
|
+
{ name: "No", value: "no" },
|
|
3072
|
+
{ name: "Maybe", value: "maybe" }
|
|
3073
|
+
]
|
|
3074
|
+
});
|
|
3005
3075
|
}
|
|
3006
3076
|
function starRating(rating) {
|
|
3007
3077
|
return "\u2605".repeat(rating) + "\u2606".repeat(5 - rating);
|
|
@@ -3012,7 +3082,7 @@ async function submitReview(body) {
|
|
|
3012
3082
|
await apiRequest("/api/reviews", { method: "POST", body });
|
|
3013
3083
|
spinner.succeed("Review submitted");
|
|
3014
3084
|
trackEvent("review_submitted", { rating: body.rating, would_recommend: body.wouldRecommend });
|
|
3015
|
-
console.log(
|
|
3085
|
+
console.log(chalk14.green.bold("\n Thank you for your feedback!\n"));
|
|
3016
3086
|
} catch (err) {
|
|
3017
3087
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3018
3088
|
spinner.fail(`Failed to submit review: ${msg}`);
|
|
@@ -3023,37 +3093,37 @@ async function submitReview(body) {
|
|
|
3023
3093
|
async function reviewCommand(message, options) {
|
|
3024
3094
|
const auth2 = getStoredAuth();
|
|
3025
3095
|
if (!auth2) {
|
|
3026
|
-
console.log(
|
|
3096
|
+
console.log(chalk14.red("\n Not authenticated. Run `caliber login` first.\n"));
|
|
3027
3097
|
throw new Error("__exit__");
|
|
3028
3098
|
}
|
|
3029
3099
|
if (message) {
|
|
3030
3100
|
const rating2 = options.rating ? parseInt(options.rating, 10) : 5;
|
|
3031
3101
|
if (rating2 < 1 || rating2 > 5 || isNaN(rating2)) {
|
|
3032
|
-
console.log(
|
|
3102
|
+
console.log(chalk14.red("Rating must be between 1 and 5."));
|
|
3033
3103
|
throw new Error("__exit__");
|
|
3034
3104
|
}
|
|
3035
|
-
console.log(
|
|
3105
|
+
console.log(chalk14.dim(`
|
|
3036
3106
|
${starRating(rating2)} "${message}"
|
|
3037
3107
|
`));
|
|
3038
3108
|
await submitReview({ rating: rating2, bestPart: message, biggestGap: "", wouldRecommend: "yes" });
|
|
3039
3109
|
return;
|
|
3040
3110
|
}
|
|
3041
|
-
console.log(
|
|
3042
|
-
console.log(
|
|
3043
|
-
console.log(
|
|
3044
|
-
console.log(
|
|
3111
|
+
console.log(chalk14.hex("#6366f1").bold("\n Share your feedback\n"));
|
|
3112
|
+
console.log(chalk14.dim(" We'd love to hear how Caliber is working for you."));
|
|
3113
|
+
console.log(chalk14.dim(" This takes under 2 minutes.\n"));
|
|
3114
|
+
console.log(chalk14.dim(' Tip: use `caliber review "your feedback"` for a quick review.\n'));
|
|
3045
3115
|
const rating = await promptRating();
|
|
3046
|
-
const bestPart = await prompt(
|
|
3047
|
-
const biggestGap = await prompt(
|
|
3116
|
+
const bestPart = await prompt(chalk14.cyan("\n What did you find most useful? "));
|
|
3117
|
+
const biggestGap = await prompt(chalk14.cyan("\n What was missing or could be better? "));
|
|
3048
3118
|
const wouldRecommend = await promptRecommend();
|
|
3049
|
-
console.log(
|
|
3119
|
+
console.log(chalk14.bold("\n Your review:\n"));
|
|
3050
3120
|
console.log(` Rating: ${starRating(rating)} (${rating}/5)`);
|
|
3051
|
-
console.log(` Most useful: ${bestPart ||
|
|
3052
|
-
console.log(` Could be better: ${biggestGap ||
|
|
3121
|
+
console.log(` Most useful: ${bestPart || chalk14.dim("(skipped)")}`);
|
|
3122
|
+
console.log(` Could be better: ${biggestGap || chalk14.dim("(skipped)")}`);
|
|
3053
3123
|
console.log(` Would recommend: ${wouldRecommend}`);
|
|
3054
|
-
const
|
|
3055
|
-
if (
|
|
3056
|
-
console.log(
|
|
3124
|
+
const shouldSubmit = await confirm2({ message: "Submit this review?", default: true });
|
|
3125
|
+
if (!shouldSubmit) {
|
|
3126
|
+
console.log(chalk14.dim("\n Review cancelled.\n"));
|
|
3057
3127
|
return;
|
|
3058
3128
|
}
|
|
3059
3129
|
await submitReview({ rating, bestPart, biggestGap, wouldRecommend });
|
|
@@ -3087,23 +3157,16 @@ hooks.command("status").description("Check if auto-refresh hook is installed").a
|
|
|
3087
3157
|
import fs21 from "fs";
|
|
3088
3158
|
import path18 from "path";
|
|
3089
3159
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3090
|
-
import readline4 from "readline";
|
|
3091
3160
|
import { execSync as execSync4 } from "child_process";
|
|
3092
|
-
import
|
|
3161
|
+
import chalk15 from "chalk";
|
|
3093
3162
|
import ora11 from "ora";
|
|
3163
|
+
import confirm3 from "@inquirer/confirm";
|
|
3094
3164
|
var __dirname_vc = path18.dirname(fileURLToPath4(import.meta.url));
|
|
3095
3165
|
var pkg4 = JSON.parse(
|
|
3096
3166
|
fs21.readFileSync(path18.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
3097
3167
|
);
|
|
3098
|
-
function promptYesNo(question) {
|
|
3099
|
-
|
|
3100
|
-
return new Promise((resolve2) => {
|
|
3101
|
-
rl.question(chalk14.cyan(`${question} `), (answer) => {
|
|
3102
|
-
rl.close();
|
|
3103
|
-
const normalized = answer.trim().toLowerCase();
|
|
3104
|
-
resolve2(normalized === "" || normalized === "y" || normalized === "yes");
|
|
3105
|
-
});
|
|
3106
|
-
});
|
|
3168
|
+
async function promptYesNo(question) {
|
|
3169
|
+
return confirm3({ message: question, default: true });
|
|
3107
3170
|
}
|
|
3108
3171
|
async function checkForUpdates() {
|
|
3109
3172
|
if (process.env.CALIBER_SKIP_UPDATE_CHECK) return;
|
|
@@ -3123,17 +3186,17 @@ async function checkForUpdates() {
|
|
|
3123
3186
|
const isInteractive = process.stdin.isTTY === true;
|
|
3124
3187
|
if (!isInteractive) {
|
|
3125
3188
|
console.log(
|
|
3126
|
-
|
|
3189
|
+
chalk15.yellow(
|
|
3127
3190
|
`
|
|
3128
3191
|
Update available: ${current} -> ${latest}
|
|
3129
|
-
Run ${
|
|
3192
|
+
Run ${chalk15.bold("npm install -g @caliber-ai/cli")} to upgrade.
|
|
3130
3193
|
`
|
|
3131
3194
|
)
|
|
3132
3195
|
);
|
|
3133
3196
|
return;
|
|
3134
3197
|
}
|
|
3135
3198
|
console.log(
|
|
3136
|
-
|
|
3199
|
+
chalk15.yellow(`
|
|
3137
3200
|
Update available: ${current} -> ${latest}`)
|
|
3138
3201
|
);
|
|
3139
3202
|
const shouldUpdate = await promptYesNo("Would you like to update now? (Y/n)");
|
|
@@ -3144,9 +3207,9 @@ Update available: ${current} -> ${latest}`)
|
|
|
3144
3207
|
const spinner = ora11("Updating @caliber-ai/cli...").start();
|
|
3145
3208
|
try {
|
|
3146
3209
|
execSync4("npm install -g @caliber-ai/cli --force", { stdio: "pipe" });
|
|
3147
|
-
spinner.succeed(
|
|
3210
|
+
spinner.succeed(chalk15.green(`Updated to ${latest}`));
|
|
3148
3211
|
const args = process.argv.slice(2);
|
|
3149
|
-
console.log(
|
|
3212
|
+
console.log(chalk15.dim(`
|
|
3150
3213
|
Restarting: caliber ${args.join(" ")}
|
|
3151
3214
|
`));
|
|
3152
3215
|
execSync4(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
@@ -3157,8 +3220,8 @@ Restarting: caliber ${args.join(" ")}
|
|
|
3157
3220
|
} catch {
|
|
3158
3221
|
spinner.fail("Update failed");
|
|
3159
3222
|
console.log(
|
|
3160
|
-
|
|
3161
|
-
`Run ${
|
|
3223
|
+
chalk15.yellow(
|
|
3224
|
+
`Run ${chalk15.bold("npm install -g @caliber-ai/cli")} manually to upgrade.
|
|
3162
3225
|
`
|
|
3163
3226
|
)
|
|
3164
3227
|
);
|