@caliber-ai/cli 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -75,8 +75,8 @@ if (dsn) {
75
75
 
76
76
  // src/cli.ts
77
77
  import { Command } from "commander";
78
- import fs20 from "fs";
79
- import path17 from "path";
78
+ import fs22 from "fs";
79
+ import path19 from "path";
80
80
  import { fileURLToPath as fileURLToPath3 } from "url";
81
81
 
82
82
  // src/commands/init.ts
@@ -84,8 +84,7 @@ import chalk3 from "chalk";
84
84
  import ora2 from "ora";
85
85
  import readline from "readline";
86
86
  import select from "@inquirer/select";
87
- import fs16 from "fs";
88
- import { createTwoFilesPatch } from "diff";
87
+ import fs18 from "fs";
89
88
 
90
89
  // src/auth/token-store.ts
91
90
  init_constants();
@@ -328,6 +327,8 @@ ${authUrl}
328
327
 
329
328
  // src/fingerprint/index.ts
330
329
  import crypto2 from "crypto";
330
+ import fs8 from "fs";
331
+ import path9 from "path";
331
332
 
332
333
  // src/fingerprint/git.ts
333
334
  import { execSync } from "child_process";
@@ -605,6 +606,20 @@ function readExistingConfigs(dir) {
605
606
  } catch {
606
607
  }
607
608
  }
609
+ const cursorSkillsDir = path7.join(dir, ".cursor", "skills");
610
+ if (fs6.existsSync(cursorSkillsDir)) {
611
+ try {
612
+ const slugs = fs6.readdirSync(cursorSkillsDir).filter((f) => {
613
+ return fs6.statSync(path7.join(cursorSkillsDir, f)).isDirectory();
614
+ });
615
+ configs.cursorSkills = slugs.filter((slug) => fs6.existsSync(path7.join(cursorSkillsDir, slug, "SKILL.md"))).map((slug) => ({
616
+ slug,
617
+ filename: "SKILL.md",
618
+ content: fs6.readFileSync(path7.join(cursorSkillsDir, slug, "SKILL.md"), "utf-8")
619
+ }));
620
+ } catch {
621
+ }
622
+ }
608
623
  const mcpJsonPath = path7.join(dir, ".mcp.json");
609
624
  if (fs6.existsSync(mcpJsonPath)) {
610
625
  try {
@@ -899,6 +914,127 @@ function estimateSummarySize(summary) {
899
914
  return summary.path.length + summary.imports.reduce((s, i) => s + i.length, 0) + summary.exports.reduce((s, e) => s + e.length, 0) + summary.functions.reduce((s, f) => s + f.length, 0) + summary.classes.reduce((s, c) => s + c.length, 0) + summary.types.reduce((s, t) => s + t.length, 0) + summary.routes.reduce((s, r) => s + r.length, 0);
900
915
  }
901
916
 
917
+ // src/api/client.ts
918
+ init_constants();
919
+ async function forceRefreshToken() {
920
+ const auth2 = getStoredAuth();
921
+ if (!auth2) return null;
922
+ try {
923
+ const resp = await fetch(`${API_URL}/api/auth/refresh`, {
924
+ method: "POST",
925
+ headers: { "Content-Type": "application/json" },
926
+ body: JSON.stringify({ refreshToken: auth2.refreshToken })
927
+ });
928
+ if (!resp.ok) return null;
929
+ const { data } = await resp.json();
930
+ storeAuth({
931
+ accessToken: data.accessToken,
932
+ refreshToken: data.refreshToken,
933
+ expiresIn: data.expiresIn,
934
+ user: { id: auth2.userId, email: auth2.email }
935
+ });
936
+ return data.accessToken;
937
+ } catch {
938
+ return null;
939
+ }
940
+ }
941
+ async function getValidToken() {
942
+ const auth2 = getStoredAuth();
943
+ if (!auth2) {
944
+ throw new Error("Not authenticated. Run `caliber login` first.");
945
+ }
946
+ if (!isTokenExpired()) return auth2.accessToken;
947
+ const refreshed = await forceRefreshToken();
948
+ if (!refreshed) {
949
+ throw new Error("Session expired. Run `caliber login` to re-authenticate.");
950
+ }
951
+ return refreshed;
952
+ }
953
+ async function apiRequest(path21, options = {}) {
954
+ let token = await getValidToken();
955
+ let resp = await fetch(`${API_URL}${path21}`, {
956
+ method: options.method || "GET",
957
+ headers: {
958
+ "Content-Type": "application/json",
959
+ Authorization: `Bearer ${token}`
960
+ },
961
+ body: options.body ? JSON.stringify(options.body) : void 0
962
+ });
963
+ if (resp.status === 401) {
964
+ const refreshed = await forceRefreshToken();
965
+ if (!refreshed) {
966
+ throw new Error("Session expired. Run `caliber login` to re-authenticate.");
967
+ }
968
+ token = refreshed;
969
+ resp = await fetch(`${API_URL}${path21}`, {
970
+ method: options.method || "GET",
971
+ headers: {
972
+ "Content-Type": "application/json",
973
+ Authorization: `Bearer ${token}`
974
+ },
975
+ body: options.body ? JSON.stringify(options.body) : void 0
976
+ });
977
+ }
978
+ if (!resp.ok) {
979
+ const error = await resp.json().catch(() => ({ error: resp.statusText }));
980
+ throw new Error(error.error || `API error: ${resp.status}`);
981
+ }
982
+ const json = await resp.json();
983
+ return json.data;
984
+ }
985
+ async function apiStream(path21, body, onChunk, onComplete, onError, onStatus) {
986
+ let token = await getValidToken();
987
+ let resp = await fetch(`${API_URL}${path21}`, {
988
+ method: "POST",
989
+ headers: {
990
+ "Content-Type": "application/json",
991
+ Authorization: `Bearer ${token}`
992
+ },
993
+ body: JSON.stringify(body)
994
+ });
995
+ if (resp.status === 401) {
996
+ const refreshed = await forceRefreshToken();
997
+ if (!refreshed) {
998
+ throw new Error("Session expired. Run `caliber login` to re-authenticate.");
999
+ }
1000
+ token = refreshed;
1001
+ resp = await fetch(`${API_URL}${path21}`, {
1002
+ method: "POST",
1003
+ headers: {
1004
+ "Content-Type": "application/json",
1005
+ Authorization: `Bearer ${token}`
1006
+ },
1007
+ body: JSON.stringify(body)
1008
+ });
1009
+ }
1010
+ if (!resp.ok || !resp.body) {
1011
+ throw new Error(`API error: ${resp.status}`);
1012
+ }
1013
+ const reader = resp.body.getReader();
1014
+ const decoder = new TextDecoder();
1015
+ let buffer = "";
1016
+ while (true) {
1017
+ const { done, value } = await reader.read();
1018
+ if (done) break;
1019
+ buffer += decoder.decode(value, { stream: true });
1020
+ const lines = buffer.split("\n");
1021
+ buffer = lines.pop() || "";
1022
+ for (const line of lines) {
1023
+ if (!line.startsWith("data: ")) continue;
1024
+ const data = line.slice(6);
1025
+ if (data === "[DONE]") return;
1026
+ try {
1027
+ const parsed = JSON.parse(data);
1028
+ if (parsed.type === "chunk") onChunk(parsed.content);
1029
+ else if (parsed.type === "status" && onStatus) onStatus(parsed.message);
1030
+ else if (parsed.type === "complete") onComplete({ setup: parsed.setup, explanation: parsed.explanation, raw: parsed.raw });
1031
+ else if (parsed.type === "error") onError(parsed.message);
1032
+ } catch {
1033
+ }
1034
+ }
1035
+ }
1036
+ }
1037
+
902
1038
  // src/fingerprint/index.ts
903
1039
  function collectFingerprint(dir) {
904
1040
  const gitRemoteUrl = getGitRemoteUrl();
@@ -925,15 +1061,68 @@ function computeFingerprintHash(fingerprint) {
925
1061
  ].join("::");
926
1062
  return crypto2.createHash("sha256").update(key).digest("hex");
927
1063
  }
1064
+ var DEP_FILE_PATTERNS = [
1065
+ "package.json",
1066
+ "pyproject.toml",
1067
+ "requirements.txt",
1068
+ "setup.py",
1069
+ "Pipfile",
1070
+ "Cargo.toml",
1071
+ "go.mod",
1072
+ "Gemfile",
1073
+ "build.gradle",
1074
+ "pom.xml",
1075
+ "composer.json"
1076
+ ];
1077
+ var MAX_CONTENT_SIZE = 50 * 1024;
1078
+ async function enrichFingerprintWithLLM(fingerprint, dir) {
1079
+ try {
1080
+ const fileContents = {};
1081
+ let totalSize = 0;
1082
+ for (const treePath of fingerprint.fileTree) {
1083
+ const basename = path9.basename(treePath);
1084
+ if (!DEP_FILE_PATTERNS.includes(basename)) continue;
1085
+ const fullPath = path9.join(dir, treePath);
1086
+ if (!fs8.existsSync(fullPath)) continue;
1087
+ try {
1088
+ const content = fs8.readFileSync(fullPath, "utf-8");
1089
+ if (totalSize + content.length > MAX_CONTENT_SIZE) break;
1090
+ fileContents[treePath] = content;
1091
+ totalSize += content.length;
1092
+ } catch {
1093
+ continue;
1094
+ }
1095
+ }
1096
+ if (Object.keys(fileContents).length === 0 && fingerprint.fileTree.length === 0) return;
1097
+ const result = await apiRequest(
1098
+ "/api/fingerprint/detect",
1099
+ {
1100
+ method: "POST",
1101
+ body: { fileTree: fingerprint.fileTree, fileContents }
1102
+ }
1103
+ );
1104
+ if (result.languages?.length) {
1105
+ const langSet = new Set(fingerprint.languages);
1106
+ for (const lang of result.languages) langSet.add(lang);
1107
+ fingerprint.languages = [...langSet];
1108
+ }
1109
+ if (result.frameworks?.length) {
1110
+ const fwSet = new Set(fingerprint.frameworks);
1111
+ for (const fw of result.frameworks) fwSet.add(fw);
1112
+ fingerprint.frameworks = [...fwSet];
1113
+ }
1114
+ } catch {
1115
+ }
1116
+ }
928
1117
 
929
1118
  // src/scanner/index.ts
930
- import fs8 from "fs";
931
- import path9 from "path";
1119
+ import fs9 from "fs";
1120
+ import path10 from "path";
932
1121
  import crypto3 from "crypto";
933
1122
  function scanLocalState(dir) {
934
1123
  const items = [];
935
- const claudeMdPath = path9.join(dir, "CLAUDE.md");
936
- if (fs8.existsSync(claudeMdPath)) {
1124
+ const claudeMdPath = path10.join(dir, "CLAUDE.md");
1125
+ if (fs9.existsSync(claudeMdPath)) {
937
1126
  items.push({
938
1127
  type: "rule",
939
1128
  platform: "claude",
@@ -942,10 +1131,10 @@ function scanLocalState(dir) {
942
1131
  path: claudeMdPath
943
1132
  });
944
1133
  }
945
- const skillsDir = path9.join(dir, ".claude", "skills");
946
- if (fs8.existsSync(skillsDir)) {
947
- for (const file of fs8.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
948
- const filePath = path9.join(skillsDir, file);
1134
+ const skillsDir = path10.join(dir, ".claude", "skills");
1135
+ if (fs9.existsSync(skillsDir)) {
1136
+ for (const file of fs9.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
1137
+ const filePath = path10.join(skillsDir, file);
949
1138
  items.push({
950
1139
  type: "skill",
951
1140
  platform: "claude",
@@ -955,10 +1144,10 @@ function scanLocalState(dir) {
955
1144
  });
956
1145
  }
957
1146
  }
958
- const mcpJsonPath = path9.join(dir, ".mcp.json");
959
- if (fs8.existsSync(mcpJsonPath)) {
1147
+ const mcpJsonPath = path10.join(dir, ".mcp.json");
1148
+ if (fs9.existsSync(mcpJsonPath)) {
960
1149
  try {
961
- const mcpJson = JSON.parse(fs8.readFileSync(mcpJsonPath, "utf-8"));
1150
+ const mcpJson = JSON.parse(fs9.readFileSync(mcpJsonPath, "utf-8"));
962
1151
  if (mcpJson.mcpServers) {
963
1152
  for (const name of Object.keys(mcpJson.mcpServers)) {
964
1153
  items.push({
@@ -973,8 +1162,8 @@ function scanLocalState(dir) {
973
1162
  } catch {
974
1163
  }
975
1164
  }
976
- const cursorrulesPath = path9.join(dir, ".cursorrules");
977
- if (fs8.existsSync(cursorrulesPath)) {
1165
+ const cursorrulesPath = path10.join(dir, ".cursorrules");
1166
+ if (fs9.existsSync(cursorrulesPath)) {
978
1167
  items.push({
979
1168
  type: "rule",
980
1169
  platform: "cursor",
@@ -983,10 +1172,10 @@ function scanLocalState(dir) {
983
1172
  path: cursorrulesPath
984
1173
  });
985
1174
  }
986
- const cursorRulesDir = path9.join(dir, ".cursor", "rules");
987
- if (fs8.existsSync(cursorRulesDir)) {
988
- for (const file of fs8.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
989
- const filePath = path9.join(cursorRulesDir, file);
1175
+ const cursorRulesDir = path10.join(dir, ".cursor", "rules");
1176
+ if (fs9.existsSync(cursorRulesDir)) {
1177
+ for (const file of fs9.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
1178
+ const filePath = path10.join(cursorRulesDir, file);
990
1179
  items.push({
991
1180
  type: "rule",
992
1181
  platform: "cursor",
@@ -996,10 +1185,28 @@ function scanLocalState(dir) {
996
1185
  });
997
1186
  }
998
1187
  }
999
- const cursorMcpPath = path9.join(dir, ".cursor", "mcp.json");
1000
- if (fs8.existsSync(cursorMcpPath)) {
1188
+ const cursorSkillsDir = path10.join(dir, ".cursor", "skills");
1189
+ if (fs9.existsSync(cursorSkillsDir)) {
1190
+ try {
1191
+ for (const slug of fs9.readdirSync(cursorSkillsDir)) {
1192
+ const skillFile = path10.join(cursorSkillsDir, slug, "SKILL.md");
1193
+ if (fs9.existsSync(skillFile)) {
1194
+ items.push({
1195
+ type: "skill",
1196
+ platform: "cursor",
1197
+ name: `${slug}/SKILL.md`,
1198
+ contentHash: hashFile(skillFile),
1199
+ path: skillFile
1200
+ });
1201
+ }
1202
+ }
1203
+ } catch {
1204
+ }
1205
+ }
1206
+ const cursorMcpPath = path10.join(dir, ".cursor", "mcp.json");
1207
+ if (fs9.existsSync(cursorMcpPath)) {
1001
1208
  try {
1002
- const mcpJson = JSON.parse(fs8.readFileSync(cursorMcpPath, "utf-8"));
1209
+ const mcpJson = JSON.parse(fs9.readFileSync(cursorMcpPath, "utf-8"));
1003
1210
  if (mcpJson.mcpServers) {
1004
1211
  for (const name of Object.keys(mcpJson.mcpServers)) {
1005
1212
  items.push({
@@ -1043,196 +1250,84 @@ function compareState(serverItems, localItems) {
1043
1250
  return { installed, missing, outdated, extra };
1044
1251
  }
1045
1252
  function hashFile(filePath) {
1046
- const text = fs8.readFileSync(filePath, "utf-8");
1253
+ const text = fs9.readFileSync(filePath, "utf-8");
1047
1254
  return crypto3.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
1048
1255
  }
1049
1256
  function hashJson(obj) {
1050
1257
  return crypto3.createHash("sha256").update(JSON.stringify(obj)).digest("hex");
1051
1258
  }
1052
1259
 
1053
- // src/api/client.ts
1054
- init_constants();
1055
- async function forceRefreshToken() {
1056
- const auth2 = getStoredAuth();
1057
- if (!auth2) return null;
1058
- try {
1059
- const resp = await fetch(`${API_URL}/api/auth/refresh`, {
1060
- method: "POST",
1061
- headers: { "Content-Type": "application/json" },
1062
- body: JSON.stringify({ refreshToken: auth2.refreshToken })
1063
- });
1064
- if (!resp.ok) return null;
1065
- const { data } = await resp.json();
1066
- storeAuth({
1067
- accessToken: data.accessToken,
1068
- refreshToken: data.refreshToken,
1069
- expiresIn: data.expiresIn,
1070
- user: { id: auth2.userId, email: auth2.email }
1071
- });
1072
- return data.accessToken;
1073
- } catch {
1074
- return null;
1075
- }
1076
- }
1077
- async function getValidToken() {
1078
- const auth2 = getStoredAuth();
1079
- if (!auth2) {
1080
- throw new Error("Not authenticated. Run `caliber login` first.");
1081
- }
1082
- if (!isTokenExpired()) return auth2.accessToken;
1083
- const refreshed = await forceRefreshToken();
1084
- if (!refreshed) {
1085
- throw new Error("Session expired. Run `caliber login` to re-authenticate.");
1086
- }
1087
- return refreshed;
1088
- }
1089
- async function apiRequest(path19, options = {}) {
1090
- let token = await getValidToken();
1091
- let resp = await fetch(`${API_URL}${path19}`, {
1092
- method: options.method || "GET",
1093
- headers: {
1094
- "Content-Type": "application/json",
1095
- Authorization: `Bearer ${token}`
1096
- },
1097
- body: options.body ? JSON.stringify(options.body) : void 0
1098
- });
1099
- if (resp.status === 401) {
1100
- const refreshed = await forceRefreshToken();
1101
- if (!refreshed) {
1102
- throw new Error("Session expired. Run `caliber login` to re-authenticate.");
1103
- }
1104
- token = refreshed;
1105
- resp = await fetch(`${API_URL}${path19}`, {
1106
- method: options.method || "GET",
1107
- headers: {
1108
- "Content-Type": "application/json",
1109
- Authorization: `Bearer ${token}`
1110
- },
1111
- body: options.body ? JSON.stringify(options.body) : void 0
1112
- });
1113
- }
1114
- if (!resp.ok) {
1115
- const error = await resp.json().catch(() => ({ error: resp.statusText }));
1116
- throw new Error(error.error || `API error: ${resp.status}`);
1117
- }
1118
- const json = await resp.json();
1119
- return json.data;
1120
- }
1121
- async function apiStream(path19, body, onChunk, onComplete, onError, onStatus) {
1122
- let token = await getValidToken();
1123
- let resp = await fetch(`${API_URL}${path19}`, {
1124
- method: "POST",
1125
- headers: {
1126
- "Content-Type": "application/json",
1127
- Authorization: `Bearer ${token}`
1128
- },
1129
- body: JSON.stringify(body)
1130
- });
1131
- if (resp.status === 401) {
1132
- const refreshed = await forceRefreshToken();
1133
- if (!refreshed) {
1134
- throw new Error("Session expired. Run `caliber login` to re-authenticate.");
1135
- }
1136
- token = refreshed;
1137
- resp = await fetch(`${API_URL}${path19}`, {
1138
- method: "POST",
1139
- headers: {
1140
- "Content-Type": "application/json",
1141
- Authorization: `Bearer ${token}`
1142
- },
1143
- body: JSON.stringify(body)
1144
- });
1145
- }
1146
- if (!resp.ok || !resp.body) {
1147
- throw new Error(`API error: ${resp.status}`);
1148
- }
1149
- const reader = resp.body.getReader();
1150
- const decoder = new TextDecoder();
1151
- let buffer = "";
1152
- while (true) {
1153
- const { done, value } = await reader.read();
1154
- if (done) break;
1155
- buffer += decoder.decode(value, { stream: true });
1156
- const lines = buffer.split("\n");
1157
- buffer = lines.pop() || "";
1158
- for (const line of lines) {
1159
- if (!line.startsWith("data: ")) continue;
1160
- const data = line.slice(6);
1161
- if (data === "[DONE]") return;
1162
- try {
1163
- const parsed = JSON.parse(data);
1164
- if (parsed.type === "chunk") onChunk(parsed.content);
1165
- else if (parsed.type === "status" && onStatus) onStatus(parsed.message);
1166
- else if (parsed.type === "complete") onComplete({ setup: parsed.setup, explanation: parsed.explanation, raw: parsed.raw });
1167
- else if (parsed.type === "error") onError(parsed.message);
1168
- } catch {
1169
- }
1170
- }
1171
- }
1172
- }
1173
-
1174
1260
  // src/writers/index.ts
1175
- import fs13 from "fs";
1261
+ import fs14 from "fs";
1176
1262
 
1177
1263
  // src/writers/claude/index.ts
1178
- import fs9 from "fs";
1179
- import path10 from "path";
1264
+ import fs10 from "fs";
1265
+ import path11 from "path";
1180
1266
  function writeClaudeConfig(config) {
1181
1267
  const written = [];
1182
- fs9.writeFileSync("CLAUDE.md", config.claudeMd);
1268
+ fs10.writeFileSync("CLAUDE.md", config.claudeMd);
1183
1269
  written.push("CLAUDE.md");
1184
1270
  if (config.skills?.length) {
1185
- const skillsDir = path10.join(".claude", "skills");
1186
- if (!fs9.existsSync(skillsDir)) fs9.mkdirSync(skillsDir, { recursive: true });
1271
+ const skillsDir = path11.join(".claude", "skills");
1272
+ if (!fs10.existsSync(skillsDir)) fs10.mkdirSync(skillsDir, { recursive: true });
1187
1273
  for (const skill of config.skills) {
1188
1274
  const filename = `${skill.name.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}.md`;
1189
- const skillPath = path10.join(skillsDir, filename);
1190
- fs9.writeFileSync(skillPath, skill.content);
1275
+ const skillPath = path11.join(skillsDir, filename);
1276
+ fs10.writeFileSync(skillPath, skill.content);
1191
1277
  written.push(skillPath);
1192
1278
  }
1193
1279
  }
1194
1280
  if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
1195
1281
  let existingServers = {};
1196
1282
  try {
1197
- if (fs9.existsSync(".mcp.json")) {
1198
- const existing = JSON.parse(fs9.readFileSync(".mcp.json", "utf-8"));
1283
+ if (fs10.existsSync(".mcp.json")) {
1284
+ const existing = JSON.parse(fs10.readFileSync(".mcp.json", "utf-8"));
1199
1285
  if (existing.mcpServers) existingServers = existing.mcpServers;
1200
1286
  }
1201
1287
  } catch {
1202
1288
  }
1203
1289
  const mergedServers = { ...existingServers, ...config.mcpServers };
1204
- fs9.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
1290
+ fs10.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
1205
1291
  written.push(".mcp.json");
1206
1292
  }
1207
1293
  return written;
1208
1294
  }
1209
1295
 
1210
1296
  // src/writers/cursor/index.ts
1211
- import fs10 from "fs";
1212
- import path11 from "path";
1297
+ import fs11 from "fs";
1298
+ import path12 from "path";
1213
1299
  function writeCursorConfig(config) {
1214
1300
  const written = [];
1215
1301
  if (config.cursorrules) {
1216
- fs10.writeFileSync(".cursorrules", config.cursorrules);
1302
+ fs11.writeFileSync(".cursorrules", config.cursorrules);
1217
1303
  written.push(".cursorrules");
1218
1304
  }
1219
1305
  if (config.rules?.length) {
1220
- const rulesDir = path11.join(".cursor", "rules");
1221
- if (!fs10.existsSync(rulesDir)) fs10.mkdirSync(rulesDir, { recursive: true });
1306
+ const rulesDir = path12.join(".cursor", "rules");
1307
+ if (!fs11.existsSync(rulesDir)) fs11.mkdirSync(rulesDir, { recursive: true });
1222
1308
  for (const rule of config.rules) {
1223
- const rulePath = path11.join(rulesDir, rule.filename);
1224
- fs10.writeFileSync(rulePath, rule.content);
1309
+ const rulePath = path12.join(rulesDir, rule.filename);
1310
+ fs11.writeFileSync(rulePath, rule.content);
1225
1311
  written.push(rulePath);
1226
1312
  }
1227
1313
  }
1314
+ if (config.skills?.length) {
1315
+ for (const skill of config.skills) {
1316
+ const skillDir = path12.join(".cursor", "skills", skill.slug);
1317
+ if (!fs11.existsSync(skillDir)) fs11.mkdirSync(skillDir, { recursive: true });
1318
+ const skillPath = path12.join(skillDir, "SKILL.md");
1319
+ fs11.writeFileSync(skillPath, skill.content);
1320
+ written.push(skillPath);
1321
+ }
1322
+ }
1228
1323
  if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
1229
1324
  const cursorDir = ".cursor";
1230
- if (!fs10.existsSync(cursorDir)) fs10.mkdirSync(cursorDir, { recursive: true });
1231
- const mcpPath = path11.join(cursorDir, "mcp.json");
1325
+ if (!fs11.existsSync(cursorDir)) fs11.mkdirSync(cursorDir, { recursive: true });
1326
+ const mcpPath = path12.join(cursorDir, "mcp.json");
1232
1327
  let existingServers = {};
1233
1328
  try {
1234
- if (fs10.existsSync(mcpPath)) {
1235
- const existing = JSON.parse(fs10.readFileSync(mcpPath, "utf-8"));
1329
+ if (fs11.existsSync(mcpPath)) {
1330
+ const existing = JSON.parse(fs11.readFileSync(mcpPath, "utf-8"));
1236
1331
  if (existing.mcpServers) {
1237
1332
  existingServers = existing.mcpServers;
1238
1333
  }
@@ -1240,7 +1335,7 @@ function writeCursorConfig(config) {
1240
1335
  } catch {
1241
1336
  }
1242
1337
  const mergedServers = { ...existingServers, ...config.mcpServers };
1243
- fs10.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
1338
+ fs11.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
1244
1339
  written.push(mcpPath);
1245
1340
  }
1246
1341
  return written;
@@ -1248,62 +1343,62 @@ function writeCursorConfig(config) {
1248
1343
 
1249
1344
  // src/writers/backup.ts
1250
1345
  init_constants();
1251
- import fs11 from "fs";
1252
- import path12 from "path";
1346
+ import fs12 from "fs";
1347
+ import path13 from "path";
1253
1348
  function createBackup(files) {
1254
1349
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1255
- const backupDir = path12.join(BACKUPS_DIR, timestamp);
1350
+ const backupDir = path13.join(BACKUPS_DIR, timestamp);
1256
1351
  for (const file of files) {
1257
- if (!fs11.existsSync(file)) continue;
1258
- const dest = path12.join(backupDir, file);
1259
- const destDir = path12.dirname(dest);
1260
- if (!fs11.existsSync(destDir)) {
1261
- fs11.mkdirSync(destDir, { recursive: true });
1352
+ if (!fs12.existsSync(file)) continue;
1353
+ const dest = path13.join(backupDir, file);
1354
+ const destDir = path13.dirname(dest);
1355
+ if (!fs12.existsSync(destDir)) {
1356
+ fs12.mkdirSync(destDir, { recursive: true });
1262
1357
  }
1263
- fs11.copyFileSync(file, dest);
1358
+ fs12.copyFileSync(file, dest);
1264
1359
  }
1265
1360
  return backupDir;
1266
1361
  }
1267
1362
  function restoreBackup(backupDir, file) {
1268
- const backupFile = path12.join(backupDir, file);
1269
- if (!fs11.existsSync(backupFile)) return false;
1270
- const destDir = path12.dirname(file);
1271
- if (!fs11.existsSync(destDir)) {
1272
- fs11.mkdirSync(destDir, { recursive: true });
1363
+ const backupFile = path13.join(backupDir, file);
1364
+ if (!fs12.existsSync(backupFile)) return false;
1365
+ const destDir = path13.dirname(file);
1366
+ if (!fs12.existsSync(destDir)) {
1367
+ fs12.mkdirSync(destDir, { recursive: true });
1273
1368
  }
1274
- fs11.copyFileSync(backupFile, file);
1369
+ fs12.copyFileSync(backupFile, file);
1275
1370
  return true;
1276
1371
  }
1277
1372
 
1278
1373
  // src/writers/manifest.ts
1279
1374
  init_constants();
1280
- import fs12 from "fs";
1375
+ import fs13 from "fs";
1281
1376
  import crypto4 from "crypto";
1282
1377
  function readManifest() {
1283
1378
  try {
1284
- if (!fs12.existsSync(MANIFEST_FILE)) return null;
1285
- return JSON.parse(fs12.readFileSync(MANIFEST_FILE, "utf-8"));
1379
+ if (!fs13.existsSync(MANIFEST_FILE)) return null;
1380
+ return JSON.parse(fs13.readFileSync(MANIFEST_FILE, "utf-8"));
1286
1381
  } catch {
1287
1382
  return null;
1288
1383
  }
1289
1384
  }
1290
1385
  function writeManifest(manifest) {
1291
- if (!fs12.existsSync(CALIBER_DIR)) {
1292
- fs12.mkdirSync(CALIBER_DIR, { recursive: true });
1386
+ if (!fs13.existsSync(CALIBER_DIR)) {
1387
+ fs13.mkdirSync(CALIBER_DIR, { recursive: true });
1293
1388
  }
1294
- fs12.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
1389
+ fs13.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
1295
1390
  }
1296
1391
  function fileChecksum(filePath) {
1297
- const content = fs12.readFileSync(filePath);
1392
+ const content = fs13.readFileSync(filePath);
1298
1393
  return crypto4.createHash("sha256").update(content).digest("hex");
1299
1394
  }
1300
1395
 
1301
1396
  // src/writers/index.ts
1302
1397
  function writeSetup(setup) {
1303
1398
  const filesToWrite = getFilesToWrite(setup);
1304
- const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs13.existsSync(f));
1399
+ const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs14.existsSync(f));
1305
1400
  const existingFiles = [
1306
- ...filesToWrite.filter((f) => fs13.existsSync(f)),
1401
+ ...filesToWrite.filter((f) => fs14.existsSync(f)),
1307
1402
  ...filesToDelete
1308
1403
  ];
1309
1404
  const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
@@ -1316,7 +1411,7 @@ function writeSetup(setup) {
1316
1411
  }
1317
1412
  const deleted = [];
1318
1413
  for (const filePath of filesToDelete) {
1319
- fs13.unlinkSync(filePath);
1414
+ fs14.unlinkSync(filePath);
1320
1415
  deleted.push(filePath);
1321
1416
  }
1322
1417
  ensureGitignore();
@@ -1346,8 +1441,8 @@ function undoSetup() {
1346
1441
  const removed = [];
1347
1442
  for (const entry of manifest.entries) {
1348
1443
  if (entry.action === "created") {
1349
- if (fs13.existsSync(entry.path)) {
1350
- fs13.unlinkSync(entry.path);
1444
+ if (fs14.existsSync(entry.path)) {
1445
+ fs14.unlinkSync(entry.path);
1351
1446
  removed.push(entry.path);
1352
1447
  }
1353
1448
  } else if ((entry.action === "modified" || entry.action === "deleted") && manifest.backupDir) {
@@ -1357,8 +1452,8 @@ function undoSetup() {
1357
1452
  }
1358
1453
  }
1359
1454
  const { MANIFEST_FILE: MANIFEST_FILE2 } = (init_constants(), __toCommonJS(constants_exports));
1360
- if (fs13.existsSync(MANIFEST_FILE2)) {
1361
- fs13.unlinkSync(MANIFEST_FILE2);
1455
+ if (fs14.existsSync(MANIFEST_FILE2)) {
1456
+ fs14.unlinkSync(MANIFEST_FILE2);
1362
1457
  }
1363
1458
  return { restored, removed };
1364
1459
  }
@@ -1378,40 +1473,123 @@ function getFilesToWrite(setup) {
1378
1473
  if (setup.cursor.rules) {
1379
1474
  for (const r of setup.cursor.rules) files.push(`.cursor/rules/${r.filename}`);
1380
1475
  }
1476
+ if (setup.cursor.skills) {
1477
+ for (const s of setup.cursor.skills) files.push(`.cursor/skills/${s.slug}/SKILL.md`);
1478
+ }
1381
1479
  if (setup.cursor.mcpServers) files.push(".cursor/mcp.json");
1382
1480
  }
1383
1481
  return files;
1384
1482
  }
1385
1483
  function ensureGitignore() {
1386
1484
  const gitignorePath = ".gitignore";
1387
- if (fs13.existsSync(gitignorePath)) {
1388
- const content = fs13.readFileSync(gitignorePath, "utf-8");
1485
+ if (fs14.existsSync(gitignorePath)) {
1486
+ const content = fs14.readFileSync(gitignorePath, "utf-8");
1389
1487
  if (!content.includes(".caliber/")) {
1390
- fs13.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
1488
+ fs14.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
1391
1489
  }
1392
1490
  } else {
1393
- fs13.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
1491
+ fs14.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
1492
+ }
1493
+ }
1494
+
1495
+ // src/writers/staging.ts
1496
+ init_constants();
1497
+ import fs15 from "fs";
1498
+ import path14 from "path";
1499
+ var STAGED_DIR = path14.join(CALIBER_DIR, "staged");
1500
+ var PROPOSED_DIR = path14.join(STAGED_DIR, "proposed");
1501
+ var CURRENT_DIR = path14.join(STAGED_DIR, "current");
1502
+ function stageFiles(files, projectDir) {
1503
+ cleanupStaging();
1504
+ let newFiles = 0;
1505
+ let modifiedFiles = 0;
1506
+ for (const file of files) {
1507
+ const proposedPath = path14.join(PROPOSED_DIR, file.path);
1508
+ fs15.mkdirSync(path14.dirname(proposedPath), { recursive: true });
1509
+ fs15.writeFileSync(proposedPath, file.content);
1510
+ const originalPath = path14.join(projectDir, file.path);
1511
+ if (fs15.existsSync(originalPath)) {
1512
+ const currentPath = path14.join(CURRENT_DIR, file.path);
1513
+ fs15.mkdirSync(path14.dirname(currentPath), { recursive: true });
1514
+ fs15.copyFileSync(originalPath, currentPath);
1515
+ modifiedFiles++;
1516
+ } else {
1517
+ newFiles++;
1518
+ }
1519
+ }
1520
+ return { newFiles, modifiedFiles };
1521
+ }
1522
+ function getStagedProposedDir() {
1523
+ return PROPOSED_DIR;
1524
+ }
1525
+ function cleanupStaging() {
1526
+ if (fs15.existsSync(STAGED_DIR)) {
1527
+ fs15.rmSync(STAGED_DIR, { recursive: true, force: true });
1528
+ }
1529
+ }
1530
+
1531
+ // src/utils/editor.ts
1532
+ import { execSync as execSync2 } from "child_process";
1533
+ function commandExists(cmd) {
1534
+ try {
1535
+ execSync2(`which ${cmd}`, { stdio: "ignore" });
1536
+ return true;
1537
+ } catch {
1538
+ return false;
1539
+ }
1540
+ }
1541
+ function openInEditor(dirPath) {
1542
+ const editors = ["cursor", "code"];
1543
+ for (const editor of editors) {
1544
+ if (commandExists(editor)) {
1545
+ try {
1546
+ execSync2(`${editor} --diff "${dirPath}" "${dirPath}"`, { stdio: "ignore" });
1547
+ return true;
1548
+ } catch {
1549
+ try {
1550
+ execSync2(`${editor} "${dirPath}"`, { stdio: "ignore" });
1551
+ return true;
1552
+ } catch {
1553
+ continue;
1554
+ }
1555
+ }
1556
+ }
1557
+ }
1558
+ const envEditor = process.env.EDITOR;
1559
+ if (envEditor && commandExists(envEditor)) {
1560
+ try {
1561
+ execSync2(`${envEditor} "${dirPath}"`, { stdio: "ignore" });
1562
+ return true;
1563
+ } catch {
1564
+ }
1565
+ }
1566
+ try {
1567
+ const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
1568
+ execSync2(`${openCmd} "${dirPath}"`, { stdio: "ignore" });
1569
+ return true;
1570
+ } catch {
1571
+ return false;
1394
1572
  }
1395
1573
  }
1396
1574
 
1397
1575
  // src/lib/hooks.ts
1398
- import fs14 from "fs";
1399
- import path13 from "path";
1400
- var SETTINGS_PATH = path13.join(".claude", "settings.json");
1576
+ import fs16 from "fs";
1577
+ import path15 from "path";
1578
+ var SETTINGS_PATH = path15.join(".claude", "settings.json");
1401
1579
  var HOOK_COMMAND = "caliber refresh --quiet";
1402
1580
  var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
1403
1581
  function readSettings() {
1404
- if (!fs14.existsSync(SETTINGS_PATH)) return {};
1582
+ if (!fs16.existsSync(SETTINGS_PATH)) return {};
1405
1583
  try {
1406
- return JSON.parse(fs14.readFileSync(SETTINGS_PATH, "utf-8"));
1584
+ return JSON.parse(fs16.readFileSync(SETTINGS_PATH, "utf-8"));
1407
1585
  } catch {
1408
1586
  return {};
1409
1587
  }
1410
1588
  }
1411
1589
  function writeSettings(settings) {
1412
- const dir = path13.dirname(SETTINGS_PATH);
1413
- if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
1414
- fs14.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
1590
+ const dir = path15.dirname(SETTINGS_PATH);
1591
+ if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
1592
+ fs16.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
1415
1593
  }
1416
1594
  function findHookIndex(sessionEnd) {
1417
1595
  return sessionEnd.findIndex(
@@ -1461,27 +1639,27 @@ function removeHook() {
1461
1639
 
1462
1640
  // src/lib/state.ts
1463
1641
  init_constants();
1464
- import fs15 from "fs";
1465
- import path14 from "path";
1466
- import { execSync as execSync2 } from "child_process";
1467
- var STATE_FILE = path14.join(CALIBER_DIR, ".caliber-state.json");
1642
+ import fs17 from "fs";
1643
+ import path16 from "path";
1644
+ import { execSync as execSync3 } from "child_process";
1645
+ var STATE_FILE = path16.join(CALIBER_DIR, ".caliber-state.json");
1468
1646
  function readState() {
1469
1647
  try {
1470
- if (!fs15.existsSync(STATE_FILE)) return null;
1471
- return JSON.parse(fs15.readFileSync(STATE_FILE, "utf-8"));
1648
+ if (!fs17.existsSync(STATE_FILE)) return null;
1649
+ return JSON.parse(fs17.readFileSync(STATE_FILE, "utf-8"));
1472
1650
  } catch {
1473
1651
  return null;
1474
1652
  }
1475
1653
  }
1476
1654
  function writeState(state) {
1477
- if (!fs15.existsSync(CALIBER_DIR)) {
1478
- fs15.mkdirSync(CALIBER_DIR, { recursive: true });
1655
+ if (!fs17.existsSync(CALIBER_DIR)) {
1656
+ fs17.mkdirSync(CALIBER_DIR, { recursive: true });
1479
1657
  }
1480
- fs15.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
1658
+ fs17.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
1481
1659
  }
1482
1660
  function getCurrentHeadSha() {
1483
1661
  try {
1484
- return execSync2("git rev-parse HEAD", {
1662
+ return execSync3("git rev-parse HEAD", {
1485
1663
  encoding: "utf-8",
1486
1664
  stdio: ["pipe", "pipe", "pipe"]
1487
1665
  }).trim();
@@ -1611,6 +1789,7 @@ async function initCommand(options) {
1611
1789
  const fingerprint = collectFingerprint(process.cwd());
1612
1790
  const hash = computeFingerprintHash(fingerprint);
1613
1791
  spinner.succeed("Project analyzed");
1792
+ const enrichmentPromise = enrichFingerprintWithLLM(fingerprint, process.cwd());
1614
1793
  trackEvent("scan_completed", {
1615
1794
  languages: fingerprint.languages,
1616
1795
  frameworks: fingerprint.frameworks,
@@ -1619,6 +1798,7 @@ async function initCommand(options) {
1619
1798
  has_claude_settings: !!fingerprint.existingConfigs.claudeSettings,
1620
1799
  has_cursorrules: !!fingerprint.existingConfigs.cursorrules,
1621
1800
  cursor_rules_count: fingerprint.existingConfigs.cursorRules?.length ?? 0,
1801
+ cursor_skills_count: fingerprint.existingConfigs.cursorSkills?.length ?? 0,
1622
1802
  skills_count: fingerprint.existingConfigs.claudeSkills?.length ?? 0,
1623
1803
  has_claude_mcp_servers: !!fingerprint.existingConfigs.claudeMcpServers,
1624
1804
  has_cursor_mcp_servers: !!fingerprint.existingConfigs.cursorMcpServers,
@@ -1648,6 +1828,11 @@ async function initCommand(options) {
1648
1828
  localConfigs.push(`.cursor/rules/${rule.filename}`);
1649
1829
  }
1650
1830
  }
1831
+ if (Array.isArray(ec.cursorSkills)) {
1832
+ for (const skill of ec.cursorSkills) {
1833
+ localConfigs.push(`.cursor/skills/${skill.slug}/SKILL.md`);
1834
+ }
1835
+ }
1651
1836
  const localState = scanLocalState(process.cwd());
1652
1837
  const claudeMcpServers = localState.filter((i) => i.type === "mcp" && i.platform === "claude").map((i) => i.name);
1653
1838
  const cursorMcpServers = localState.filter((i) => i.type === "mcp" && i.platform === "cursor").map((i) => i.name);
@@ -1703,6 +1888,7 @@ async function initCommand(options) {
1703
1888
  if (isEmpty) {
1704
1889
  fingerprint.description = await promptInput("What will you build in this project?");
1705
1890
  }
1891
+ await enrichmentPromise;
1706
1892
  console.log(chalk3.hex("#6366f1").bold(" Step 4/6 \u2014 Auditing your configs\n"));
1707
1893
  console.log(chalk3.dim(" AI is auditing your CLAUDE.md, skills, and rules against your"));
1708
1894
  console.log(chalk3.dim(" project's actual codebase and conventions.\n"));
@@ -1762,25 +1948,41 @@ async function initCommand(options) {
1762
1948
  genSpinner.succeed(`Setup generated ${chalk3.dim(`in ${timeStr}`)}`);
1763
1949
  printSetupSummary(generatedSetup);
1764
1950
  console.log(chalk3.hex("#6366f1").bold(" Step 5/6 \u2014 Review\n"));
1765
- console.log(chalk3.dim(" Review the proposed files. You can accept, refine via chat,"));
1766
- console.log(chalk3.dim(" or preview any file to see exactly what will be written.\n"));
1767
- let action = await promptAction();
1768
- while (action === "preview") {
1769
- await promptFilePreview(generatedSetup);
1770
- action = await promptAction();
1771
- }
1772
- if (action === "decline") {
1773
- trackEvent("setup_declined");
1774
- console.log(chalk3.dim("Setup declined. No files were modified."));
1775
- return;
1951
+ const setupFiles = collectSetupFiles(generatedSetup);
1952
+ const { newFiles, modifiedFiles } = stageFiles(setupFiles, process.cwd());
1953
+ const stagedDir = getStagedProposedDir();
1954
+ const editorOpened = openInEditor(stagedDir);
1955
+ if (editorOpened) {
1956
+ console.log(chalk3.dim(" Proposed files opened in your editor for review."));
1957
+ } else {
1958
+ console.log(chalk3.dim(" Staged files written to .caliber/staged/proposed/ for review."));
1776
1959
  }
1777
- if (action === "refine") {
1960
+ console.log(chalk3.dim(` ${chalk3.green(`${newFiles} new`)} / ${chalk3.yellow(`${modifiedFiles} modified`)} file${newFiles + modifiedFiles !== 1 ? "s" : ""}
1961
+ `));
1962
+ let action = await promptReviewAction();
1963
+ while (action === "refine") {
1778
1964
  generatedSetup = await refineLoop(generatedSetup, targetAgent);
1779
1965
  if (!generatedSetup) {
1966
+ cleanupStaging();
1780
1967
  trackEvent("refinement_cancelled");
1781
1968
  console.log(chalk3.dim("Refinement cancelled. No files were modified."));
1782
1969
  return;
1783
1970
  }
1971
+ const updatedFiles = collectSetupFiles(generatedSetup);
1972
+ const restaged = stageFiles(updatedFiles, process.cwd());
1973
+ if (editorOpened) {
1974
+ console.log(chalk3.dim(" Updated files re-staged. Check your editor for changes."));
1975
+ }
1976
+ console.log(chalk3.dim(` ${chalk3.green(`${restaged.newFiles} new`)} / ${chalk3.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
1977
+ `));
1978
+ printSetupSummary(generatedSetup);
1979
+ action = await promptReviewAction();
1980
+ }
1981
+ cleanupStaging();
1982
+ if (action === "decline") {
1983
+ trackEvent("setup_declined");
1984
+ console.log(chalk3.dim("Setup declined. No files were modified."));
1985
+ return;
1784
1986
  }
1785
1987
  console.log(chalk3.hex("#6366f1").bold(" Step 6/6 \u2014 Apply & sync\n"));
1786
1988
  console.log(chalk3.dim(" Writing config files to your project and syncing to Caliber so"));
@@ -1931,13 +2133,12 @@ async function promptAgent() {
1931
2133
  ]
1932
2134
  });
1933
2135
  }
1934
- async function promptAction() {
2136
+ async function promptReviewAction() {
1935
2137
  return select({
1936
2138
  message: "What would you like to do?",
1937
2139
  choices: [
1938
2140
  { name: "Accept and apply", value: "accept" },
1939
2141
  { name: "Refine via chat", value: "refine" },
1940
- { name: "Preview a file", value: "preview" },
1941
2142
  { name: "Decline", value: "decline" }
1942
2143
  ]
1943
2144
  });
@@ -1954,7 +2155,7 @@ function printSetupSummary(setup) {
1954
2155
  };
1955
2156
  if (claude) {
1956
2157
  if (claude.claudeMd) {
1957
- const icon = fs16.existsSync("CLAUDE.md") ? chalk3.yellow("~") : chalk3.green("+");
2158
+ const icon = fs18.existsSync("CLAUDE.md") ? chalk3.yellow("~") : chalk3.green("+");
1958
2159
  const desc = getDescription("CLAUDE.md");
1959
2160
  console.log(` ${icon} ${chalk3.bold("CLAUDE.md")}`);
1960
2161
  if (desc) {
@@ -1966,7 +2167,7 @@ function printSetupSummary(setup) {
1966
2167
  if (Array.isArray(skills) && skills.length > 0) {
1967
2168
  for (const skill of skills) {
1968
2169
  const skillPath = `.claude/skills/${skill.name.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}.md`;
1969
- const icon = fs16.existsSync(skillPath) ? chalk3.yellow("~") : chalk3.green("+");
2170
+ const icon = fs18.existsSync(skillPath) ? chalk3.yellow("~") : chalk3.green("+");
1970
2171
  const desc = getDescription(skillPath);
1971
2172
  console.log(` ${icon} ${chalk3.bold(skillPath)}`);
1972
2173
  console.log(chalk3.dim(` ${desc || summarizeSkill(skill)}`));
@@ -1975,7 +2176,7 @@ function printSetupSummary(setup) {
1975
2176
  }
1976
2177
  const mcpServers = claude.mcpServers;
1977
2178
  if (mcpServers && Object.keys(mcpServers).length > 0) {
1978
- const icon = fs16.existsSync(".mcp.json") ? chalk3.yellow("~") : chalk3.green("+");
2179
+ const icon = fs18.existsSync(".mcp.json") ? chalk3.yellow("~") : chalk3.green("+");
1979
2180
  const serverNames = Object.keys(mcpServers);
1980
2181
  const desc = getDescription(".mcp.json");
1981
2182
  console.log(` ${icon} ${chalk3.bold(".mcp.json")}`);
@@ -1985,17 +2186,33 @@ function printSetupSummary(setup) {
1985
2186
  }
1986
2187
  if (cursor) {
1987
2188
  if (cursor.cursorrules) {
1988
- const icon = fs16.existsSync(".cursorrules") ? chalk3.yellow("~") : chalk3.green("+");
2189
+ const icon = fs18.existsSync(".cursorrules") ? chalk3.yellow("~") : chalk3.green("+");
1989
2190
  const desc = getDescription(".cursorrules");
1990
2191
  console.log(` ${icon} ${chalk3.bold(".cursorrules")}`);
1991
2192
  if (desc) console.log(chalk3.dim(` ${desc}`));
1992
2193
  console.log("");
1993
2194
  }
2195
+ const cursorSkills = cursor.skills;
2196
+ if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
2197
+ for (const skill of cursorSkills) {
2198
+ const skillPath = `.cursor/skills/${skill.slug}/SKILL.md`;
2199
+ const icon = fs18.existsSync(skillPath) ? chalk3.yellow("~") : chalk3.green("+");
2200
+ const desc = getDescription(skillPath);
2201
+ console.log(` ${icon} ${chalk3.bold(skillPath)}`);
2202
+ if (desc) {
2203
+ console.log(chalk3.dim(` ${desc}`));
2204
+ } else {
2205
+ const firstLine = skill.content.split("\n").filter((l) => l.trim() && !l.trim().startsWith("#") && !l.trim().startsWith("---"))[0];
2206
+ if (firstLine) console.log(chalk3.dim(` ${firstLine.trim().slice(0, 80)}`));
2207
+ }
2208
+ console.log("");
2209
+ }
2210
+ }
1994
2211
  const rules = cursor.rules;
1995
2212
  if (Array.isArray(rules) && rules.length > 0) {
1996
2213
  for (const rule of rules) {
1997
2214
  const rulePath = `.cursor/rules/${rule.filename}`;
1998
- const icon = fs16.existsSync(rulePath) ? chalk3.yellow("~") : chalk3.green("+");
2215
+ const icon = fs18.existsSync(rulePath) ? chalk3.yellow("~") : chalk3.green("+");
1999
2216
  const desc = getDescription(rulePath);
2000
2217
  console.log(` ${icon} ${chalk3.bold(rulePath)}`);
2001
2218
  if (desc) {
@@ -2009,7 +2226,7 @@ function printSetupSummary(setup) {
2009
2226
  }
2010
2227
  const mcpServers = cursor.mcpServers;
2011
2228
  if (mcpServers && Object.keys(mcpServers).length > 0) {
2012
- const icon = fs16.existsSync(".cursor/mcp.json") ? chalk3.yellow("~") : chalk3.green("+");
2229
+ const icon = fs18.existsSync(".cursor/mcp.json") ? chalk3.yellow("~") : chalk3.green("+");
2013
2230
  const serverNames = Object.keys(mcpServers);
2014
2231
  const desc = getDescription(".cursor/mcp.json");
2015
2232
  console.log(` ${icon} ${chalk3.bold(".cursor/mcp.json")}`);
@@ -2050,6 +2267,12 @@ function collectSetupFiles(setup) {
2050
2267
  }
2051
2268
  if (cursor) {
2052
2269
  if (cursor.cursorrules) files.push({ path: ".cursorrules", content: cursor.cursorrules });
2270
+ const cursorSkills = cursor.skills;
2271
+ if (Array.isArray(cursorSkills)) {
2272
+ for (const skill of cursorSkills) {
2273
+ files.push({ path: `.cursor/skills/${skill.slug}/SKILL.md`, content: skill.content });
2274
+ }
2275
+ }
2053
2276
  const rules = cursor.rules;
2054
2277
  if (Array.isArray(rules)) {
2055
2278
  for (const rule of rules) {
@@ -2062,60 +2285,6 @@ function collectSetupFiles(setup) {
2062
2285
  }
2063
2286
  return files;
2064
2287
  }
2065
- function previewNewFile(filePath, content, maxLines = 40) {
2066
- const lines = content.split("\n");
2067
- const displayLines = lines.slice(0, maxLines);
2068
- console.log("");
2069
- console.log(` ${chalk3.green("+ new")} ${chalk3.bold(filePath)}`);
2070
- console.log(chalk3.dim(" \u2500".repeat(30)));
2071
- for (const line of displayLines) {
2072
- console.log(` ${chalk3.green("+")} ${line}`);
2073
- }
2074
- if (lines.length > maxLines) {
2075
- console.log("");
2076
- console.log(chalk3.dim(` ... ${lines.length - maxLines} more lines (${lines.length} total)`));
2077
- }
2078
- console.log("");
2079
- }
2080
- function previewDiff(filePath, oldContent, newContent) {
2081
- const patch = createTwoFilesPatch(filePath, filePath, oldContent, newContent, "current", "proposed", { context: 3 });
2082
- const patchLines = patch.split("\n").slice(2);
2083
- console.log("");
2084
- console.log(` ${chalk3.yellow("~ modified")} ${chalk3.bold(filePath)}`);
2085
- console.log(chalk3.dim(" \u2500".repeat(30)));
2086
- for (const line of patchLines) {
2087
- if (line.startsWith("@@")) {
2088
- console.log(` ${chalk3.cyan(line)}`);
2089
- } else if (line.startsWith("+")) {
2090
- console.log(` ${chalk3.green(line)}`);
2091
- } else if (line.startsWith("-")) {
2092
- console.log(` ${chalk3.red(line)}`);
2093
- } else {
2094
- console.log(` ${chalk3.dim(line)}`);
2095
- }
2096
- }
2097
- console.log("");
2098
- }
2099
- async function promptFilePreview(setup) {
2100
- const files = collectSetupFiles(setup);
2101
- if (files.length === 0) {
2102
- console.log(chalk3.dim("\n No files to preview.\n"));
2103
- return;
2104
- }
2105
- const choices = files.map((f, i) => {
2106
- const exists = fs16.existsSync(f.path);
2107
- const icon = exists ? chalk3.yellow("~") : chalk3.green("+");
2108
- return { name: `${icon} ${f.path}`, value: i };
2109
- });
2110
- const choice = await select({ message: "Which file to preview?", choices });
2111
- const file = files[choice];
2112
- if (fs16.existsSync(file.path)) {
2113
- const existing = fs16.readFileSync(file.path, "utf-8");
2114
- previewDiff(file.path, existing, file.content);
2115
- } else {
2116
- previewNewFile(file.path, file.content);
2117
- }
2118
- }
2119
2288
 
2120
2289
  // src/commands/undo.ts
2121
2290
  import chalk4 from "chalk";
@@ -2151,7 +2320,7 @@ function undoCommand() {
2151
2320
 
2152
2321
  // src/commands/status.ts
2153
2322
  import chalk5 from "chalk";
2154
- import fs17 from "fs";
2323
+ import fs19 from "fs";
2155
2324
  async function statusCommand(options) {
2156
2325
  const auth2 = getStoredAuth();
2157
2326
  const manifest = readManifest();
@@ -2176,7 +2345,7 @@ async function statusCommand(options) {
2176
2345
  }
2177
2346
  console.log(` Files managed: ${chalk5.cyan(manifest.entries.length.toString())}`);
2178
2347
  for (const entry of manifest.entries) {
2179
- const exists = fs17.existsSync(entry.path);
2348
+ const exists = fs19.existsSync(entry.path);
2180
2349
  const icon = exists ? chalk5.green("\u2713") : chalk5.red("\u2717");
2181
2350
  console.log(` ${icon} ${entry.path} (${entry.action})`);
2182
2351
  }
@@ -2324,7 +2493,7 @@ function detectLocalPlatforms() {
2324
2493
  }
2325
2494
  function getSkillPath(platform, slug) {
2326
2495
  if (platform === "cursor") {
2327
- return join(".cursor", "rules", `${slug}.mdc`);
2496
+ return join(".cursor", "skills", slug, "SKILL.md");
2328
2497
  }
2329
2498
  return join(".claude", "skills", `${slug}.md`);
2330
2499
  }
@@ -2811,13 +2980,13 @@ async function diffCommand(options) {
2811
2980
  }
2812
2981
 
2813
2982
  // src/commands/refresh.ts
2814
- import fs19 from "fs";
2815
- import path16 from "path";
2983
+ import fs21 from "fs";
2984
+ import path18 from "path";
2816
2985
  import chalk11 from "chalk";
2817
2986
  import ora8 from "ora";
2818
2987
 
2819
2988
  // src/lib/git-diff.ts
2820
- import { execSync as execSync3 } from "child_process";
2989
+ import { execSync as execSync4 } from "child_process";
2821
2990
  var MAX_DIFF_BYTES = 1e5;
2822
2991
  var DOC_PATTERNS = [
2823
2992
  "CLAUDE.md",
@@ -2831,7 +3000,7 @@ function excludeArgs() {
2831
3000
  }
2832
3001
  function safeExec(cmd) {
2833
3002
  try {
2834
- return execSync3(cmd, {
3003
+ return execSync4(cmd, {
2835
3004
  encoding: "utf-8",
2836
3005
  stdio: ["pipe", "pipe", "pipe"],
2837
3006
  maxBuffer: 10 * 1024 * 1024
@@ -2889,37 +3058,37 @@ function collectDiff(lastSha) {
2889
3058
  }
2890
3059
 
2891
3060
  // src/writers/refresh.ts
2892
- import fs18 from "fs";
2893
- import path15 from "path";
3061
+ import fs20 from "fs";
3062
+ import path17 from "path";
2894
3063
  function writeRefreshDocs(docs) {
2895
3064
  const written = [];
2896
3065
  if (docs.claudeMd) {
2897
- fs18.writeFileSync("CLAUDE.md", docs.claudeMd);
3066
+ fs20.writeFileSync("CLAUDE.md", docs.claudeMd);
2898
3067
  written.push("CLAUDE.md");
2899
3068
  }
2900
3069
  if (docs.readmeMd) {
2901
- fs18.writeFileSync("README.md", docs.readmeMd);
3070
+ fs20.writeFileSync("README.md", docs.readmeMd);
2902
3071
  written.push("README.md");
2903
3072
  }
2904
3073
  if (docs.cursorrules) {
2905
- fs18.writeFileSync(".cursorrules", docs.cursorrules);
3074
+ fs20.writeFileSync(".cursorrules", docs.cursorrules);
2906
3075
  written.push(".cursorrules");
2907
3076
  }
2908
3077
  if (docs.cursorRules) {
2909
- const rulesDir = path15.join(".cursor", "rules");
2910
- if (!fs18.existsSync(rulesDir)) fs18.mkdirSync(rulesDir, { recursive: true });
3078
+ const rulesDir = path17.join(".cursor", "rules");
3079
+ if (!fs20.existsSync(rulesDir)) fs20.mkdirSync(rulesDir, { recursive: true });
2911
3080
  for (const rule of docs.cursorRules) {
2912
- const filePath = path15.join(rulesDir, rule.filename);
2913
- fs18.writeFileSync(filePath, rule.content);
3081
+ const filePath = path17.join(rulesDir, rule.filename);
3082
+ fs20.writeFileSync(filePath, rule.content);
2914
3083
  written.push(filePath);
2915
3084
  }
2916
3085
  }
2917
3086
  if (docs.claudeSkills) {
2918
- const skillsDir = path15.join(".claude", "skills");
2919
- if (!fs18.existsSync(skillsDir)) fs18.mkdirSync(skillsDir, { recursive: true });
3087
+ const skillsDir = path17.join(".claude", "skills");
3088
+ if (!fs20.existsSync(skillsDir)) fs20.mkdirSync(skillsDir, { recursive: true });
2920
3089
  for (const skill of docs.claudeSkills) {
2921
- const filePath = path15.join(skillsDir, skill.filename);
2922
- fs18.writeFileSync(filePath, skill.content);
3090
+ const filePath = path17.join(skillsDir, skill.filename);
3091
+ fs20.writeFileSync(filePath, skill.content);
2923
3092
  written.push(filePath);
2924
3093
  }
2925
3094
  }
@@ -2933,11 +3102,11 @@ function log(quiet, ...args) {
2933
3102
  function discoverGitRepos(parentDir) {
2934
3103
  const repos = [];
2935
3104
  try {
2936
- const entries = fs19.readdirSync(parentDir, { withFileTypes: true });
3105
+ const entries = fs21.readdirSync(parentDir, { withFileTypes: true });
2937
3106
  for (const entry of entries) {
2938
3107
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
2939
- const childPath = path16.join(parentDir, entry.name);
2940
- if (fs19.existsSync(path16.join(childPath, ".git"))) {
3108
+ const childPath = path18.join(parentDir, entry.name);
3109
+ if (fs21.existsSync(path18.join(childPath, ".git"))) {
2941
3110
  repos.push(childPath);
2942
3111
  }
2943
3112
  }
@@ -3040,7 +3209,7 @@ async function refreshCommand(options) {
3040
3209
  `));
3041
3210
  const originalDir = process.cwd();
3042
3211
  for (const repo of repos) {
3043
- const repoName = path16.basename(repo);
3212
+ const repoName = path18.basename(repo);
3044
3213
  try {
3045
3214
  process.chdir(repo);
3046
3215
  await refreshSingleRepo(repo, { ...options, label: repoName });
@@ -3182,9 +3351,9 @@ async function reviewCommand(message, options) {
3182
3351
  }
3183
3352
 
3184
3353
  // src/cli.ts
3185
- var __dirname2 = path17.dirname(fileURLToPath3(import.meta.url));
3354
+ var __dirname2 = path19.dirname(fileURLToPath3(import.meta.url));
3186
3355
  var pkg3 = JSON.parse(
3187
- fs20.readFileSync(path17.resolve(__dirname2, "..", "package.json"), "utf-8")
3356
+ fs22.readFileSync(path19.resolve(__dirname2, "..", "package.json"), "utf-8")
3188
3357
  );
3189
3358
  var program = new Command();
3190
3359
  var displayVersion = process.env.CALIBER_LOCAL ? `${pkg3.version}-local` : pkg3.version;
@@ -3206,22 +3375,22 @@ hooks.command("remove").description("Remove auto-refresh SessionEnd hook").actio
3206
3375
  hooks.command("status").description("Check if auto-refresh hook is installed").action(hooksStatusCommand);
3207
3376
 
3208
3377
  // src/utils/version-check.ts
3209
- import fs21 from "fs";
3210
- import path18 from "path";
3378
+ import fs23 from "fs";
3379
+ import path20 from "path";
3211
3380
  import { fileURLToPath as fileURLToPath4 } from "url";
3212
- import { execSync as execSync4 } from "child_process";
3381
+ import { execSync as execSync5 } from "child_process";
3213
3382
  import chalk14 from "chalk";
3214
3383
  import ora10 from "ora";
3215
3384
  import confirm3 from "@inquirer/confirm";
3216
- var __dirname_vc = path18.dirname(fileURLToPath4(import.meta.url));
3385
+ var __dirname_vc = path20.dirname(fileURLToPath4(import.meta.url));
3217
3386
  var pkg4 = JSON.parse(
3218
- fs21.readFileSync(path18.resolve(__dirname_vc, "..", "package.json"), "utf-8")
3387
+ fs23.readFileSync(path20.resolve(__dirname_vc, "..", "package.json"), "utf-8")
3219
3388
  );
3220
3389
  function getInstalledVersion() {
3221
3390
  try {
3222
- const globalRoot = execSync4("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
3223
- const pkgPath = path18.join(globalRoot, "@caliber-ai", "cli", "package.json");
3224
- return JSON.parse(fs21.readFileSync(pkgPath, "utf-8")).version;
3391
+ const globalRoot = execSync5("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
3392
+ const pkgPath = path20.join(globalRoot, "@caliber-ai", "cli", "package.json");
3393
+ return JSON.parse(fs23.readFileSync(pkgPath, "utf-8")).version;
3225
3394
  } catch {
3226
3395
  return null;
3227
3396
  }
@@ -3264,7 +3433,7 @@ Update available: ${current} -> ${latest}`)
3264
3433
  }
3265
3434
  const spinner = ora10("Updating @caliber-ai/cli...").start();
3266
3435
  try {
3267
- execSync4(`npm install -g @caliber-ai/cli@${latest} --prefer-online`, { stdio: "pipe", timeout: 6e4 });
3436
+ execSync5(`npm install -g @caliber-ai/cli@${latest} --prefer-online`, { stdio: "pipe", timeout: 6e4 });
3268
3437
  const installed = getInstalledVersion();
3269
3438
  if (installed !== latest) {
3270
3439
  spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
@@ -3277,7 +3446,7 @@ Update available: ${current} -> ${latest}`)
3277
3446
  console.log(chalk14.dim(`
3278
3447
  Restarting: caliber ${args.join(" ")}
3279
3448
  `));
3280
- execSync4(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
3449
+ execSync5(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
3281
3450
  stdio: "inherit",
3282
3451
  env: { ...process.env, CALIBER_SKIP_UPDATE_CHECK: "1" }
3283
3452
  });