@caliber-ai/cli 0.18.0 → 0.20.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 +529 -331
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
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
|
|
79
|
-
import
|
|
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
|
|
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
|
|
931
|
-
import
|
|
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 =
|
|
936
|
-
if (
|
|
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 =
|
|
946
|
-
if (
|
|
947
|
-
for (const file of
|
|
948
|
-
const filePath =
|
|
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 =
|
|
959
|
-
if (
|
|
1147
|
+
const mcpJsonPath = path10.join(dir, ".mcp.json");
|
|
1148
|
+
if (fs9.existsSync(mcpJsonPath)) {
|
|
960
1149
|
try {
|
|
961
|
-
const mcpJson = JSON.parse(
|
|
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 =
|
|
977
|
-
if (
|
|
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 =
|
|
987
|
-
if (
|
|
988
|
-
for (const file of
|
|
989
|
-
const filePath =
|
|
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
|
|
1000
|
-
if (
|
|
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(
|
|
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 =
|
|
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
|
|
1261
|
+
import fs14 from "fs";
|
|
1176
1262
|
|
|
1177
1263
|
// src/writers/claude/index.ts
|
|
1178
|
-
import
|
|
1179
|
-
import
|
|
1264
|
+
import fs10 from "fs";
|
|
1265
|
+
import path11 from "path";
|
|
1180
1266
|
function writeClaudeConfig(config) {
|
|
1181
1267
|
const written = [];
|
|
1182
|
-
|
|
1268
|
+
fs10.writeFileSync("CLAUDE.md", config.claudeMd);
|
|
1183
1269
|
written.push("CLAUDE.md");
|
|
1184
1270
|
if (config.skills?.length) {
|
|
1185
|
-
const skillsDir =
|
|
1186
|
-
if (!
|
|
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 =
|
|
1190
|
-
|
|
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 (
|
|
1198
|
-
const existing = JSON.parse(
|
|
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
|
-
|
|
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
|
|
1212
|
-
import
|
|
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
|
-
|
|
1302
|
+
fs11.writeFileSync(".cursorrules", config.cursorrules);
|
|
1217
1303
|
written.push(".cursorrules");
|
|
1218
1304
|
}
|
|
1219
1305
|
if (config.rules?.length) {
|
|
1220
|
-
const rulesDir =
|
|
1221
|
-
if (!
|
|
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 =
|
|
1224
|
-
|
|
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 (!
|
|
1231
|
-
const mcpPath =
|
|
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 (
|
|
1235
|
-
const existing = JSON.parse(
|
|
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
|
-
|
|
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
|
|
1252
|
-
import
|
|
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 =
|
|
1350
|
+
const backupDir = path13.join(BACKUPS_DIR, timestamp);
|
|
1256
1351
|
for (const file of files) {
|
|
1257
|
-
if (!
|
|
1258
|
-
const dest =
|
|
1259
|
-
const destDir =
|
|
1260
|
-
if (!
|
|
1261
|
-
|
|
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
|
-
|
|
1358
|
+
fs12.copyFileSync(file, dest);
|
|
1264
1359
|
}
|
|
1265
1360
|
return backupDir;
|
|
1266
1361
|
}
|
|
1267
1362
|
function restoreBackup(backupDir, file) {
|
|
1268
|
-
const backupFile =
|
|
1269
|
-
if (!
|
|
1270
|
-
const destDir =
|
|
1271
|
-
if (!
|
|
1272
|
-
|
|
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
|
-
|
|
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
|
|
1375
|
+
import fs13 from "fs";
|
|
1281
1376
|
import crypto4 from "crypto";
|
|
1282
1377
|
function readManifest() {
|
|
1283
1378
|
try {
|
|
1284
|
-
if (!
|
|
1285
|
-
return JSON.parse(
|
|
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 (!
|
|
1292
|
-
|
|
1386
|
+
if (!fs13.existsSync(CALIBER_DIR)) {
|
|
1387
|
+
fs13.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
1293
1388
|
}
|
|
1294
|
-
|
|
1389
|
+
fs13.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
|
|
1295
1390
|
}
|
|
1296
1391
|
function fileChecksum(filePath) {
|
|
1297
|
-
const content =
|
|
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) =>
|
|
1399
|
+
const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs14.existsSync(f));
|
|
1305
1400
|
const existingFiles = [
|
|
1306
|
-
...filesToWrite.filter((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
|
-
|
|
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 (
|
|
1350
|
-
|
|
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 (
|
|
1361
|
-
|
|
1455
|
+
if (fs14.existsSync(MANIFEST_FILE2)) {
|
|
1456
|
+
fs14.unlinkSync(MANIFEST_FILE2);
|
|
1362
1457
|
}
|
|
1363
1458
|
return { restored, removed };
|
|
1364
1459
|
}
|
|
@@ -1378,40 +1473,114 @@ 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 (
|
|
1388
|
-
const content =
|
|
1485
|
+
if (fs14.existsSync(gitignorePath)) {
|
|
1486
|
+
const content = fs14.readFileSync(gitignorePath, "utf-8");
|
|
1389
1487
|
if (!content.includes(".caliber/")) {
|
|
1390
|
-
|
|
1488
|
+
fs14.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
|
|
1391
1489
|
}
|
|
1392
1490
|
} else {
|
|
1393
|
-
|
|
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
|
+
const stagedFiles = [];
|
|
1507
|
+
for (const file of files) {
|
|
1508
|
+
const proposedPath = path14.join(PROPOSED_DIR, file.path);
|
|
1509
|
+
fs15.mkdirSync(path14.dirname(proposedPath), { recursive: true });
|
|
1510
|
+
fs15.writeFileSync(proposedPath, file.content);
|
|
1511
|
+
const originalPath = path14.join(projectDir, file.path);
|
|
1512
|
+
if (fs15.existsSync(originalPath)) {
|
|
1513
|
+
const currentPath = path14.join(CURRENT_DIR, file.path);
|
|
1514
|
+
fs15.mkdirSync(path14.dirname(currentPath), { recursive: true });
|
|
1515
|
+
fs15.copyFileSync(originalPath, currentPath);
|
|
1516
|
+
modifiedFiles++;
|
|
1517
|
+
stagedFiles.push({ relativePath: file.path, proposedPath, currentPath, isNew: false });
|
|
1518
|
+
} else {
|
|
1519
|
+
newFiles++;
|
|
1520
|
+
stagedFiles.push({ relativePath: file.path, proposedPath, isNew: true });
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
return { newFiles, modifiedFiles, stagedFiles };
|
|
1524
|
+
}
|
|
1525
|
+
function cleanupStaging() {
|
|
1526
|
+
if (fs15.existsSync(STAGED_DIR)) {
|
|
1527
|
+
fs15.rmSync(STAGED_DIR, { recursive: true, force: true });
|
|
1394
1528
|
}
|
|
1395
1529
|
}
|
|
1396
1530
|
|
|
1531
|
+
// src/utils/editor.ts
|
|
1532
|
+
import { execSync as execSync2, spawnSync } 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 detectAvailableEditors() {
|
|
1542
|
+
const methods = [];
|
|
1543
|
+
if (commandExists("cursor")) methods.push("cursor");
|
|
1544
|
+
if (commandExists("code")) methods.push("vscode");
|
|
1545
|
+
methods.push("terminal");
|
|
1546
|
+
return methods;
|
|
1547
|
+
}
|
|
1548
|
+
function openDiffsInEditor(editor, files) {
|
|
1549
|
+
const cmd = editor === "cursor" ? "cursor" : "code";
|
|
1550
|
+
for (const file of files) {
|
|
1551
|
+
try {
|
|
1552
|
+
if (file.currentPath) {
|
|
1553
|
+
spawnSync(cmd, ["--diff", file.currentPath, file.proposedPath], { stdio: "ignore" });
|
|
1554
|
+
} else {
|
|
1555
|
+
spawnSync(cmd, [file.proposedPath], { stdio: "ignore" });
|
|
1556
|
+
}
|
|
1557
|
+
} catch {
|
|
1558
|
+
continue;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
// src/commands/init.ts
|
|
1564
|
+
import { createTwoFilesPatch } from "diff";
|
|
1565
|
+
|
|
1397
1566
|
// src/lib/hooks.ts
|
|
1398
|
-
import
|
|
1399
|
-
import
|
|
1400
|
-
var SETTINGS_PATH =
|
|
1567
|
+
import fs16 from "fs";
|
|
1568
|
+
import path15 from "path";
|
|
1569
|
+
var SETTINGS_PATH = path15.join(".claude", "settings.json");
|
|
1401
1570
|
var HOOK_COMMAND = "caliber refresh --quiet";
|
|
1402
1571
|
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
1403
1572
|
function readSettings() {
|
|
1404
|
-
if (!
|
|
1573
|
+
if (!fs16.existsSync(SETTINGS_PATH)) return {};
|
|
1405
1574
|
try {
|
|
1406
|
-
return JSON.parse(
|
|
1575
|
+
return JSON.parse(fs16.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
1407
1576
|
} catch {
|
|
1408
1577
|
return {};
|
|
1409
1578
|
}
|
|
1410
1579
|
}
|
|
1411
1580
|
function writeSettings(settings) {
|
|
1412
|
-
const dir =
|
|
1413
|
-
if (!
|
|
1414
|
-
|
|
1581
|
+
const dir = path15.dirname(SETTINGS_PATH);
|
|
1582
|
+
if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
|
|
1583
|
+
fs16.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
1415
1584
|
}
|
|
1416
1585
|
function findHookIndex(sessionEnd) {
|
|
1417
1586
|
return sessionEnd.findIndex(
|
|
@@ -1461,27 +1630,27 @@ function removeHook() {
|
|
|
1461
1630
|
|
|
1462
1631
|
// src/lib/state.ts
|
|
1463
1632
|
init_constants();
|
|
1464
|
-
import
|
|
1465
|
-
import
|
|
1466
|
-
import { execSync as
|
|
1467
|
-
var STATE_FILE =
|
|
1633
|
+
import fs17 from "fs";
|
|
1634
|
+
import path16 from "path";
|
|
1635
|
+
import { execSync as execSync3 } from "child_process";
|
|
1636
|
+
var STATE_FILE = path16.join(CALIBER_DIR, ".caliber-state.json");
|
|
1468
1637
|
function readState() {
|
|
1469
1638
|
try {
|
|
1470
|
-
if (!
|
|
1471
|
-
return JSON.parse(
|
|
1639
|
+
if (!fs17.existsSync(STATE_FILE)) return null;
|
|
1640
|
+
return JSON.parse(fs17.readFileSync(STATE_FILE, "utf-8"));
|
|
1472
1641
|
} catch {
|
|
1473
1642
|
return null;
|
|
1474
1643
|
}
|
|
1475
1644
|
}
|
|
1476
1645
|
function writeState(state) {
|
|
1477
|
-
if (!
|
|
1478
|
-
|
|
1646
|
+
if (!fs17.existsSync(CALIBER_DIR)) {
|
|
1647
|
+
fs17.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
1479
1648
|
}
|
|
1480
|
-
|
|
1649
|
+
fs17.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
1481
1650
|
}
|
|
1482
1651
|
function getCurrentHeadSha() {
|
|
1483
1652
|
try {
|
|
1484
|
-
return
|
|
1653
|
+
return execSync3("git rev-parse HEAD", {
|
|
1485
1654
|
encoding: "utf-8",
|
|
1486
1655
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1487
1656
|
}).trim();
|
|
@@ -1611,6 +1780,7 @@ async function initCommand(options) {
|
|
|
1611
1780
|
const fingerprint = collectFingerprint(process.cwd());
|
|
1612
1781
|
const hash = computeFingerprintHash(fingerprint);
|
|
1613
1782
|
spinner.succeed("Project analyzed");
|
|
1783
|
+
const enrichmentPromise = enrichFingerprintWithLLM(fingerprint, process.cwd());
|
|
1614
1784
|
trackEvent("scan_completed", {
|
|
1615
1785
|
languages: fingerprint.languages,
|
|
1616
1786
|
frameworks: fingerprint.frameworks,
|
|
@@ -1619,6 +1789,7 @@ async function initCommand(options) {
|
|
|
1619
1789
|
has_claude_settings: !!fingerprint.existingConfigs.claudeSettings,
|
|
1620
1790
|
has_cursorrules: !!fingerprint.existingConfigs.cursorrules,
|
|
1621
1791
|
cursor_rules_count: fingerprint.existingConfigs.cursorRules?.length ?? 0,
|
|
1792
|
+
cursor_skills_count: fingerprint.existingConfigs.cursorSkills?.length ?? 0,
|
|
1622
1793
|
skills_count: fingerprint.existingConfigs.claudeSkills?.length ?? 0,
|
|
1623
1794
|
has_claude_mcp_servers: !!fingerprint.existingConfigs.claudeMcpServers,
|
|
1624
1795
|
has_cursor_mcp_servers: !!fingerprint.existingConfigs.cursorMcpServers,
|
|
@@ -1648,6 +1819,11 @@ async function initCommand(options) {
|
|
|
1648
1819
|
localConfigs.push(`.cursor/rules/${rule.filename}`);
|
|
1649
1820
|
}
|
|
1650
1821
|
}
|
|
1822
|
+
if (Array.isArray(ec.cursorSkills)) {
|
|
1823
|
+
for (const skill of ec.cursorSkills) {
|
|
1824
|
+
localConfigs.push(`.cursor/skills/${skill.slug}/SKILL.md`);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1651
1827
|
const localState = scanLocalState(process.cwd());
|
|
1652
1828
|
const claudeMcpServers = localState.filter((i) => i.type === "mcp" && i.platform === "claude").map((i) => i.name);
|
|
1653
1829
|
const cursorMcpServers = localState.filter((i) => i.type === "mcp" && i.platform === "cursor").map((i) => i.name);
|
|
@@ -1703,6 +1879,7 @@ async function initCommand(options) {
|
|
|
1703
1879
|
if (isEmpty) {
|
|
1704
1880
|
fingerprint.description = await promptInput("What will you build in this project?");
|
|
1705
1881
|
}
|
|
1882
|
+
await enrichmentPromise;
|
|
1706
1883
|
console.log(chalk3.hex("#6366f1").bold(" Step 4/6 \u2014 Auditing your configs\n"));
|
|
1707
1884
|
console.log(chalk3.dim(" AI is auditing your CLAUDE.md, skills, and rules against your"));
|
|
1708
1885
|
console.log(chalk3.dim(" project's actual codebase and conventions.\n"));
|
|
@@ -1762,25 +1939,34 @@ async function initCommand(options) {
|
|
|
1762
1939
|
genSpinner.succeed(`Setup generated ${chalk3.dim(`in ${timeStr}`)}`);
|
|
1763
1940
|
printSetupSummary(generatedSetup);
|
|
1764
1941
|
console.log(chalk3.hex("#6366f1").bold(" Step 5/6 \u2014 Review\n"));
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
trackEvent("setup_declined");
|
|
1774
|
-
console.log(chalk3.dim("Setup declined. No files were modified."));
|
|
1775
|
-
return;
|
|
1776
|
-
}
|
|
1777
|
-
if (action === "refine") {
|
|
1942
|
+
const setupFiles = collectSetupFiles(generatedSetup);
|
|
1943
|
+
const staged = stageFiles(setupFiles, process.cwd());
|
|
1944
|
+
console.log(chalk3.dim(` ${chalk3.green(`${staged.newFiles} new`)} / ${chalk3.yellow(`${staged.modifiedFiles} modified`)} file${staged.newFiles + staged.modifiedFiles !== 1 ? "s" : ""}
|
|
1945
|
+
`));
|
|
1946
|
+
const reviewMethod = await promptReviewMethod();
|
|
1947
|
+
openReview(reviewMethod, staged.stagedFiles);
|
|
1948
|
+
let action = await promptReviewAction();
|
|
1949
|
+
while (action === "refine") {
|
|
1778
1950
|
generatedSetup = await refineLoop(generatedSetup, targetAgent);
|
|
1779
1951
|
if (!generatedSetup) {
|
|
1952
|
+
cleanupStaging();
|
|
1780
1953
|
trackEvent("refinement_cancelled");
|
|
1781
1954
|
console.log(chalk3.dim("Refinement cancelled. No files were modified."));
|
|
1782
1955
|
return;
|
|
1783
1956
|
}
|
|
1957
|
+
const updatedFiles = collectSetupFiles(generatedSetup);
|
|
1958
|
+
const restaged = stageFiles(updatedFiles, process.cwd());
|
|
1959
|
+
console.log(chalk3.dim(` ${chalk3.green(`${restaged.newFiles} new`)} / ${chalk3.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
|
|
1960
|
+
`));
|
|
1961
|
+
openReview(reviewMethod, restaged.stagedFiles);
|
|
1962
|
+
printSetupSummary(generatedSetup);
|
|
1963
|
+
action = await promptReviewAction();
|
|
1964
|
+
}
|
|
1965
|
+
cleanupStaging();
|
|
1966
|
+
if (action === "decline") {
|
|
1967
|
+
trackEvent("setup_declined");
|
|
1968
|
+
console.log(chalk3.dim("Setup declined. No files were modified."));
|
|
1969
|
+
return;
|
|
1784
1970
|
}
|
|
1785
1971
|
console.log(chalk3.hex("#6366f1").bold(" Step 6/6 \u2014 Apply & sync\n"));
|
|
1786
1972
|
console.log(chalk3.dim(" Writing config files to your project and syncing to Caliber so"));
|
|
@@ -1931,13 +2117,57 @@ async function promptAgent() {
|
|
|
1931
2117
|
]
|
|
1932
2118
|
});
|
|
1933
2119
|
}
|
|
1934
|
-
async function
|
|
2120
|
+
async function promptReviewMethod() {
|
|
2121
|
+
const available = detectAvailableEditors();
|
|
2122
|
+
if (available.length === 1) return "terminal";
|
|
2123
|
+
const choices = available.map((method) => {
|
|
2124
|
+
switch (method) {
|
|
2125
|
+
case "cursor":
|
|
2126
|
+
return { name: "Cursor (diff view)", value: "cursor" };
|
|
2127
|
+
case "vscode":
|
|
2128
|
+
return { name: "VS Code (diff view)", value: "vscode" };
|
|
2129
|
+
case "terminal":
|
|
2130
|
+
return { name: "Terminal", value: "terminal" };
|
|
2131
|
+
}
|
|
2132
|
+
});
|
|
2133
|
+
return select({ message: "How would you like to review the changes?", choices });
|
|
2134
|
+
}
|
|
2135
|
+
function openReview(method, stagedFiles) {
|
|
2136
|
+
if (method === "cursor" || method === "vscode") {
|
|
2137
|
+
openDiffsInEditor(method, stagedFiles.map((f) => ({
|
|
2138
|
+
currentPath: f.currentPath,
|
|
2139
|
+
proposedPath: f.proposedPath
|
|
2140
|
+
})));
|
|
2141
|
+
console.log(chalk3.dim(" Diffs opened in your editor.\n"));
|
|
2142
|
+
} else {
|
|
2143
|
+
for (const file of stagedFiles) {
|
|
2144
|
+
console.log(chalk3.bold(` ${file.relativePath}`));
|
|
2145
|
+
if (file.currentPath) {
|
|
2146
|
+
const current = fs18.readFileSync(file.currentPath, "utf-8");
|
|
2147
|
+
const proposed = fs18.readFileSync(file.proposedPath, "utf-8");
|
|
2148
|
+
const patch = createTwoFilesPatch(file.relativePath, file.relativePath, current, proposed, "current", "proposed");
|
|
2149
|
+
for (const line of patch.split("\n").slice(2)) {
|
|
2150
|
+
if (line.startsWith("+")) console.log(chalk3.green(` ${line}`));
|
|
2151
|
+
else if (line.startsWith("-")) console.log(chalk3.red(` ${line}`));
|
|
2152
|
+
else console.log(chalk3.dim(` ${line}`));
|
|
2153
|
+
}
|
|
2154
|
+
} else {
|
|
2155
|
+
console.log(chalk3.green(" (new file)"));
|
|
2156
|
+
const content = fs18.readFileSync(file.proposedPath, "utf-8");
|
|
2157
|
+
const preview = content.split("\n").slice(0, 20);
|
|
2158
|
+
for (const line of preview) console.log(chalk3.green(` +${line}`));
|
|
2159
|
+
if (content.split("\n").length > 20) console.log(chalk3.dim(` ... ${content.split("\n").length - 20} more lines`));
|
|
2160
|
+
}
|
|
2161
|
+
console.log("");
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
async function promptReviewAction() {
|
|
1935
2166
|
return select({
|
|
1936
2167
|
message: "What would you like to do?",
|
|
1937
2168
|
choices: [
|
|
1938
2169
|
{ name: "Accept and apply", value: "accept" },
|
|
1939
2170
|
{ name: "Refine via chat", value: "refine" },
|
|
1940
|
-
{ name: "Preview a file", value: "preview" },
|
|
1941
2171
|
{ name: "Decline", value: "decline" }
|
|
1942
2172
|
]
|
|
1943
2173
|
});
|
|
@@ -1954,7 +2184,7 @@ function printSetupSummary(setup) {
|
|
|
1954
2184
|
};
|
|
1955
2185
|
if (claude) {
|
|
1956
2186
|
if (claude.claudeMd) {
|
|
1957
|
-
const icon =
|
|
2187
|
+
const icon = fs18.existsSync("CLAUDE.md") ? chalk3.yellow("~") : chalk3.green("+");
|
|
1958
2188
|
const desc = getDescription("CLAUDE.md");
|
|
1959
2189
|
console.log(` ${icon} ${chalk3.bold("CLAUDE.md")}`);
|
|
1960
2190
|
if (desc) {
|
|
@@ -1966,7 +2196,7 @@ function printSetupSummary(setup) {
|
|
|
1966
2196
|
if (Array.isArray(skills) && skills.length > 0) {
|
|
1967
2197
|
for (const skill of skills) {
|
|
1968
2198
|
const skillPath = `.claude/skills/${skill.name.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}.md`;
|
|
1969
|
-
const icon =
|
|
2199
|
+
const icon = fs18.existsSync(skillPath) ? chalk3.yellow("~") : chalk3.green("+");
|
|
1970
2200
|
const desc = getDescription(skillPath);
|
|
1971
2201
|
console.log(` ${icon} ${chalk3.bold(skillPath)}`);
|
|
1972
2202
|
console.log(chalk3.dim(` ${desc || summarizeSkill(skill)}`));
|
|
@@ -1975,7 +2205,7 @@ function printSetupSummary(setup) {
|
|
|
1975
2205
|
}
|
|
1976
2206
|
const mcpServers = claude.mcpServers;
|
|
1977
2207
|
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
1978
|
-
const icon =
|
|
2208
|
+
const icon = fs18.existsSync(".mcp.json") ? chalk3.yellow("~") : chalk3.green("+");
|
|
1979
2209
|
const serverNames = Object.keys(mcpServers);
|
|
1980
2210
|
const desc = getDescription(".mcp.json");
|
|
1981
2211
|
console.log(` ${icon} ${chalk3.bold(".mcp.json")}`);
|
|
@@ -1985,17 +2215,33 @@ function printSetupSummary(setup) {
|
|
|
1985
2215
|
}
|
|
1986
2216
|
if (cursor) {
|
|
1987
2217
|
if (cursor.cursorrules) {
|
|
1988
|
-
const icon =
|
|
2218
|
+
const icon = fs18.existsSync(".cursorrules") ? chalk3.yellow("~") : chalk3.green("+");
|
|
1989
2219
|
const desc = getDescription(".cursorrules");
|
|
1990
2220
|
console.log(` ${icon} ${chalk3.bold(".cursorrules")}`);
|
|
1991
2221
|
if (desc) console.log(chalk3.dim(` ${desc}`));
|
|
1992
2222
|
console.log("");
|
|
1993
2223
|
}
|
|
2224
|
+
const cursorSkills = cursor.skills;
|
|
2225
|
+
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
2226
|
+
for (const skill of cursorSkills) {
|
|
2227
|
+
const skillPath = `.cursor/skills/${skill.slug}/SKILL.md`;
|
|
2228
|
+
const icon = fs18.existsSync(skillPath) ? chalk3.yellow("~") : chalk3.green("+");
|
|
2229
|
+
const desc = getDescription(skillPath);
|
|
2230
|
+
console.log(` ${icon} ${chalk3.bold(skillPath)}`);
|
|
2231
|
+
if (desc) {
|
|
2232
|
+
console.log(chalk3.dim(` ${desc}`));
|
|
2233
|
+
} else {
|
|
2234
|
+
const firstLine = skill.content.split("\n").filter((l) => l.trim() && !l.trim().startsWith("#") && !l.trim().startsWith("---"))[0];
|
|
2235
|
+
if (firstLine) console.log(chalk3.dim(` ${firstLine.trim().slice(0, 80)}`));
|
|
2236
|
+
}
|
|
2237
|
+
console.log("");
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
1994
2240
|
const rules = cursor.rules;
|
|
1995
2241
|
if (Array.isArray(rules) && rules.length > 0) {
|
|
1996
2242
|
for (const rule of rules) {
|
|
1997
2243
|
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
1998
|
-
const icon =
|
|
2244
|
+
const icon = fs18.existsSync(rulePath) ? chalk3.yellow("~") : chalk3.green("+");
|
|
1999
2245
|
const desc = getDescription(rulePath);
|
|
2000
2246
|
console.log(` ${icon} ${chalk3.bold(rulePath)}`);
|
|
2001
2247
|
if (desc) {
|
|
@@ -2009,7 +2255,7 @@ function printSetupSummary(setup) {
|
|
|
2009
2255
|
}
|
|
2010
2256
|
const mcpServers = cursor.mcpServers;
|
|
2011
2257
|
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
2012
|
-
const icon =
|
|
2258
|
+
const icon = fs18.existsSync(".cursor/mcp.json") ? chalk3.yellow("~") : chalk3.green("+");
|
|
2013
2259
|
const serverNames = Object.keys(mcpServers);
|
|
2014
2260
|
const desc = getDescription(".cursor/mcp.json");
|
|
2015
2261
|
console.log(` ${icon} ${chalk3.bold(".cursor/mcp.json")}`);
|
|
@@ -2050,6 +2296,12 @@ function collectSetupFiles(setup) {
|
|
|
2050
2296
|
}
|
|
2051
2297
|
if (cursor) {
|
|
2052
2298
|
if (cursor.cursorrules) files.push({ path: ".cursorrules", content: cursor.cursorrules });
|
|
2299
|
+
const cursorSkills = cursor.skills;
|
|
2300
|
+
if (Array.isArray(cursorSkills)) {
|
|
2301
|
+
for (const skill of cursorSkills) {
|
|
2302
|
+
files.push({ path: `.cursor/skills/${skill.slug}/SKILL.md`, content: skill.content });
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2053
2305
|
const rules = cursor.rules;
|
|
2054
2306
|
if (Array.isArray(rules)) {
|
|
2055
2307
|
for (const rule of rules) {
|
|
@@ -2062,60 +2314,6 @@ function collectSetupFiles(setup) {
|
|
|
2062
2314
|
}
|
|
2063
2315
|
return files;
|
|
2064
2316
|
}
|
|
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
2317
|
|
|
2120
2318
|
// src/commands/undo.ts
|
|
2121
2319
|
import chalk4 from "chalk";
|
|
@@ -2151,7 +2349,7 @@ function undoCommand() {
|
|
|
2151
2349
|
|
|
2152
2350
|
// src/commands/status.ts
|
|
2153
2351
|
import chalk5 from "chalk";
|
|
2154
|
-
import
|
|
2352
|
+
import fs19 from "fs";
|
|
2155
2353
|
async function statusCommand(options) {
|
|
2156
2354
|
const auth2 = getStoredAuth();
|
|
2157
2355
|
const manifest = readManifest();
|
|
@@ -2176,7 +2374,7 @@ async function statusCommand(options) {
|
|
|
2176
2374
|
}
|
|
2177
2375
|
console.log(` Files managed: ${chalk5.cyan(manifest.entries.length.toString())}`);
|
|
2178
2376
|
for (const entry of manifest.entries) {
|
|
2179
|
-
const exists =
|
|
2377
|
+
const exists = fs19.existsSync(entry.path);
|
|
2180
2378
|
const icon = exists ? chalk5.green("\u2713") : chalk5.red("\u2717");
|
|
2181
2379
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
2182
2380
|
}
|
|
@@ -2324,7 +2522,7 @@ function detectLocalPlatforms() {
|
|
|
2324
2522
|
}
|
|
2325
2523
|
function getSkillPath(platform, slug) {
|
|
2326
2524
|
if (platform === "cursor") {
|
|
2327
|
-
return join(".cursor", "
|
|
2525
|
+
return join(".cursor", "skills", slug, "SKILL.md");
|
|
2328
2526
|
}
|
|
2329
2527
|
return join(".claude", "skills", `${slug}.md`);
|
|
2330
2528
|
}
|
|
@@ -2811,13 +3009,13 @@ async function diffCommand(options) {
|
|
|
2811
3009
|
}
|
|
2812
3010
|
|
|
2813
3011
|
// src/commands/refresh.ts
|
|
2814
|
-
import
|
|
2815
|
-
import
|
|
3012
|
+
import fs21 from "fs";
|
|
3013
|
+
import path18 from "path";
|
|
2816
3014
|
import chalk11 from "chalk";
|
|
2817
3015
|
import ora8 from "ora";
|
|
2818
3016
|
|
|
2819
3017
|
// src/lib/git-diff.ts
|
|
2820
|
-
import { execSync as
|
|
3018
|
+
import { execSync as execSync4 } from "child_process";
|
|
2821
3019
|
var MAX_DIFF_BYTES = 1e5;
|
|
2822
3020
|
var DOC_PATTERNS = [
|
|
2823
3021
|
"CLAUDE.md",
|
|
@@ -2831,7 +3029,7 @@ function excludeArgs() {
|
|
|
2831
3029
|
}
|
|
2832
3030
|
function safeExec(cmd) {
|
|
2833
3031
|
try {
|
|
2834
|
-
return
|
|
3032
|
+
return execSync4(cmd, {
|
|
2835
3033
|
encoding: "utf-8",
|
|
2836
3034
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2837
3035
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -2889,37 +3087,37 @@ function collectDiff(lastSha) {
|
|
|
2889
3087
|
}
|
|
2890
3088
|
|
|
2891
3089
|
// src/writers/refresh.ts
|
|
2892
|
-
import
|
|
2893
|
-
import
|
|
3090
|
+
import fs20 from "fs";
|
|
3091
|
+
import path17 from "path";
|
|
2894
3092
|
function writeRefreshDocs(docs) {
|
|
2895
3093
|
const written = [];
|
|
2896
3094
|
if (docs.claudeMd) {
|
|
2897
|
-
|
|
3095
|
+
fs20.writeFileSync("CLAUDE.md", docs.claudeMd);
|
|
2898
3096
|
written.push("CLAUDE.md");
|
|
2899
3097
|
}
|
|
2900
3098
|
if (docs.readmeMd) {
|
|
2901
|
-
|
|
3099
|
+
fs20.writeFileSync("README.md", docs.readmeMd);
|
|
2902
3100
|
written.push("README.md");
|
|
2903
3101
|
}
|
|
2904
3102
|
if (docs.cursorrules) {
|
|
2905
|
-
|
|
3103
|
+
fs20.writeFileSync(".cursorrules", docs.cursorrules);
|
|
2906
3104
|
written.push(".cursorrules");
|
|
2907
3105
|
}
|
|
2908
3106
|
if (docs.cursorRules) {
|
|
2909
|
-
const rulesDir =
|
|
2910
|
-
if (!
|
|
3107
|
+
const rulesDir = path17.join(".cursor", "rules");
|
|
3108
|
+
if (!fs20.existsSync(rulesDir)) fs20.mkdirSync(rulesDir, { recursive: true });
|
|
2911
3109
|
for (const rule of docs.cursorRules) {
|
|
2912
|
-
const filePath =
|
|
2913
|
-
|
|
3110
|
+
const filePath = path17.join(rulesDir, rule.filename);
|
|
3111
|
+
fs20.writeFileSync(filePath, rule.content);
|
|
2914
3112
|
written.push(filePath);
|
|
2915
3113
|
}
|
|
2916
3114
|
}
|
|
2917
3115
|
if (docs.claudeSkills) {
|
|
2918
|
-
const skillsDir =
|
|
2919
|
-
if (!
|
|
3116
|
+
const skillsDir = path17.join(".claude", "skills");
|
|
3117
|
+
if (!fs20.existsSync(skillsDir)) fs20.mkdirSync(skillsDir, { recursive: true });
|
|
2920
3118
|
for (const skill of docs.claudeSkills) {
|
|
2921
|
-
const filePath =
|
|
2922
|
-
|
|
3119
|
+
const filePath = path17.join(skillsDir, skill.filename);
|
|
3120
|
+
fs20.writeFileSync(filePath, skill.content);
|
|
2923
3121
|
written.push(filePath);
|
|
2924
3122
|
}
|
|
2925
3123
|
}
|
|
@@ -2933,11 +3131,11 @@ function log(quiet, ...args) {
|
|
|
2933
3131
|
function discoverGitRepos(parentDir) {
|
|
2934
3132
|
const repos = [];
|
|
2935
3133
|
try {
|
|
2936
|
-
const entries =
|
|
3134
|
+
const entries = fs21.readdirSync(parentDir, { withFileTypes: true });
|
|
2937
3135
|
for (const entry of entries) {
|
|
2938
3136
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
2939
|
-
const childPath =
|
|
2940
|
-
if (
|
|
3137
|
+
const childPath = path18.join(parentDir, entry.name);
|
|
3138
|
+
if (fs21.existsSync(path18.join(childPath, ".git"))) {
|
|
2941
3139
|
repos.push(childPath);
|
|
2942
3140
|
}
|
|
2943
3141
|
}
|
|
@@ -3040,7 +3238,7 @@ async function refreshCommand(options) {
|
|
|
3040
3238
|
`));
|
|
3041
3239
|
const originalDir = process.cwd();
|
|
3042
3240
|
for (const repo of repos) {
|
|
3043
|
-
const repoName =
|
|
3241
|
+
const repoName = path18.basename(repo);
|
|
3044
3242
|
try {
|
|
3045
3243
|
process.chdir(repo);
|
|
3046
3244
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -3182,9 +3380,9 @@ async function reviewCommand(message, options) {
|
|
|
3182
3380
|
}
|
|
3183
3381
|
|
|
3184
3382
|
// src/cli.ts
|
|
3185
|
-
var __dirname2 =
|
|
3383
|
+
var __dirname2 = path19.dirname(fileURLToPath3(import.meta.url));
|
|
3186
3384
|
var pkg3 = JSON.parse(
|
|
3187
|
-
|
|
3385
|
+
fs22.readFileSync(path19.resolve(__dirname2, "..", "package.json"), "utf-8")
|
|
3188
3386
|
);
|
|
3189
3387
|
var program = new Command();
|
|
3190
3388
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg3.version}-local` : pkg3.version;
|
|
@@ -3206,22 +3404,22 @@ hooks.command("remove").description("Remove auto-refresh SessionEnd hook").actio
|
|
|
3206
3404
|
hooks.command("status").description("Check if auto-refresh hook is installed").action(hooksStatusCommand);
|
|
3207
3405
|
|
|
3208
3406
|
// src/utils/version-check.ts
|
|
3209
|
-
import
|
|
3210
|
-
import
|
|
3407
|
+
import fs23 from "fs";
|
|
3408
|
+
import path20 from "path";
|
|
3211
3409
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3212
|
-
import { execSync as
|
|
3410
|
+
import { execSync as execSync5 } from "child_process";
|
|
3213
3411
|
import chalk14 from "chalk";
|
|
3214
3412
|
import ora10 from "ora";
|
|
3215
3413
|
import confirm3 from "@inquirer/confirm";
|
|
3216
|
-
var __dirname_vc =
|
|
3414
|
+
var __dirname_vc = path20.dirname(fileURLToPath4(import.meta.url));
|
|
3217
3415
|
var pkg4 = JSON.parse(
|
|
3218
|
-
|
|
3416
|
+
fs23.readFileSync(path20.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
3219
3417
|
);
|
|
3220
3418
|
function getInstalledVersion() {
|
|
3221
3419
|
try {
|
|
3222
|
-
const globalRoot =
|
|
3223
|
-
const pkgPath =
|
|
3224
|
-
return JSON.parse(
|
|
3420
|
+
const globalRoot = execSync5("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3421
|
+
const pkgPath = path20.join(globalRoot, "@caliber-ai", "cli", "package.json");
|
|
3422
|
+
return JSON.parse(fs23.readFileSync(pkgPath, "utf-8")).version;
|
|
3225
3423
|
} catch {
|
|
3226
3424
|
return null;
|
|
3227
3425
|
}
|
|
@@ -3264,7 +3462,7 @@ Update available: ${current} -> ${latest}`)
|
|
|
3264
3462
|
}
|
|
3265
3463
|
const spinner = ora10("Updating @caliber-ai/cli...").start();
|
|
3266
3464
|
try {
|
|
3267
|
-
|
|
3465
|
+
execSync5(`npm install -g @caliber-ai/cli@${latest} --prefer-online`, { stdio: "pipe", timeout: 6e4 });
|
|
3268
3466
|
const installed = getInstalledVersion();
|
|
3269
3467
|
if (installed !== latest) {
|
|
3270
3468
|
spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
|
|
@@ -3277,7 +3475,7 @@ Update available: ${current} -> ${latest}`)
|
|
|
3277
3475
|
console.log(chalk14.dim(`
|
|
3278
3476
|
Restarting: caliber ${args.join(" ")}
|
|
3279
3477
|
`));
|
|
3280
|
-
|
|
3478
|
+
execSync5(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
3281
3479
|
stdio: "inherit",
|
|
3282
3480
|
env: { ...process.env, CALIBER_SKIP_UPDATE_CHECK: "1" }
|
|
3283
3481
|
});
|