@rely-ai/caliber 1.1.0 → 1.1.2
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 +1054 -600
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -32,17 +32,17 @@ __export(constants_exports, {
|
|
|
32
32
|
LEARNING_STATE_FILE: () => LEARNING_STATE_FILE,
|
|
33
33
|
MANIFEST_FILE: () => MANIFEST_FILE
|
|
34
34
|
});
|
|
35
|
-
import
|
|
35
|
+
import path8 from "path";
|
|
36
36
|
import os2 from "os";
|
|
37
37
|
var AUTH_DIR, CALIBER_DIR, MANIFEST_FILE, BACKUPS_DIR, LEARNING_DIR, LEARNING_SESSION_FILE, LEARNING_STATE_FILE, LEARNING_MAX_EVENTS;
|
|
38
38
|
var init_constants = __esm({
|
|
39
39
|
"src/constants.ts"() {
|
|
40
40
|
"use strict";
|
|
41
|
-
AUTH_DIR =
|
|
41
|
+
AUTH_DIR = path8.join(os2.homedir(), ".caliber");
|
|
42
42
|
CALIBER_DIR = ".caliber";
|
|
43
|
-
MANIFEST_FILE =
|
|
44
|
-
BACKUPS_DIR =
|
|
45
|
-
LEARNING_DIR =
|
|
43
|
+
MANIFEST_FILE = path8.join(CALIBER_DIR, "manifest.json");
|
|
44
|
+
BACKUPS_DIR = path8.join(CALIBER_DIR, "backups");
|
|
45
|
+
LEARNING_DIR = path8.join(CALIBER_DIR, "learning");
|
|
46
46
|
LEARNING_SESSION_FILE = "current-session.jsonl";
|
|
47
47
|
LEARNING_STATE_FILE = "state.json";
|
|
48
48
|
LEARNING_MAX_EVENTS = 500;
|
|
@@ -52,19 +52,19 @@ var init_constants = __esm({
|
|
|
52
52
|
// src/cli.ts
|
|
53
53
|
import { Command } from "commander";
|
|
54
54
|
import fs25 from "fs";
|
|
55
|
-
import
|
|
55
|
+
import path21 from "path";
|
|
56
56
|
import { fileURLToPath } from "url";
|
|
57
57
|
|
|
58
58
|
// src/commands/onboard.ts
|
|
59
|
-
import
|
|
60
|
-
import
|
|
61
|
-
import
|
|
59
|
+
import chalk5 from "chalk";
|
|
60
|
+
import ora2 from "ora";
|
|
61
|
+
import readline4 from "readline";
|
|
62
62
|
import select2 from "@inquirer/select";
|
|
63
63
|
import fs18 from "fs";
|
|
64
64
|
|
|
65
65
|
// src/fingerprint/index.ts
|
|
66
|
-
import
|
|
67
|
-
import
|
|
66
|
+
import fs6 from "fs";
|
|
67
|
+
import path5 from "path";
|
|
68
68
|
|
|
69
69
|
// src/fingerprint/git.ts
|
|
70
70
|
import { execSync } from "child_process";
|
|
@@ -91,54 +91,9 @@ function isGitRepo() {
|
|
|
91
91
|
return isInsideGitRepo();
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
// src/fingerprint/
|
|
94
|
+
// src/fingerprint/file-tree.ts
|
|
95
95
|
import fs from "fs";
|
|
96
96
|
import path from "path";
|
|
97
|
-
import { globSync } from "glob";
|
|
98
|
-
var WORKSPACE_GLOBS = [
|
|
99
|
-
"apps/*/package.json",
|
|
100
|
-
"packages/*/package.json",
|
|
101
|
-
"services/*/package.json",
|
|
102
|
-
"libs/*/package.json"
|
|
103
|
-
];
|
|
104
|
-
function analyzePackageJson(dir) {
|
|
105
|
-
const rootPkgPath = path.join(dir, "package.json");
|
|
106
|
-
let name;
|
|
107
|
-
const languages = [];
|
|
108
|
-
if (fs.existsSync(rootPkgPath)) {
|
|
109
|
-
try {
|
|
110
|
-
const pkg3 = JSON.parse(fs.readFileSync(rootPkgPath, "utf-8"));
|
|
111
|
-
name = pkg3.name;
|
|
112
|
-
const allDeps = { ...pkg3.dependencies, ...pkg3.devDependencies };
|
|
113
|
-
if (allDeps.typescript || allDeps["@types/node"]) {
|
|
114
|
-
languages.push("TypeScript");
|
|
115
|
-
}
|
|
116
|
-
languages.push("JavaScript");
|
|
117
|
-
} catch {
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
for (const glob of WORKSPACE_GLOBS) {
|
|
121
|
-
const matches = globSync(glob, { cwd: dir, absolute: true });
|
|
122
|
-
for (const pkgPath of matches) {
|
|
123
|
-
try {
|
|
124
|
-
const pkg3 = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
125
|
-
const deps = { ...pkg3.dependencies, ...pkg3.devDependencies };
|
|
126
|
-
if (deps.typescript || deps["@types/node"]) {
|
|
127
|
-
languages.push("TypeScript");
|
|
128
|
-
}
|
|
129
|
-
} catch {
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return {
|
|
134
|
-
name,
|
|
135
|
-
languages: [...new Set(languages)]
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// src/fingerprint/file-tree.ts
|
|
140
|
-
import fs2 from "fs";
|
|
141
|
-
import path2 from "path";
|
|
142
97
|
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
143
98
|
"node_modules",
|
|
144
99
|
".git",
|
|
@@ -161,10 +116,10 @@ function getFileTree(dir, maxDepth = 3) {
|
|
|
161
116
|
}
|
|
162
117
|
function scan(base, rel, depth, maxDepth, result) {
|
|
163
118
|
if (depth > maxDepth) return;
|
|
164
|
-
const fullPath =
|
|
119
|
+
const fullPath = path.join(base, rel);
|
|
165
120
|
let entries;
|
|
166
121
|
try {
|
|
167
|
-
entries =
|
|
122
|
+
entries = fs.readdirSync(fullPath, { withFileTypes: true });
|
|
168
123
|
} catch {
|
|
169
124
|
return;
|
|
170
125
|
}
|
|
@@ -181,122 +136,87 @@ function scan(base, rel, depth, maxDepth, result) {
|
|
|
181
136
|
}
|
|
182
137
|
}
|
|
183
138
|
|
|
184
|
-
// src/fingerprint/languages.ts
|
|
185
|
-
import path3 from "path";
|
|
186
|
-
var EXT_TO_LANG = {
|
|
187
|
-
".ts": "TypeScript",
|
|
188
|
-
".tsx": "TypeScript",
|
|
189
|
-
".js": "JavaScript",
|
|
190
|
-
".jsx": "JavaScript",
|
|
191
|
-
".py": "Python",
|
|
192
|
-
".go": "Go",
|
|
193
|
-
".rs": "Rust",
|
|
194
|
-
".rb": "Ruby",
|
|
195
|
-
".java": "Java",
|
|
196
|
-
".kt": "Kotlin",
|
|
197
|
-
".swift": "Swift",
|
|
198
|
-
".cs": "C#",
|
|
199
|
-
".cpp": "C++",
|
|
200
|
-
".c": "C",
|
|
201
|
-
".php": "PHP",
|
|
202
|
-
".dart": "Dart",
|
|
203
|
-
".ex": "Elixir",
|
|
204
|
-
".exs": "Elixir",
|
|
205
|
-
".scala": "Scala",
|
|
206
|
-
".zig": "Zig"
|
|
207
|
-
};
|
|
208
|
-
function detectLanguages(fileTree) {
|
|
209
|
-
const langs = /* @__PURE__ */ new Set();
|
|
210
|
-
for (const file of fileTree) {
|
|
211
|
-
const ext = path3.extname(file).toLowerCase();
|
|
212
|
-
if (EXT_TO_LANG[ext]) {
|
|
213
|
-
langs.add(EXT_TO_LANG[ext]);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return [...langs];
|
|
217
|
-
}
|
|
218
|
-
|
|
219
139
|
// src/fingerprint/existing-config.ts
|
|
220
|
-
import
|
|
221
|
-
import
|
|
140
|
+
import fs2 from "fs";
|
|
141
|
+
import path2 from "path";
|
|
222
142
|
function readExistingConfigs(dir) {
|
|
223
143
|
const configs = {};
|
|
224
|
-
const readmeMdPath =
|
|
225
|
-
if (
|
|
226
|
-
configs.readmeMd =
|
|
144
|
+
const readmeMdPath = path2.join(dir, "README.md");
|
|
145
|
+
if (fs2.existsSync(readmeMdPath)) {
|
|
146
|
+
configs.readmeMd = fs2.readFileSync(readmeMdPath, "utf-8");
|
|
227
147
|
}
|
|
228
|
-
const claudeMdPath =
|
|
229
|
-
if (
|
|
230
|
-
configs.claudeMd =
|
|
148
|
+
const claudeMdPath = path2.join(dir, "CLAUDE.md");
|
|
149
|
+
if (fs2.existsSync(claudeMdPath)) {
|
|
150
|
+
configs.claudeMd = fs2.readFileSync(claudeMdPath, "utf-8");
|
|
231
151
|
}
|
|
232
|
-
const claudeSettingsPath =
|
|
233
|
-
if (
|
|
152
|
+
const claudeSettingsPath = path2.join(dir, ".claude", "settings.json");
|
|
153
|
+
if (fs2.existsSync(claudeSettingsPath)) {
|
|
234
154
|
try {
|
|
235
|
-
configs.claudeSettings = JSON.parse(
|
|
155
|
+
configs.claudeSettings = JSON.parse(fs2.readFileSync(claudeSettingsPath, "utf-8"));
|
|
236
156
|
} catch {
|
|
237
157
|
}
|
|
238
158
|
}
|
|
239
|
-
const skillsDir =
|
|
240
|
-
if (
|
|
159
|
+
const skillsDir = path2.join(dir, ".claude", "skills");
|
|
160
|
+
if (fs2.existsSync(skillsDir)) {
|
|
241
161
|
try {
|
|
242
|
-
const entries =
|
|
162
|
+
const entries = fs2.readdirSync(skillsDir);
|
|
243
163
|
const skills = [];
|
|
244
164
|
for (const entry of entries) {
|
|
245
|
-
const entryPath =
|
|
246
|
-
const skillMdPath =
|
|
247
|
-
if (
|
|
248
|
-
skills.push({ filename: `${entry}/SKILL.md`, content:
|
|
165
|
+
const entryPath = path2.join(skillsDir, entry);
|
|
166
|
+
const skillMdPath = path2.join(entryPath, "SKILL.md");
|
|
167
|
+
if (fs2.statSync(entryPath).isDirectory() && fs2.existsSync(skillMdPath)) {
|
|
168
|
+
skills.push({ filename: `${entry}/SKILL.md`, content: fs2.readFileSync(skillMdPath, "utf-8") });
|
|
249
169
|
} else if (entry.endsWith(".md")) {
|
|
250
|
-
skills.push({ filename: entry, content:
|
|
170
|
+
skills.push({ filename: entry, content: fs2.readFileSync(entryPath, "utf-8") });
|
|
251
171
|
}
|
|
252
172
|
}
|
|
253
173
|
if (skills.length > 0) configs.claudeSkills = skills;
|
|
254
174
|
} catch {
|
|
255
175
|
}
|
|
256
176
|
}
|
|
257
|
-
const cursorrulesPath =
|
|
258
|
-
if (
|
|
259
|
-
configs.cursorrules =
|
|
177
|
+
const cursorrulesPath = path2.join(dir, ".cursorrules");
|
|
178
|
+
if (fs2.existsSync(cursorrulesPath)) {
|
|
179
|
+
configs.cursorrules = fs2.readFileSync(cursorrulesPath, "utf-8");
|
|
260
180
|
}
|
|
261
|
-
const cursorRulesDir =
|
|
262
|
-
if (
|
|
181
|
+
const cursorRulesDir = path2.join(dir, ".cursor", "rules");
|
|
182
|
+
if (fs2.existsSync(cursorRulesDir)) {
|
|
263
183
|
try {
|
|
264
|
-
const files =
|
|
184
|
+
const files = fs2.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"));
|
|
265
185
|
configs.cursorRules = files.map((f) => ({
|
|
266
186
|
filename: f,
|
|
267
|
-
content:
|
|
187
|
+
content: fs2.readFileSync(path2.join(cursorRulesDir, f), "utf-8")
|
|
268
188
|
}));
|
|
269
189
|
} catch {
|
|
270
190
|
}
|
|
271
191
|
}
|
|
272
|
-
const cursorSkillsDir =
|
|
273
|
-
if (
|
|
192
|
+
const cursorSkillsDir = path2.join(dir, ".cursor", "skills");
|
|
193
|
+
if (fs2.existsSync(cursorSkillsDir)) {
|
|
274
194
|
try {
|
|
275
|
-
const slugs =
|
|
276
|
-
return
|
|
195
|
+
const slugs = fs2.readdirSync(cursorSkillsDir).filter((f) => {
|
|
196
|
+
return fs2.statSync(path2.join(cursorSkillsDir, f)).isDirectory();
|
|
277
197
|
});
|
|
278
|
-
configs.cursorSkills = slugs.filter((slug) =>
|
|
198
|
+
configs.cursorSkills = slugs.filter((slug) => fs2.existsSync(path2.join(cursorSkillsDir, slug, "SKILL.md"))).map((name) => ({
|
|
279
199
|
name,
|
|
280
200
|
filename: "SKILL.md",
|
|
281
|
-
content:
|
|
201
|
+
content: fs2.readFileSync(path2.join(cursorSkillsDir, name, "SKILL.md"), "utf-8")
|
|
282
202
|
}));
|
|
283
203
|
} catch {
|
|
284
204
|
}
|
|
285
205
|
}
|
|
286
|
-
const mcpJsonPath =
|
|
287
|
-
if (
|
|
206
|
+
const mcpJsonPath = path2.join(dir, ".mcp.json");
|
|
207
|
+
if (fs2.existsSync(mcpJsonPath)) {
|
|
288
208
|
try {
|
|
289
|
-
const mcpJson = JSON.parse(
|
|
209
|
+
const mcpJson = JSON.parse(fs2.readFileSync(mcpJsonPath, "utf-8"));
|
|
290
210
|
if (mcpJson.mcpServers) {
|
|
291
211
|
configs.claudeMcpServers = mcpJson.mcpServers;
|
|
292
212
|
}
|
|
293
213
|
} catch {
|
|
294
214
|
}
|
|
295
215
|
}
|
|
296
|
-
const cursorMcpPath =
|
|
297
|
-
if (
|
|
216
|
+
const cursorMcpPath = path2.join(dir, ".cursor", "mcp.json");
|
|
217
|
+
if (fs2.existsSync(cursorMcpPath)) {
|
|
298
218
|
try {
|
|
299
|
-
const cursorMcpJson = JSON.parse(
|
|
219
|
+
const cursorMcpJson = JSON.parse(fs2.readFileSync(cursorMcpPath, "utf-8"));
|
|
300
220
|
if (cursorMcpJson.mcpServers) {
|
|
301
221
|
configs.cursorMcpServers = cursorMcpJson.mcpServers;
|
|
302
222
|
}
|
|
@@ -307,8 +227,8 @@ function readExistingConfigs(dir) {
|
|
|
307
227
|
}
|
|
308
228
|
|
|
309
229
|
// src/fingerprint/code-analysis.ts
|
|
310
|
-
import
|
|
311
|
-
import
|
|
230
|
+
import fs3 from "fs";
|
|
231
|
+
import path3 from "path";
|
|
312
232
|
var IGNORE_DIRS2 = /* @__PURE__ */ new Set([
|
|
313
233
|
"node_modules",
|
|
314
234
|
".git",
|
|
@@ -379,16 +299,16 @@ function analyzeCode(dir) {
|
|
|
379
299
|
let truncated = false;
|
|
380
300
|
const fileSummaries = [];
|
|
381
301
|
for (const relPath of sourceFiles) {
|
|
382
|
-
const fullPath =
|
|
302
|
+
const fullPath = path3.join(dir, relPath);
|
|
383
303
|
let content;
|
|
384
304
|
try {
|
|
385
|
-
content =
|
|
305
|
+
content = fs3.readFileSync(fullPath, "utf-8");
|
|
386
306
|
} catch {
|
|
387
307
|
continue;
|
|
388
308
|
}
|
|
389
309
|
const lineCount = content.split("\n").length;
|
|
390
310
|
if (lineCount > 500) continue;
|
|
391
|
-
const ext =
|
|
311
|
+
const ext = path3.extname(relPath);
|
|
392
312
|
const language = resolveLanguage(ext);
|
|
393
313
|
if (!language) continue;
|
|
394
314
|
const summary = language === "py" ? extractPython(relPath, content) : extractTypeScriptJavaScript(relPath, content, language);
|
|
@@ -404,10 +324,10 @@ function analyzeCode(dir) {
|
|
|
404
324
|
}
|
|
405
325
|
function walkDir(base, rel, depth, maxDepth, sourceFiles, configFiles, rootDir) {
|
|
406
326
|
if (depth > maxDepth) return;
|
|
407
|
-
const fullPath =
|
|
327
|
+
const fullPath = path3.join(base, rel);
|
|
408
328
|
let entries;
|
|
409
329
|
try {
|
|
410
|
-
entries =
|
|
330
|
+
entries = fs3.readdirSync(fullPath, { withFileTypes: true });
|
|
411
331
|
} catch {
|
|
412
332
|
return;
|
|
413
333
|
}
|
|
@@ -426,12 +346,12 @@ function walkDir(base, rel, depth, maxDepth, sourceFiles, configFiles, rootDir)
|
|
|
426
346
|
} else {
|
|
427
347
|
if (CONFIG_FILE_NAMES.has(entry.name)) {
|
|
428
348
|
try {
|
|
429
|
-
const content =
|
|
349
|
+
const content = fs3.readFileSync(path3.join(base, relPath), "utf-8");
|
|
430
350
|
configFiles.push({ path: relPath, content });
|
|
431
351
|
} catch {
|
|
432
352
|
}
|
|
433
353
|
}
|
|
434
|
-
const ext =
|
|
354
|
+
const ext = path3.extname(entry.name);
|
|
435
355
|
if (SOURCE_EXTENSIONS.has(ext) && !entry.name.endsWith(".d.ts")) {
|
|
436
356
|
sourceFiles.push(relPath);
|
|
437
357
|
}
|
|
@@ -439,10 +359,10 @@ function walkDir(base, rel, depth, maxDepth, sourceFiles, configFiles, rootDir)
|
|
|
439
359
|
}
|
|
440
360
|
}
|
|
441
361
|
function collectConfigsFromDir(base, relDir, pattern, configFiles) {
|
|
442
|
-
const fullDir =
|
|
362
|
+
const fullDir = path3.join(base, relDir);
|
|
443
363
|
let entries;
|
|
444
364
|
try {
|
|
445
|
-
entries =
|
|
365
|
+
entries = fs3.readdirSync(fullDir, { withFileTypes: true });
|
|
446
366
|
} catch {
|
|
447
367
|
return;
|
|
448
368
|
}
|
|
@@ -450,7 +370,7 @@ function collectConfigsFromDir(base, relDir, pattern, configFiles) {
|
|
|
450
370
|
if (entry.isFile() && pattern.test(entry.name)) {
|
|
451
371
|
const relPath = `${relDir}/${entry.name}`;
|
|
452
372
|
try {
|
|
453
|
-
const content =
|
|
373
|
+
const content = fs3.readFileSync(path3.join(base, relPath), "utf-8");
|
|
454
374
|
configFiles.push({ path: relPath, content });
|
|
455
375
|
} catch {
|
|
456
376
|
}
|
|
@@ -464,7 +384,7 @@ function sortByPriority(files) {
|
|
|
464
384
|
const servicePattern = /(service|lib|utils)/i;
|
|
465
385
|
const testPattern = /(test|spec|__tests__)/i;
|
|
466
386
|
function priority(filePath) {
|
|
467
|
-
const base =
|
|
387
|
+
const base = path3.basename(filePath);
|
|
468
388
|
if (entryPointNames.has(base)) return 0;
|
|
469
389
|
if (routePattern.test(filePath)) return 1;
|
|
470
390
|
if (schemaPattern.test(filePath)) return 2;
|
|
@@ -583,11 +503,11 @@ function estimateSummarySize(summary) {
|
|
|
583
503
|
}
|
|
584
504
|
|
|
585
505
|
// src/llm/config.ts
|
|
586
|
-
import
|
|
587
|
-
import
|
|
506
|
+
import fs4 from "fs";
|
|
507
|
+
import path4 from "path";
|
|
588
508
|
import os from "os";
|
|
589
|
-
var CONFIG_DIR =
|
|
590
|
-
var CONFIG_FILE =
|
|
509
|
+
var CONFIG_DIR = path4.join(os.homedir(), ".caliber");
|
|
510
|
+
var CONFIG_FILE = path4.join(CONFIG_DIR, "config.json");
|
|
591
511
|
var DEFAULT_MODELS = {
|
|
592
512
|
anthropic: "claude-sonnet-4-6",
|
|
593
513
|
vertex: "claude-sonnet-4-6",
|
|
@@ -641,8 +561,8 @@ function resolveFromEnv() {
|
|
|
641
561
|
}
|
|
642
562
|
function readConfigFile() {
|
|
643
563
|
try {
|
|
644
|
-
if (!
|
|
645
|
-
const raw =
|
|
564
|
+
if (!fs4.existsSync(CONFIG_FILE)) return null;
|
|
565
|
+
const raw = fs4.readFileSync(CONFIG_FILE, "utf-8");
|
|
646
566
|
const parsed = JSON.parse(raw);
|
|
647
567
|
if (!parsed.provider || !["anthropic", "vertex", "openai", "cursor", "claude-cli"].includes(parsed.provider)) {
|
|
648
568
|
return null;
|
|
@@ -653,18 +573,21 @@ function readConfigFile() {
|
|
|
653
573
|
}
|
|
654
574
|
}
|
|
655
575
|
function writeConfigFile(config) {
|
|
656
|
-
if (!
|
|
657
|
-
|
|
576
|
+
if (!fs4.existsSync(CONFIG_DIR)) {
|
|
577
|
+
fs4.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
658
578
|
}
|
|
659
579
|
const sanitized = { ...config };
|
|
660
580
|
if (sanitized.apiKey) {
|
|
661
581
|
sanitized.apiKey = sanitized.apiKey.trim();
|
|
662
582
|
}
|
|
663
|
-
|
|
583
|
+
fs4.writeFileSync(CONFIG_FILE, JSON.stringify(sanitized, null, 2) + "\n", { mode: 384 });
|
|
664
584
|
}
|
|
665
585
|
function getConfigFilePath() {
|
|
666
586
|
return CONFIG_FILE;
|
|
667
587
|
}
|
|
588
|
+
function getFastModel() {
|
|
589
|
+
return process.env.CALIBER_FAST_MODEL || process.env.ANTHROPIC_SMALL_FAST_MODEL || void 0;
|
|
590
|
+
}
|
|
668
591
|
|
|
669
592
|
// src/llm/anthropic.ts
|
|
670
593
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -710,7 +633,7 @@ var AnthropicProvider = class {
|
|
|
710
633
|
};
|
|
711
634
|
|
|
712
635
|
// src/llm/vertex.ts
|
|
713
|
-
import
|
|
636
|
+
import fs5 from "fs";
|
|
714
637
|
import { AnthropicVertex } from "@anthropic-ai/vertex-sdk";
|
|
715
638
|
import { GoogleAuth } from "google-auth-library";
|
|
716
639
|
var VertexProvider = class {
|
|
@@ -733,7 +656,7 @@ var VertexProvider = class {
|
|
|
733
656
|
}
|
|
734
657
|
} else {
|
|
735
658
|
try {
|
|
736
|
-
creds = JSON.parse(
|
|
659
|
+
creds = JSON.parse(fs5.readFileSync(raw, "utf-8"));
|
|
737
660
|
} catch {
|
|
738
661
|
throw new Error(`Cannot read credentials file: ${raw}`);
|
|
739
662
|
}
|
|
@@ -1470,22 +1393,26 @@ All markdown content inside string values must be properly escaped for JSON (new
|
|
|
1470
1393
|
|
|
1471
1394
|
If there's nothing worth learning from the events (routine successful operations), return:
|
|
1472
1395
|
{"claudeMdLearnedSection": null, "skills": null, "explanations": ["No actionable patterns found in these events."]}`;
|
|
1473
|
-
var FINGERPRINT_SYSTEM_PROMPT = `You are an expert at detecting programming languages and
|
|
1396
|
+
var FINGERPRINT_SYSTEM_PROMPT = `You are an expert at detecting programming languages, frameworks, and external tools/services from project file trees and dependency files.
|
|
1474
1397
|
|
|
1475
1398
|
Analyze the provided file tree and dependency file contents. Return a JSON object with:
|
|
1476
|
-
- "languages": array of programming languages used (e.g. "TypeScript", "Python", "Go", "Rust")
|
|
1477
|
-
- "frameworks": array of frameworks and key libraries detected (e.g. "FastAPI", "React", "Celery", "Django", "Express", "Next.js")
|
|
1399
|
+
- "languages": array of programming languages used (e.g. "TypeScript", "Python", "Go", "Rust", "HCL")
|
|
1400
|
+
- "frameworks": array of frameworks and key libraries detected (e.g. "FastAPI", "React", "Celery", "Django", "Express", "Next.js", "Terraform")
|
|
1401
|
+
- "tools": array of external tools, services, and platforms the project integrates with \u2014 things that could have an MCP server or API integration (e.g. "PostgreSQL", "Redis", "Stripe", "Sentry", "AWS", "GCP", "GitHub", "Slack", "Docker", "Kubernetes", "Datadog", "PagerDuty", "MongoDB", "Elasticsearch")
|
|
1478
1402
|
|
|
1479
1403
|
Be thorough \u2014 look for signals in:
|
|
1480
1404
|
- Dependency files (package.json, pyproject.toml, requirements.txt, go.mod, Cargo.toml, etc.)
|
|
1481
1405
|
- File extensions and directory structure
|
|
1482
|
-
- Configuration files (e.g. next.config.js implies Next.js)
|
|
1406
|
+
- Configuration files (e.g. next.config.js implies Next.js, .tf files imply Terraform + cloud providers)
|
|
1407
|
+
- Infrastructure-as-code files (Terraform, CloudFormation, Pulumi, Dockerfiles, k8s manifests)
|
|
1408
|
+
- CI/CD configs (.github/workflows, .gitlab-ci.yml, Jenkinsfile)
|
|
1409
|
+
- Environment variable patterns and service references in code
|
|
1483
1410
|
|
|
1484
|
-
Only include
|
|
1411
|
+
Only include items you're confident about. Return ONLY the JSON object.`;
|
|
1485
1412
|
|
|
1486
1413
|
// src/ai/detect.ts
|
|
1487
|
-
async function
|
|
1488
|
-
const parts = ["Analyze this project and detect languages and
|
|
1414
|
+
async function detectProjectStack(fileTree, fileContents) {
|
|
1415
|
+
const parts = ["Analyze this project and detect languages, frameworks, and external tools/services.\n"];
|
|
1489
1416
|
if (fileTree.length > 0) {
|
|
1490
1417
|
parts.push("File tree:");
|
|
1491
1418
|
parts.push(fileTree.join("\n"));
|
|
@@ -1498,7 +1425,7 @@ async function detectFrameworks(fileTree, fileContents) {
|
|
|
1498
1425
|
parts.push(content);
|
|
1499
1426
|
}
|
|
1500
1427
|
}
|
|
1501
|
-
const fastModel =
|
|
1428
|
+
const fastModel = getFastModel();
|
|
1502
1429
|
const result = await llmJsonCall({
|
|
1503
1430
|
system: FINGERPRINT_SYSTEM_PROMPT,
|
|
1504
1431
|
prompt: parts.join("\n"),
|
|
@@ -1506,29 +1433,39 @@ async function detectFrameworks(fileTree, fileContents) {
|
|
|
1506
1433
|
});
|
|
1507
1434
|
return {
|
|
1508
1435
|
languages: Array.isArray(result.languages) ? result.languages : [],
|
|
1509
|
-
frameworks: Array.isArray(result.frameworks) ? result.frameworks : []
|
|
1436
|
+
frameworks: Array.isArray(result.frameworks) ? result.frameworks : [],
|
|
1437
|
+
tools: Array.isArray(result.tools) ? result.tools : []
|
|
1510
1438
|
};
|
|
1511
1439
|
}
|
|
1512
1440
|
|
|
1513
1441
|
// src/fingerprint/index.ts
|
|
1514
1442
|
function collectFingerprint(dir) {
|
|
1515
1443
|
const gitRemoteUrl = getGitRemoteUrl();
|
|
1516
|
-
const pkgInfo = analyzePackageJson(dir);
|
|
1517
1444
|
const fileTree = getFileTree(dir);
|
|
1518
|
-
const fileLangs = detectLanguages(fileTree);
|
|
1519
1445
|
const existingConfigs = readExistingConfigs(dir);
|
|
1520
1446
|
const codeAnalysis = analyzeCode(dir);
|
|
1521
|
-
const
|
|
1447
|
+
const packageName = readPackageName(dir);
|
|
1522
1448
|
return {
|
|
1523
1449
|
gitRemoteUrl,
|
|
1524
|
-
packageName
|
|
1525
|
-
languages,
|
|
1450
|
+
packageName,
|
|
1451
|
+
languages: [],
|
|
1526
1452
|
frameworks: [],
|
|
1453
|
+
tools: [],
|
|
1527
1454
|
fileTree,
|
|
1528
1455
|
existingConfigs,
|
|
1529
1456
|
codeAnalysis
|
|
1530
1457
|
};
|
|
1531
1458
|
}
|
|
1459
|
+
function readPackageName(dir) {
|
|
1460
|
+
try {
|
|
1461
|
+
const pkgPath = path5.join(dir, "package.json");
|
|
1462
|
+
if (!fs6.existsSync(pkgPath)) return void 0;
|
|
1463
|
+
const pkg3 = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
|
|
1464
|
+
return pkg3.name;
|
|
1465
|
+
} catch {
|
|
1466
|
+
return void 0;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1532
1469
|
var DEP_FILE_PATTERNS = [
|
|
1533
1470
|
"package.json",
|
|
1534
1471
|
"pyproject.toml",
|
|
@@ -1550,12 +1487,12 @@ async function enrichFingerprintWithLLM(fingerprint, dir) {
|
|
|
1550
1487
|
const fileContents = {};
|
|
1551
1488
|
let totalSize = 0;
|
|
1552
1489
|
for (const treePath of fingerprint.fileTree) {
|
|
1553
|
-
const basename =
|
|
1490
|
+
const basename = path5.basename(treePath);
|
|
1554
1491
|
if (!DEP_FILE_PATTERNS.includes(basename)) continue;
|
|
1555
|
-
const fullPath =
|
|
1556
|
-
if (!
|
|
1492
|
+
const fullPath = path5.join(dir, treePath);
|
|
1493
|
+
if (!fs6.existsSync(fullPath)) continue;
|
|
1557
1494
|
try {
|
|
1558
|
-
const content =
|
|
1495
|
+
const content = fs6.readFileSync(fullPath, "utf-8");
|
|
1559
1496
|
if (totalSize + content.length > MAX_CONTENT_SIZE) break;
|
|
1560
1497
|
fileContents[treePath] = content;
|
|
1561
1498
|
totalSize += content.length;
|
|
@@ -1564,7 +1501,7 @@ async function enrichFingerprintWithLLM(fingerprint, dir) {
|
|
|
1564
1501
|
}
|
|
1565
1502
|
}
|
|
1566
1503
|
if (Object.keys(fileContents).length === 0 && fingerprint.fileTree.length === 0) return;
|
|
1567
|
-
const result = await
|
|
1504
|
+
const result = await detectProjectStack(fingerprint.fileTree, fileContents);
|
|
1568
1505
|
if (result.languages?.length) {
|
|
1569
1506
|
const langSet = new Set(fingerprint.languages);
|
|
1570
1507
|
for (const lang of result.languages) langSet.add(lang);
|
|
@@ -1575,6 +1512,11 @@ async function enrichFingerprintWithLLM(fingerprint, dir) {
|
|
|
1575
1512
|
for (const fw of result.frameworks) fwSet.add(fw);
|
|
1576
1513
|
fingerprint.frameworks = [...fwSet];
|
|
1577
1514
|
}
|
|
1515
|
+
if (result.tools?.length) {
|
|
1516
|
+
const toolSet = new Set(fingerprint.tools);
|
|
1517
|
+
for (const tool of result.tools) toolSet.add(tool);
|
|
1518
|
+
fingerprint.tools = [...toolSet];
|
|
1519
|
+
}
|
|
1578
1520
|
} catch {
|
|
1579
1521
|
}
|
|
1580
1522
|
}
|
|
@@ -1875,20 +1817,20 @@ Return the complete updated AgentSetup JSON incorporating the user's changes. Re
|
|
|
1875
1817
|
}
|
|
1876
1818
|
|
|
1877
1819
|
// src/writers/index.ts
|
|
1878
|
-
import
|
|
1820
|
+
import fs11 from "fs";
|
|
1879
1821
|
|
|
1880
1822
|
// src/writers/claude/index.ts
|
|
1881
|
-
import
|
|
1882
|
-
import
|
|
1823
|
+
import fs7 from "fs";
|
|
1824
|
+
import path6 from "path";
|
|
1883
1825
|
function writeClaudeConfig(config) {
|
|
1884
1826
|
const written = [];
|
|
1885
|
-
|
|
1827
|
+
fs7.writeFileSync("CLAUDE.md", config.claudeMd);
|
|
1886
1828
|
written.push("CLAUDE.md");
|
|
1887
1829
|
if (config.skills?.length) {
|
|
1888
1830
|
for (const skill of config.skills) {
|
|
1889
|
-
const skillDir =
|
|
1890
|
-
if (!
|
|
1891
|
-
const skillPath =
|
|
1831
|
+
const skillDir = path6.join(".claude", "skills", skill.name);
|
|
1832
|
+
if (!fs7.existsSync(skillDir)) fs7.mkdirSync(skillDir, { recursive: true });
|
|
1833
|
+
const skillPath = path6.join(skillDir, "SKILL.md");
|
|
1892
1834
|
const frontmatter = [
|
|
1893
1835
|
"---",
|
|
1894
1836
|
`name: ${skill.name}`,
|
|
@@ -1896,49 +1838,49 @@ function writeClaudeConfig(config) {
|
|
|
1896
1838
|
"---",
|
|
1897
1839
|
""
|
|
1898
1840
|
].join("\n");
|
|
1899
|
-
|
|
1841
|
+
fs7.writeFileSync(skillPath, frontmatter + skill.content);
|
|
1900
1842
|
written.push(skillPath);
|
|
1901
1843
|
}
|
|
1902
1844
|
}
|
|
1903
1845
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
1904
1846
|
let existingServers = {};
|
|
1905
1847
|
try {
|
|
1906
|
-
if (
|
|
1907
|
-
const existing = JSON.parse(
|
|
1848
|
+
if (fs7.existsSync(".mcp.json")) {
|
|
1849
|
+
const existing = JSON.parse(fs7.readFileSync(".mcp.json", "utf-8"));
|
|
1908
1850
|
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
1909
1851
|
}
|
|
1910
1852
|
} catch {
|
|
1911
1853
|
}
|
|
1912
1854
|
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
1913
|
-
|
|
1855
|
+
fs7.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
1914
1856
|
written.push(".mcp.json");
|
|
1915
1857
|
}
|
|
1916
1858
|
return written;
|
|
1917
1859
|
}
|
|
1918
1860
|
|
|
1919
1861
|
// src/writers/cursor/index.ts
|
|
1920
|
-
import
|
|
1921
|
-
import
|
|
1862
|
+
import fs8 from "fs";
|
|
1863
|
+
import path7 from "path";
|
|
1922
1864
|
function writeCursorConfig(config) {
|
|
1923
1865
|
const written = [];
|
|
1924
1866
|
if (config.cursorrules) {
|
|
1925
|
-
|
|
1867
|
+
fs8.writeFileSync(".cursorrules", config.cursorrules);
|
|
1926
1868
|
written.push(".cursorrules");
|
|
1927
1869
|
}
|
|
1928
1870
|
if (config.rules?.length) {
|
|
1929
|
-
const rulesDir =
|
|
1930
|
-
if (!
|
|
1871
|
+
const rulesDir = path7.join(".cursor", "rules");
|
|
1872
|
+
if (!fs8.existsSync(rulesDir)) fs8.mkdirSync(rulesDir, { recursive: true });
|
|
1931
1873
|
for (const rule of config.rules) {
|
|
1932
|
-
const rulePath =
|
|
1933
|
-
|
|
1874
|
+
const rulePath = path7.join(rulesDir, rule.filename);
|
|
1875
|
+
fs8.writeFileSync(rulePath, rule.content);
|
|
1934
1876
|
written.push(rulePath);
|
|
1935
1877
|
}
|
|
1936
1878
|
}
|
|
1937
1879
|
if (config.skills?.length) {
|
|
1938
1880
|
for (const skill of config.skills) {
|
|
1939
|
-
const skillDir =
|
|
1940
|
-
if (!
|
|
1941
|
-
const skillPath =
|
|
1881
|
+
const skillDir = path7.join(".cursor", "skills", skill.name);
|
|
1882
|
+
if (!fs8.existsSync(skillDir)) fs8.mkdirSync(skillDir, { recursive: true });
|
|
1883
|
+
const skillPath = path7.join(skillDir, "SKILL.md");
|
|
1942
1884
|
const frontmatter = [
|
|
1943
1885
|
"---",
|
|
1944
1886
|
`name: ${skill.name}`,
|
|
@@ -1946,24 +1888,24 @@ function writeCursorConfig(config) {
|
|
|
1946
1888
|
"---",
|
|
1947
1889
|
""
|
|
1948
1890
|
].join("\n");
|
|
1949
|
-
|
|
1891
|
+
fs8.writeFileSync(skillPath, frontmatter + skill.content);
|
|
1950
1892
|
written.push(skillPath);
|
|
1951
1893
|
}
|
|
1952
1894
|
}
|
|
1953
1895
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
1954
1896
|
const cursorDir = ".cursor";
|
|
1955
|
-
if (!
|
|
1956
|
-
const mcpPath =
|
|
1897
|
+
if (!fs8.existsSync(cursorDir)) fs8.mkdirSync(cursorDir, { recursive: true });
|
|
1898
|
+
const mcpPath = path7.join(cursorDir, "mcp.json");
|
|
1957
1899
|
let existingServers = {};
|
|
1958
1900
|
try {
|
|
1959
|
-
if (
|
|
1960
|
-
const existing = JSON.parse(
|
|
1901
|
+
if (fs8.existsSync(mcpPath)) {
|
|
1902
|
+
const existing = JSON.parse(fs8.readFileSync(mcpPath, "utf-8"));
|
|
1961
1903
|
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
1962
1904
|
}
|
|
1963
1905
|
} catch {
|
|
1964
1906
|
}
|
|
1965
1907
|
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
1966
|
-
|
|
1908
|
+
fs8.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
1967
1909
|
written.push(mcpPath);
|
|
1968
1910
|
}
|
|
1969
1911
|
return written;
|
|
@@ -1971,62 +1913,62 @@ function writeCursorConfig(config) {
|
|
|
1971
1913
|
|
|
1972
1914
|
// src/writers/backup.ts
|
|
1973
1915
|
init_constants();
|
|
1974
|
-
import
|
|
1975
|
-
import
|
|
1916
|
+
import fs9 from "fs";
|
|
1917
|
+
import path9 from "path";
|
|
1976
1918
|
function createBackup(files) {
|
|
1977
1919
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1978
|
-
const backupDir =
|
|
1920
|
+
const backupDir = path9.join(BACKUPS_DIR, timestamp);
|
|
1979
1921
|
for (const file of files) {
|
|
1980
|
-
if (!
|
|
1981
|
-
const dest =
|
|
1982
|
-
const destDir =
|
|
1983
|
-
if (!
|
|
1984
|
-
|
|
1922
|
+
if (!fs9.existsSync(file)) continue;
|
|
1923
|
+
const dest = path9.join(backupDir, file);
|
|
1924
|
+
const destDir = path9.dirname(dest);
|
|
1925
|
+
if (!fs9.existsSync(destDir)) {
|
|
1926
|
+
fs9.mkdirSync(destDir, { recursive: true });
|
|
1985
1927
|
}
|
|
1986
|
-
|
|
1928
|
+
fs9.copyFileSync(file, dest);
|
|
1987
1929
|
}
|
|
1988
1930
|
return backupDir;
|
|
1989
1931
|
}
|
|
1990
1932
|
function restoreBackup(backupDir, file) {
|
|
1991
|
-
const backupFile =
|
|
1992
|
-
if (!
|
|
1993
|
-
const destDir =
|
|
1994
|
-
if (!
|
|
1995
|
-
|
|
1933
|
+
const backupFile = path9.join(backupDir, file);
|
|
1934
|
+
if (!fs9.existsSync(backupFile)) return false;
|
|
1935
|
+
const destDir = path9.dirname(file);
|
|
1936
|
+
if (!fs9.existsSync(destDir)) {
|
|
1937
|
+
fs9.mkdirSync(destDir, { recursive: true });
|
|
1996
1938
|
}
|
|
1997
|
-
|
|
1939
|
+
fs9.copyFileSync(backupFile, file);
|
|
1998
1940
|
return true;
|
|
1999
1941
|
}
|
|
2000
1942
|
|
|
2001
1943
|
// src/writers/manifest.ts
|
|
2002
1944
|
init_constants();
|
|
2003
|
-
import
|
|
1945
|
+
import fs10 from "fs";
|
|
2004
1946
|
import crypto from "crypto";
|
|
2005
1947
|
function readManifest() {
|
|
2006
1948
|
try {
|
|
2007
|
-
if (!
|
|
2008
|
-
return JSON.parse(
|
|
1949
|
+
if (!fs10.existsSync(MANIFEST_FILE)) return null;
|
|
1950
|
+
return JSON.parse(fs10.readFileSync(MANIFEST_FILE, "utf-8"));
|
|
2009
1951
|
} catch {
|
|
2010
1952
|
return null;
|
|
2011
1953
|
}
|
|
2012
1954
|
}
|
|
2013
1955
|
function writeManifest(manifest) {
|
|
2014
|
-
if (!
|
|
2015
|
-
|
|
1956
|
+
if (!fs10.existsSync(CALIBER_DIR)) {
|
|
1957
|
+
fs10.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
2016
1958
|
}
|
|
2017
|
-
|
|
1959
|
+
fs10.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
|
|
2018
1960
|
}
|
|
2019
1961
|
function fileChecksum(filePath) {
|
|
2020
|
-
const content =
|
|
1962
|
+
const content = fs10.readFileSync(filePath);
|
|
2021
1963
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
2022
1964
|
}
|
|
2023
1965
|
|
|
2024
1966
|
// src/writers/index.ts
|
|
2025
1967
|
function writeSetup(setup) {
|
|
2026
1968
|
const filesToWrite = getFilesToWrite(setup);
|
|
2027
|
-
const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) =>
|
|
1969
|
+
const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs11.existsSync(f));
|
|
2028
1970
|
const existingFiles = [
|
|
2029
|
-
...filesToWrite.filter((f) =>
|
|
1971
|
+
...filesToWrite.filter((f) => fs11.existsSync(f)),
|
|
2030
1972
|
...filesToDelete
|
|
2031
1973
|
];
|
|
2032
1974
|
const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
|
|
@@ -2039,7 +1981,7 @@ function writeSetup(setup) {
|
|
|
2039
1981
|
}
|
|
2040
1982
|
const deleted = [];
|
|
2041
1983
|
for (const filePath of filesToDelete) {
|
|
2042
|
-
|
|
1984
|
+
fs11.unlinkSync(filePath);
|
|
2043
1985
|
deleted.push(filePath);
|
|
2044
1986
|
}
|
|
2045
1987
|
ensureGitignore();
|
|
@@ -2069,8 +2011,8 @@ function undoSetup() {
|
|
|
2069
2011
|
const removed = [];
|
|
2070
2012
|
for (const entry of manifest.entries) {
|
|
2071
2013
|
if (entry.action === "created") {
|
|
2072
|
-
if (
|
|
2073
|
-
|
|
2014
|
+
if (fs11.existsSync(entry.path)) {
|
|
2015
|
+
fs11.unlinkSync(entry.path);
|
|
2074
2016
|
removed.push(entry.path);
|
|
2075
2017
|
}
|
|
2076
2018
|
} else if ((entry.action === "modified" || entry.action === "deleted") && manifest.backupDir) {
|
|
@@ -2080,8 +2022,8 @@ function undoSetup() {
|
|
|
2080
2022
|
}
|
|
2081
2023
|
}
|
|
2082
2024
|
const { MANIFEST_FILE: MANIFEST_FILE2 } = (init_constants(), __toCommonJS(constants_exports));
|
|
2083
|
-
if (
|
|
2084
|
-
|
|
2025
|
+
if (fs11.existsSync(MANIFEST_FILE2)) {
|
|
2026
|
+
fs11.unlinkSync(MANIFEST_FILE2);
|
|
2085
2027
|
}
|
|
2086
2028
|
return { restored, removed };
|
|
2087
2029
|
}
|
|
@@ -2110,23 +2052,23 @@ function getFilesToWrite(setup) {
|
|
|
2110
2052
|
}
|
|
2111
2053
|
function ensureGitignore() {
|
|
2112
2054
|
const gitignorePath = ".gitignore";
|
|
2113
|
-
if (
|
|
2114
|
-
const content =
|
|
2055
|
+
if (fs11.existsSync(gitignorePath)) {
|
|
2056
|
+
const content = fs11.readFileSync(gitignorePath, "utf-8");
|
|
2115
2057
|
if (!content.includes(".caliber/")) {
|
|
2116
|
-
|
|
2058
|
+
fs11.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
|
|
2117
2059
|
}
|
|
2118
2060
|
} else {
|
|
2119
|
-
|
|
2061
|
+
fs11.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
|
|
2120
2062
|
}
|
|
2121
2063
|
}
|
|
2122
2064
|
|
|
2123
2065
|
// src/writers/staging.ts
|
|
2124
2066
|
init_constants();
|
|
2125
|
-
import
|
|
2126
|
-
import
|
|
2127
|
-
var STAGED_DIR =
|
|
2128
|
-
var PROPOSED_DIR =
|
|
2129
|
-
var CURRENT_DIR =
|
|
2067
|
+
import fs12 from "fs";
|
|
2068
|
+
import path10 from "path";
|
|
2069
|
+
var STAGED_DIR = path10.join(CALIBER_DIR, "staged");
|
|
2070
|
+
var PROPOSED_DIR = path10.join(STAGED_DIR, "proposed");
|
|
2071
|
+
var CURRENT_DIR = path10.join(STAGED_DIR, "current");
|
|
2130
2072
|
function normalizeContent(content) {
|
|
2131
2073
|
return content.split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
2132
2074
|
}
|
|
@@ -2136,20 +2078,20 @@ function stageFiles(files, projectDir) {
|
|
|
2136
2078
|
let modifiedFiles = 0;
|
|
2137
2079
|
const stagedFiles = [];
|
|
2138
2080
|
for (const file of files) {
|
|
2139
|
-
const originalPath =
|
|
2140
|
-
if (
|
|
2141
|
-
const existing =
|
|
2081
|
+
const originalPath = path10.join(projectDir, file.path);
|
|
2082
|
+
if (fs12.existsSync(originalPath)) {
|
|
2083
|
+
const existing = fs12.readFileSync(originalPath, "utf-8");
|
|
2142
2084
|
if (normalizeContent(existing) === normalizeContent(file.content)) {
|
|
2143
2085
|
continue;
|
|
2144
2086
|
}
|
|
2145
2087
|
}
|
|
2146
|
-
const proposedPath =
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
if (
|
|
2150
|
-
const currentPath =
|
|
2151
|
-
|
|
2152
|
-
|
|
2088
|
+
const proposedPath = path10.join(PROPOSED_DIR, file.path);
|
|
2089
|
+
fs12.mkdirSync(path10.dirname(proposedPath), { recursive: true });
|
|
2090
|
+
fs12.writeFileSync(proposedPath, file.content);
|
|
2091
|
+
if (fs12.existsSync(originalPath)) {
|
|
2092
|
+
const currentPath = path10.join(CURRENT_DIR, file.path);
|
|
2093
|
+
fs12.mkdirSync(path10.dirname(currentPath), { recursive: true });
|
|
2094
|
+
fs12.copyFileSync(originalPath, currentPath);
|
|
2153
2095
|
modifiedFiles++;
|
|
2154
2096
|
stagedFiles.push({ relativePath: file.path, proposedPath, currentPath, originalPath, isNew: false });
|
|
2155
2097
|
} else {
|
|
@@ -2160,8 +2102,8 @@ function stageFiles(files, projectDir) {
|
|
|
2160
2102
|
return { newFiles, modifiedFiles, stagedFiles };
|
|
2161
2103
|
}
|
|
2162
2104
|
function cleanupStaging() {
|
|
2163
|
-
if (
|
|
2164
|
-
|
|
2105
|
+
if (fs12.existsSync(STAGED_DIR)) {
|
|
2106
|
+
fs12.rmSync(STAGED_DIR, { recursive: true, force: true });
|
|
2165
2107
|
}
|
|
2166
2108
|
}
|
|
2167
2109
|
|
|
@@ -2207,24 +2149,24 @@ function openDiffsInEditor(editor, files) {
|
|
|
2207
2149
|
import { createTwoFilesPatch } from "diff";
|
|
2208
2150
|
|
|
2209
2151
|
// src/lib/hooks.ts
|
|
2210
|
-
import
|
|
2211
|
-
import
|
|
2152
|
+
import fs13 from "fs";
|
|
2153
|
+
import path11 from "path";
|
|
2212
2154
|
import { execSync as execSync5 } from "child_process";
|
|
2213
|
-
var SETTINGS_PATH =
|
|
2155
|
+
var SETTINGS_PATH = path11.join(".claude", "settings.json");
|
|
2214
2156
|
var HOOK_COMMAND = "caliber refresh --quiet";
|
|
2215
2157
|
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
2216
2158
|
function readSettings() {
|
|
2217
|
-
if (!
|
|
2159
|
+
if (!fs13.existsSync(SETTINGS_PATH)) return {};
|
|
2218
2160
|
try {
|
|
2219
|
-
return JSON.parse(
|
|
2161
|
+
return JSON.parse(fs13.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
2220
2162
|
} catch {
|
|
2221
2163
|
return {};
|
|
2222
2164
|
}
|
|
2223
2165
|
}
|
|
2224
2166
|
function writeSettings(settings) {
|
|
2225
|
-
const dir =
|
|
2226
|
-
if (!
|
|
2227
|
-
|
|
2167
|
+
const dir = path11.dirname(SETTINGS_PATH);
|
|
2168
|
+
if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
|
|
2169
|
+
fs13.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
2228
2170
|
}
|
|
2229
2171
|
function findHookIndex(sessionEnd) {
|
|
2230
2172
|
return sessionEnd.findIndex(
|
|
@@ -2282,19 +2224,19 @@ ${PRECOMMIT_END}`;
|
|
|
2282
2224
|
function getGitHooksDir() {
|
|
2283
2225
|
try {
|
|
2284
2226
|
const gitDir = execSync5("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
2285
|
-
return
|
|
2227
|
+
return path11.join(gitDir, "hooks");
|
|
2286
2228
|
} catch {
|
|
2287
2229
|
return null;
|
|
2288
2230
|
}
|
|
2289
2231
|
}
|
|
2290
2232
|
function getPreCommitPath() {
|
|
2291
2233
|
const hooksDir = getGitHooksDir();
|
|
2292
|
-
return hooksDir ?
|
|
2234
|
+
return hooksDir ? path11.join(hooksDir, "pre-commit") : null;
|
|
2293
2235
|
}
|
|
2294
2236
|
function isPreCommitHookInstalled() {
|
|
2295
2237
|
const hookPath = getPreCommitPath();
|
|
2296
|
-
if (!hookPath || !
|
|
2297
|
-
const content =
|
|
2238
|
+
if (!hookPath || !fs13.existsSync(hookPath)) return false;
|
|
2239
|
+
const content = fs13.readFileSync(hookPath, "utf-8");
|
|
2298
2240
|
return content.includes(PRECOMMIT_START);
|
|
2299
2241
|
}
|
|
2300
2242
|
function installPreCommitHook() {
|
|
@@ -2303,43 +2245,43 @@ function installPreCommitHook() {
|
|
|
2303
2245
|
}
|
|
2304
2246
|
const hookPath = getPreCommitPath();
|
|
2305
2247
|
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
2306
|
-
const hooksDir =
|
|
2307
|
-
if (!
|
|
2248
|
+
const hooksDir = path11.dirname(hookPath);
|
|
2249
|
+
if (!fs13.existsSync(hooksDir)) fs13.mkdirSync(hooksDir, { recursive: true });
|
|
2308
2250
|
let content = "";
|
|
2309
|
-
if (
|
|
2310
|
-
content =
|
|
2251
|
+
if (fs13.existsSync(hookPath)) {
|
|
2252
|
+
content = fs13.readFileSync(hookPath, "utf-8");
|
|
2311
2253
|
if (!content.endsWith("\n")) content += "\n";
|
|
2312
2254
|
content += "\n" + PRECOMMIT_BLOCK + "\n";
|
|
2313
2255
|
} else {
|
|
2314
2256
|
content = "#!/bin/sh\n\n" + PRECOMMIT_BLOCK + "\n";
|
|
2315
2257
|
}
|
|
2316
|
-
|
|
2317
|
-
|
|
2258
|
+
fs13.writeFileSync(hookPath, content);
|
|
2259
|
+
fs13.chmodSync(hookPath, 493);
|
|
2318
2260
|
return { installed: true, alreadyInstalled: false };
|
|
2319
2261
|
}
|
|
2320
2262
|
function removePreCommitHook() {
|
|
2321
2263
|
const hookPath = getPreCommitPath();
|
|
2322
|
-
if (!hookPath || !
|
|
2264
|
+
if (!hookPath || !fs13.existsSync(hookPath)) {
|
|
2323
2265
|
return { removed: false, notFound: true };
|
|
2324
2266
|
}
|
|
2325
|
-
let content =
|
|
2267
|
+
let content = fs13.readFileSync(hookPath, "utf-8");
|
|
2326
2268
|
if (!content.includes(PRECOMMIT_START)) {
|
|
2327
2269
|
return { removed: false, notFound: true };
|
|
2328
2270
|
}
|
|
2329
2271
|
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
2330
2272
|
content = content.replace(regex, "\n");
|
|
2331
2273
|
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
2332
|
-
|
|
2274
|
+
fs13.unlinkSync(hookPath);
|
|
2333
2275
|
} else {
|
|
2334
|
-
|
|
2276
|
+
fs13.writeFileSync(hookPath, content);
|
|
2335
2277
|
}
|
|
2336
2278
|
return { removed: true, notFound: false };
|
|
2337
2279
|
}
|
|
2338
2280
|
|
|
2339
2281
|
// src/lib/learning-hooks.ts
|
|
2340
|
-
import
|
|
2341
|
-
import
|
|
2342
|
-
var SETTINGS_PATH2 =
|
|
2282
|
+
import fs14 from "fs";
|
|
2283
|
+
import path12 from "path";
|
|
2284
|
+
var SETTINGS_PATH2 = path12.join(".claude", "settings.json");
|
|
2343
2285
|
var HOOK_CONFIGS = [
|
|
2344
2286
|
{
|
|
2345
2287
|
event: "PostToolUse",
|
|
@@ -2358,17 +2300,17 @@ var HOOK_CONFIGS = [
|
|
|
2358
2300
|
}
|
|
2359
2301
|
];
|
|
2360
2302
|
function readSettings2() {
|
|
2361
|
-
if (!
|
|
2303
|
+
if (!fs14.existsSync(SETTINGS_PATH2)) return {};
|
|
2362
2304
|
try {
|
|
2363
|
-
return JSON.parse(
|
|
2305
|
+
return JSON.parse(fs14.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
2364
2306
|
} catch {
|
|
2365
2307
|
return {};
|
|
2366
2308
|
}
|
|
2367
2309
|
}
|
|
2368
2310
|
function writeSettings2(settings) {
|
|
2369
|
-
const dir =
|
|
2370
|
-
if (!
|
|
2371
|
-
|
|
2311
|
+
const dir = path12.dirname(SETTINGS_PATH2);
|
|
2312
|
+
if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
|
|
2313
|
+
fs14.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
2372
2314
|
}
|
|
2373
2315
|
function hasLearningHook(matchers, command) {
|
|
2374
2316
|
return matchers.some((entry) => entry.hooks?.some((h) => h.command === command));
|
|
@@ -2425,23 +2367,23 @@ function removeLearningHooks() {
|
|
|
2425
2367
|
|
|
2426
2368
|
// src/lib/state.ts
|
|
2427
2369
|
init_constants();
|
|
2428
|
-
import
|
|
2429
|
-
import
|
|
2370
|
+
import fs15 from "fs";
|
|
2371
|
+
import path13 from "path";
|
|
2430
2372
|
import { execSync as execSync6 } from "child_process";
|
|
2431
|
-
var STATE_FILE =
|
|
2373
|
+
var STATE_FILE = path13.join(CALIBER_DIR, ".caliber-state.json");
|
|
2432
2374
|
function readState() {
|
|
2433
2375
|
try {
|
|
2434
|
-
if (!
|
|
2435
|
-
return JSON.parse(
|
|
2376
|
+
if (!fs15.existsSync(STATE_FILE)) return null;
|
|
2377
|
+
return JSON.parse(fs15.readFileSync(STATE_FILE, "utf-8"));
|
|
2436
2378
|
} catch {
|
|
2437
2379
|
return null;
|
|
2438
2380
|
}
|
|
2439
2381
|
}
|
|
2440
2382
|
function writeState(state) {
|
|
2441
|
-
if (!
|
|
2442
|
-
|
|
2383
|
+
if (!fs15.existsSync(CALIBER_DIR)) {
|
|
2384
|
+
fs15.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
2443
2385
|
}
|
|
2444
|
-
|
|
2386
|
+
fs15.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
2445
2387
|
}
|
|
2446
2388
|
function getCurrentHeadSha() {
|
|
2447
2389
|
try {
|
|
@@ -2724,15 +2666,15 @@ function computeGrade(score) {
|
|
|
2724
2666
|
// src/scoring/checks/coverage.ts
|
|
2725
2667
|
import { readFileSync, readdirSync } from "fs";
|
|
2726
2668
|
import { join } from "path";
|
|
2727
|
-
function readFileOrNull(
|
|
2669
|
+
function readFileOrNull(path23) {
|
|
2728
2670
|
try {
|
|
2729
|
-
return readFileSync(
|
|
2671
|
+
return readFileSync(path23, "utf-8");
|
|
2730
2672
|
} catch {
|
|
2731
2673
|
return null;
|
|
2732
2674
|
}
|
|
2733
2675
|
}
|
|
2734
|
-
function readJsonOrNull(
|
|
2735
|
-
const content = readFileOrNull(
|
|
2676
|
+
function readJsonOrNull(path23) {
|
|
2677
|
+
const content = readFileOrNull(path23);
|
|
2736
2678
|
if (!content) return null;
|
|
2737
2679
|
try {
|
|
2738
2680
|
return JSON.parse(content);
|
|
@@ -3088,9 +3030,9 @@ function checkExistence(dir) {
|
|
|
3088
3030
|
// src/scoring/checks/quality.ts
|
|
3089
3031
|
import { readFileSync as readFileSync3 } from "fs";
|
|
3090
3032
|
import { join as join3 } from "path";
|
|
3091
|
-
function readFileOrNull2(
|
|
3033
|
+
function readFileOrNull2(path23) {
|
|
3092
3034
|
try {
|
|
3093
|
-
return readFileSync3(
|
|
3035
|
+
return readFileSync3(path23, "utf-8");
|
|
3094
3036
|
} catch {
|
|
3095
3037
|
return null;
|
|
3096
3038
|
}
|
|
@@ -3241,15 +3183,15 @@ function checkQuality(dir) {
|
|
|
3241
3183
|
// src/scoring/checks/accuracy.ts
|
|
3242
3184
|
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync } from "fs";
|
|
3243
3185
|
import { join as join4 } from "path";
|
|
3244
|
-
function readFileOrNull3(
|
|
3186
|
+
function readFileOrNull3(path23) {
|
|
3245
3187
|
try {
|
|
3246
|
-
return readFileSync4(
|
|
3188
|
+
return readFileSync4(path23, "utf-8");
|
|
3247
3189
|
} catch {
|
|
3248
3190
|
return null;
|
|
3249
3191
|
}
|
|
3250
3192
|
}
|
|
3251
|
-
function readJsonOrNull2(
|
|
3252
|
-
const content = readFileOrNull3(
|
|
3193
|
+
function readJsonOrNull2(path23) {
|
|
3194
|
+
const content = readFileOrNull3(path23);
|
|
3253
3195
|
if (!content) return null;
|
|
3254
3196
|
try {
|
|
3255
3197
|
return JSON.parse(content);
|
|
@@ -3432,9 +3374,9 @@ function checkAccuracy(dir) {
|
|
|
3432
3374
|
// src/scoring/checks/freshness.ts
|
|
3433
3375
|
import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
3434
3376
|
import { join as join5 } from "path";
|
|
3435
|
-
function readFileOrNull4(
|
|
3377
|
+
function readFileOrNull4(path23) {
|
|
3436
3378
|
try {
|
|
3437
|
-
return readFileSync5(
|
|
3379
|
+
return readFileSync5(path23, "utf-8");
|
|
3438
3380
|
} catch {
|
|
3439
3381
|
return null;
|
|
3440
3382
|
}
|
|
@@ -3544,9 +3486,9 @@ function checkFreshness(dir) {
|
|
|
3544
3486
|
import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "fs";
|
|
3545
3487
|
import { execSync as execSync7 } from "child_process";
|
|
3546
3488
|
import { join as join6 } from "path";
|
|
3547
|
-
function readFileOrNull5(
|
|
3489
|
+
function readFileOrNull5(path23) {
|
|
3548
3490
|
try {
|
|
3549
|
-
return readFileSync6(
|
|
3491
|
+
return readFileSync6(path23, "utf-8");
|
|
3550
3492
|
} catch {
|
|
3551
3493
|
return null;
|
|
3552
3494
|
}
|
|
@@ -3641,22 +3583,22 @@ function checkBonus(dir) {
|
|
|
3641
3583
|
|
|
3642
3584
|
// src/scoring/dismissed.ts
|
|
3643
3585
|
init_constants();
|
|
3644
|
-
import
|
|
3645
|
-
import
|
|
3646
|
-
var DISMISSED_FILE =
|
|
3586
|
+
import fs16 from "fs";
|
|
3587
|
+
import path14 from "path";
|
|
3588
|
+
var DISMISSED_FILE = path14.join(CALIBER_DIR, "dismissed-checks.json");
|
|
3647
3589
|
function readDismissedChecks() {
|
|
3648
3590
|
try {
|
|
3649
|
-
if (!
|
|
3650
|
-
return JSON.parse(
|
|
3591
|
+
if (!fs16.existsSync(DISMISSED_FILE)) return [];
|
|
3592
|
+
return JSON.parse(fs16.readFileSync(DISMISSED_FILE, "utf-8"));
|
|
3651
3593
|
} catch {
|
|
3652
3594
|
return [];
|
|
3653
3595
|
}
|
|
3654
3596
|
}
|
|
3655
3597
|
function writeDismissedChecks(checks) {
|
|
3656
|
-
if (!
|
|
3657
|
-
|
|
3598
|
+
if (!fs16.existsSync(CALIBER_DIR)) {
|
|
3599
|
+
fs16.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
3658
3600
|
}
|
|
3659
|
-
|
|
3601
|
+
fs16.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
|
|
3660
3602
|
}
|
|
3661
3603
|
function getDismissedIds() {
|
|
3662
3604
|
return new Set(readDismissedChecks().map((c) => c.id));
|
|
@@ -3853,10 +3795,502 @@ function displayScoreDelta(before, after) {
|
|
|
3853
3795
|
}
|
|
3854
3796
|
}
|
|
3855
3797
|
|
|
3798
|
+
// src/mcp/index.ts
|
|
3799
|
+
import chalk4 from "chalk";
|
|
3800
|
+
import ora from "ora";
|
|
3801
|
+
import readline3 from "readline";
|
|
3802
|
+
import fs17 from "fs";
|
|
3803
|
+
import path15 from "path";
|
|
3804
|
+
|
|
3805
|
+
// src/mcp/search.ts
|
|
3806
|
+
var AWESOME_MCP_URL = "https://raw.githubusercontent.com/punkpeye/awesome-mcp-servers/main/README.md";
|
|
3807
|
+
var GITHUB_SEARCH_URL = "https://github.com/search";
|
|
3808
|
+
var SEARCH_HEADERS = {
|
|
3809
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
3810
|
+
"Accept": "text/html"
|
|
3811
|
+
};
|
|
3812
|
+
function parseGitHubSearchHtml(html) {
|
|
3813
|
+
try {
|
|
3814
|
+
const scriptMatch = html.match(/<script type="application\/json" data-target="react-app\.embeddedData">([\s\S]*?)<\/script>/);
|
|
3815
|
+
if (!scriptMatch) {
|
|
3816
|
+
return [];
|
|
3817
|
+
}
|
|
3818
|
+
const data = JSON.parse(scriptMatch[1]);
|
|
3819
|
+
const results = data?.payload?.results;
|
|
3820
|
+
if (!Array.isArray(results)) {
|
|
3821
|
+
return [];
|
|
3822
|
+
}
|
|
3823
|
+
return results.map((r) => {
|
|
3824
|
+
const repo = r.repo?.repository;
|
|
3825
|
+
const ownerLogin = repo?.owner_login || "";
|
|
3826
|
+
const name = repo?.name || "";
|
|
3827
|
+
const description = typeof r.hl_trunc_description === "string" ? r.hl_trunc_description.replace(/<\/?em>/g, "") : null;
|
|
3828
|
+
return {
|
|
3829
|
+
full_name: `${ownerLogin}/${name}`,
|
|
3830
|
+
description,
|
|
3831
|
+
stargazers_count: typeof r.followers === "number" ? r.followers : 0,
|
|
3832
|
+
pushed_at: repo?.updated_at || "",
|
|
3833
|
+
owner_login: ownerLogin
|
|
3834
|
+
};
|
|
3835
|
+
});
|
|
3836
|
+
} catch {
|
|
3837
|
+
return [];
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
async function searchAllMcpSources(toolDeps) {
|
|
3841
|
+
if (toolDeps.length === 0) return [];
|
|
3842
|
+
const searches = [
|
|
3843
|
+
searchAwesomeMcpLists(toolDeps),
|
|
3844
|
+
...toolDeps.map((dep) => searchGitHub(dep)),
|
|
3845
|
+
...toolDeps.map((dep) => searchVendorOrg(dep))
|
|
3846
|
+
];
|
|
3847
|
+
const results = await Promise.all(searches);
|
|
3848
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3849
|
+
for (const batch of results) {
|
|
3850
|
+
for (const candidate of batch) {
|
|
3851
|
+
const key = candidate.repoFullName.toLowerCase();
|
|
3852
|
+
const existing = seen.get(key);
|
|
3853
|
+
if (!existing || candidate.vendor || candidate.stars > existing.stars) {
|
|
3854
|
+
seen.set(key, candidate);
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
return Array.from(seen.values());
|
|
3859
|
+
}
|
|
3860
|
+
async function searchAwesomeMcpLists(toolDeps) {
|
|
3861
|
+
try {
|
|
3862
|
+
const resp = await fetch(AWESOME_MCP_URL, {
|
|
3863
|
+
signal: AbortSignal.timeout(1e4)
|
|
3864
|
+
});
|
|
3865
|
+
if (!resp.ok) return [];
|
|
3866
|
+
const markdown = await resp.text();
|
|
3867
|
+
const candidates = [];
|
|
3868
|
+
const depLower = toolDeps.map((d) => d.toLowerCase().replace(/^@[^/]+\//, ""));
|
|
3869
|
+
const itemPattern = /^[-*]\s+\[([^\]]+)\]\(([^)]+)\)\s*[-–—:]\s*(.*)/gm;
|
|
3870
|
+
let match;
|
|
3871
|
+
while ((match = itemPattern.exec(markdown)) !== null) {
|
|
3872
|
+
const [, name, url, description] = match;
|
|
3873
|
+
if (!url.includes("github.com")) continue;
|
|
3874
|
+
const text = `${name} ${description}`.toLowerCase();
|
|
3875
|
+
const matchedDep = depLower.find((d) => text.includes(d));
|
|
3876
|
+
if (!matchedDep) continue;
|
|
3877
|
+
const repoMatch = url.match(/github\.com\/([^/]+\/[^/]+)/);
|
|
3878
|
+
if (!repoMatch) continue;
|
|
3879
|
+
candidates.push({
|
|
3880
|
+
name: name.trim(),
|
|
3881
|
+
repoFullName: repoMatch[1],
|
|
3882
|
+
url: url.trim(),
|
|
3883
|
+
description: description.trim().slice(0, 200),
|
|
3884
|
+
stars: 0,
|
|
3885
|
+
lastPush: "",
|
|
3886
|
+
vendor: false,
|
|
3887
|
+
score: 0,
|
|
3888
|
+
reason: "",
|
|
3889
|
+
matchedDep: toolDeps.find((d) => d.toLowerCase().replace(/^@[^/]+\//, "") === matchedDep) || matchedDep
|
|
3890
|
+
});
|
|
3891
|
+
}
|
|
3892
|
+
return candidates;
|
|
3893
|
+
} catch {
|
|
3894
|
+
return [];
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
async function searchGitHub(dep) {
|
|
3898
|
+
const depName = dep.replace(/^@[^/]+\//, "");
|
|
3899
|
+
try {
|
|
3900
|
+
const query = encodeURIComponent(`${depName} mcp server`);
|
|
3901
|
+
const url = `${GITHUB_SEARCH_URL}?q=${query}&type=repositories&s=stars&o=desc`;
|
|
3902
|
+
const resp = await fetch(url, {
|
|
3903
|
+
signal: AbortSignal.timeout(1e4),
|
|
3904
|
+
headers: SEARCH_HEADERS
|
|
3905
|
+
});
|
|
3906
|
+
if (!resp.ok) return [];
|
|
3907
|
+
const html = await resp.text();
|
|
3908
|
+
const repos = parseGitHubSearchHtml(html).slice(0, 5);
|
|
3909
|
+
return repos.map((repo) => ({
|
|
3910
|
+
name: repo.full_name.split("/")[1],
|
|
3911
|
+
repoFullName: repo.full_name,
|
|
3912
|
+
url: `https://github.com/${repo.full_name}`,
|
|
3913
|
+
description: repo.description || "",
|
|
3914
|
+
stars: repo.stargazers_count,
|
|
3915
|
+
lastPush: repo.pushed_at,
|
|
3916
|
+
vendor: false,
|
|
3917
|
+
score: 0,
|
|
3918
|
+
reason: "",
|
|
3919
|
+
matchedDep: dep
|
|
3920
|
+
}));
|
|
3921
|
+
} catch {
|
|
3922
|
+
return [];
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
async function searchVendorOrg(dep) {
|
|
3926
|
+
const depName = dep.replace(/^@([^/]+)\/.*/, "$1").replace(/^@/, "");
|
|
3927
|
+
const orgName = depName.toLowerCase();
|
|
3928
|
+
try {
|
|
3929
|
+
const query = encodeURIComponent(`mcp org:${orgName}`);
|
|
3930
|
+
const url = `${GITHUB_SEARCH_URL}?q=${query}&type=repositories&s=stars&o=desc`;
|
|
3931
|
+
const resp = await fetch(url, {
|
|
3932
|
+
signal: AbortSignal.timeout(1e4),
|
|
3933
|
+
headers: SEARCH_HEADERS
|
|
3934
|
+
});
|
|
3935
|
+
if (!resp.ok) return [];
|
|
3936
|
+
const html = await resp.text();
|
|
3937
|
+
const repos = parseGitHubSearchHtml(html).slice(0, 5);
|
|
3938
|
+
return repos.map((repo) => ({
|
|
3939
|
+
name: repo.full_name.split("/")[1],
|
|
3940
|
+
repoFullName: repo.full_name,
|
|
3941
|
+
url: `https://github.com/${repo.full_name}`,
|
|
3942
|
+
description: repo.description || "",
|
|
3943
|
+
stars: repo.stargazers_count,
|
|
3944
|
+
lastPush: repo.pushed_at,
|
|
3945
|
+
vendor: repo.owner_login.toLowerCase() === orgName,
|
|
3946
|
+
score: 0,
|
|
3947
|
+
reason: "",
|
|
3948
|
+
matchedDep: dep
|
|
3949
|
+
}));
|
|
3950
|
+
} catch {
|
|
3951
|
+
return [];
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
|
|
3955
|
+
// src/mcp/prompts.ts
|
|
3956
|
+
var SCORE_MCP_PROMPT = `You evaluate MCP (Model Context Protocol) server candidates for relevance to a software project.
|
|
3957
|
+
|
|
3958
|
+
Score each candidate from 0-100 based on:
|
|
3959
|
+
- **Relevance** (40%): How directly does it match the project's detected tool dependencies?
|
|
3960
|
+
- **Capabilities** (30%): Does it provide meaningful read+write operations (not just read-only)?
|
|
3961
|
+
- **Quality signals** (30%): Stars, recent activity, vendor/official status
|
|
3962
|
+
|
|
3963
|
+
Return a JSON array where each element has:
|
|
3964
|
+
- "index": the candidate's index number
|
|
3965
|
+
- "score": relevance score 0-100
|
|
3966
|
+
- "reason": one-liner explaining the score (max 80 chars)
|
|
3967
|
+
|
|
3968
|
+
Be selective. Only score candidates that would genuinely help developers working on this project.
|
|
3969
|
+
Return ONLY the JSON array.`;
|
|
3970
|
+
var EXTRACT_CONFIG_PROMPT = `You extract MCP server configuration from a README file.
|
|
3971
|
+
|
|
3972
|
+
Look for the MCP server's configuration block \u2014 typically a JSON snippet showing how to add it to claude_desktop_config.json, .mcp.json, or similar.
|
|
3973
|
+
|
|
3974
|
+
Return a JSON object with:
|
|
3975
|
+
- "command": the executable command (e.g., "npx", "uvx", "node", "docker")
|
|
3976
|
+
- "args": array of arguments (e.g., ["-y", "@supabase/mcp-server"])
|
|
3977
|
+
- "env": array of objects, each with:
|
|
3978
|
+
- "key": environment variable name (e.g., "SUPABASE_ACCESS_TOKEN")
|
|
3979
|
+
- "description": brief description of what this value is (e.g., "Personal access token from dashboard")
|
|
3980
|
+
- "required": boolean
|
|
3981
|
+
|
|
3982
|
+
If the README shows multiple configuration methods, prefer npx > uvx > node > docker.
|
|
3983
|
+
If you cannot determine the configuration, return {"command": "", "args": [], "env": []}.
|
|
3984
|
+
Return ONLY the JSON object.`;
|
|
3985
|
+
|
|
3986
|
+
// src/mcp/validate.ts
|
|
3987
|
+
var MIN_STARS = 100;
|
|
3988
|
+
var MAX_AGE_DAYS = 180;
|
|
3989
|
+
async function validateAndScore(candidates, toolDeps) {
|
|
3990
|
+
const qualityFiltered = candidates.filter((c) => {
|
|
3991
|
+
if (c.vendor) return true;
|
|
3992
|
+
if (c.stars > 0 && c.stars < MIN_STARS) return false;
|
|
3993
|
+
if (c.lastPush) {
|
|
3994
|
+
const pushDate = new Date(c.lastPush);
|
|
3995
|
+
const daysAgo = (Date.now() - pushDate.getTime()) / (1e3 * 60 * 60 * 24);
|
|
3996
|
+
if (daysAgo > MAX_AGE_DAYS) return false;
|
|
3997
|
+
}
|
|
3998
|
+
return true;
|
|
3999
|
+
});
|
|
4000
|
+
if (qualityFiltered.length === 0) return [];
|
|
4001
|
+
try {
|
|
4002
|
+
return await scoreWithLLM(qualityFiltered, toolDeps);
|
|
4003
|
+
} catch {
|
|
4004
|
+
return qualityFiltered.slice(0, 5).map((c) => ({
|
|
4005
|
+
...c,
|
|
4006
|
+
score: 50,
|
|
4007
|
+
reason: c.description.slice(0, 80)
|
|
4008
|
+
}));
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
async function scoreWithLLM(candidates, toolDeps) {
|
|
4012
|
+
const candidateList = candidates.map((c, i) => {
|
|
4013
|
+
const vendorTag = c.vendor ? " [VENDOR/OFFICIAL]" : "";
|
|
4014
|
+
return `${i}. "${c.name}"${vendorTag} (${c.stars} stars) \u2014 ${c.description.slice(0, 100)}`;
|
|
4015
|
+
}).join("\n");
|
|
4016
|
+
const scored = await llmJsonCall({
|
|
4017
|
+
system: SCORE_MCP_PROMPT,
|
|
4018
|
+
prompt: `TOOL DEPENDENCIES IN PROJECT:
|
|
4019
|
+
${toolDeps.join(", ")}
|
|
4020
|
+
|
|
4021
|
+
MCP SERVER CANDIDATES:
|
|
4022
|
+
${candidateList}`,
|
|
4023
|
+
maxTokens: 4e3
|
|
4024
|
+
});
|
|
4025
|
+
if (!Array.isArray(scored)) return [];
|
|
4026
|
+
return scored.filter((s) => s.score >= 60 && s.index >= 0 && s.index < candidates.length).sort((a, b) => b.score - a.score).slice(0, 5).map((s) => ({
|
|
4027
|
+
...candidates[s.index],
|
|
4028
|
+
score: s.score,
|
|
4029
|
+
reason: s.reason || candidates[s.index].description.slice(0, 80)
|
|
4030
|
+
}));
|
|
4031
|
+
}
|
|
4032
|
+
|
|
4033
|
+
// src/mcp/config-extract.ts
|
|
4034
|
+
async function fetchReadme(repoFullName) {
|
|
4035
|
+
try {
|
|
4036
|
+
const resp = await fetch(
|
|
4037
|
+
`https://raw.githubusercontent.com/${repoFullName}/HEAD/README.md`,
|
|
4038
|
+
{ signal: AbortSignal.timeout(1e4) }
|
|
4039
|
+
);
|
|
4040
|
+
if (resp.ok) return await resp.text();
|
|
4041
|
+
} catch {
|
|
4042
|
+
}
|
|
4043
|
+
try {
|
|
4044
|
+
const resp = await fetch(
|
|
4045
|
+
`https://raw.githubusercontent.com/${repoFullName}/main/README.md`,
|
|
4046
|
+
{ signal: AbortSignal.timeout(1e4) }
|
|
4047
|
+
);
|
|
4048
|
+
if (resp.ok) return await resp.text();
|
|
4049
|
+
} catch {
|
|
4050
|
+
}
|
|
4051
|
+
return null;
|
|
4052
|
+
}
|
|
4053
|
+
async function extractMcpConfig(readme, serverName) {
|
|
4054
|
+
try {
|
|
4055
|
+
const truncated = readme.length > 15e3 ? readme.slice(0, 15e3) : readme;
|
|
4056
|
+
const result = await llmJsonCall({
|
|
4057
|
+
system: EXTRACT_CONFIG_PROMPT,
|
|
4058
|
+
prompt: `MCP Server: ${serverName}
|
|
4059
|
+
|
|
4060
|
+
README:
|
|
4061
|
+
${truncated}`,
|
|
4062
|
+
maxTokens: 2e3
|
|
4063
|
+
});
|
|
4064
|
+
if (!result || !result.command) return null;
|
|
4065
|
+
return {
|
|
4066
|
+
command: result.command,
|
|
4067
|
+
args: Array.isArray(result.args) ? result.args : [],
|
|
4068
|
+
env: Array.isArray(result.env) ? result.env : []
|
|
4069
|
+
};
|
|
4070
|
+
} catch {
|
|
4071
|
+
return null;
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
|
|
4075
|
+
// src/mcp/index.ts
|
|
4076
|
+
async function discoverAndInstallMcps(targetAgent, fingerprint, dir) {
|
|
4077
|
+
console.log(chalk4.hex("#6366f1").bold("\n MCP Server Discovery\n"));
|
|
4078
|
+
const toolDeps = fingerprint.tools;
|
|
4079
|
+
if (toolDeps.length === 0) {
|
|
4080
|
+
console.log(chalk4.dim(" No external tools or services detected \u2014 skipping MCP discovery"));
|
|
4081
|
+
return { installed: 0, names: [] };
|
|
4082
|
+
}
|
|
4083
|
+
const spinner = ora(`Searching MCP servers for ${toolDeps.length} detected tool${toolDeps.length === 1 ? "" : "s"}...`).start();
|
|
4084
|
+
console.log(chalk4.dim(` Detected: ${toolDeps.join(", ")}`));
|
|
4085
|
+
const existingMcps = getExistingMcpNames(fingerprint, targetAgent);
|
|
4086
|
+
const filteredDeps = toolDeps.filter((d) => {
|
|
4087
|
+
const lower = d.toLowerCase();
|
|
4088
|
+
return !existingMcps.some((name) => name.includes(lower) || lower.includes(name));
|
|
4089
|
+
});
|
|
4090
|
+
if (filteredDeps.length === 0) {
|
|
4091
|
+
spinner.succeed(chalk4.dim("All detected tools already have MCP servers configured"));
|
|
4092
|
+
return { installed: 0, names: [] };
|
|
4093
|
+
}
|
|
4094
|
+
const candidates = await searchAllMcpSources(filteredDeps);
|
|
4095
|
+
if (candidates.length === 0) {
|
|
4096
|
+
spinner.succeed(chalk4.dim("No MCP servers found for detected tools"));
|
|
4097
|
+
return { installed: 0, names: [] };
|
|
4098
|
+
}
|
|
4099
|
+
spinner.succeed(`Found ${candidates.length} candidate${candidates.length === 1 ? "" : "s"} for ${filteredDeps.join(", ")}`);
|
|
4100
|
+
const scoreSpinner = ora("Scoring MCP candidates...").start();
|
|
4101
|
+
const scored = await validateAndScore(candidates, filteredDeps);
|
|
4102
|
+
if (scored.length === 0) {
|
|
4103
|
+
scoreSpinner.succeed(chalk4.dim("No quality MCP servers passed validation"));
|
|
4104
|
+
console.log(chalk4.dim(` Candidates checked: ${candidates.map((c) => c.name).join(", ")}`));
|
|
4105
|
+
return { installed: 0, names: [] };
|
|
4106
|
+
}
|
|
4107
|
+
scoreSpinner.succeed(`${scored.length} quality MCP server${scored.length === 1 ? "" : "s"} found`);
|
|
4108
|
+
console.log(chalk4.dim(` Scored: ${scored.map((c) => `${c.name} (${c.score})`).join(", ")}`));
|
|
4109
|
+
const selected = await interactiveSelect(scored);
|
|
4110
|
+
if (!selected || selected.length === 0) {
|
|
4111
|
+
return { installed: 0, names: [] };
|
|
4112
|
+
}
|
|
4113
|
+
const mcpServers = {};
|
|
4114
|
+
const installedNames = [];
|
|
4115
|
+
for (const mcp of selected) {
|
|
4116
|
+
console.log(chalk4.bold(`
|
|
4117
|
+
Configuring ${mcp.name}...`));
|
|
4118
|
+
const readme = await fetchReadme(mcp.repoFullName);
|
|
4119
|
+
if (!readme) {
|
|
4120
|
+
console.log(chalk4.yellow(` Could not fetch README for ${mcp.repoFullName} \u2014 skipping`));
|
|
4121
|
+
console.log(chalk4.dim(` Manual setup: ${mcp.url}`));
|
|
4122
|
+
continue;
|
|
4123
|
+
}
|
|
4124
|
+
const config = await extractMcpConfig(readme, mcp.name);
|
|
4125
|
+
if (!config || !config.command) {
|
|
4126
|
+
console.log(chalk4.yellow(` Could not extract config for ${mcp.name} \u2014 skipping`));
|
|
4127
|
+
console.log(chalk4.dim(` Manual setup: ${mcp.url}`));
|
|
4128
|
+
continue;
|
|
4129
|
+
}
|
|
4130
|
+
const env = {};
|
|
4131
|
+
for (const envVar of config.env) {
|
|
4132
|
+
if (!envVar.required) continue;
|
|
4133
|
+
const value = await promptInput2(` ? ${envVar.key} (${envVar.description})`);
|
|
4134
|
+
if (value) {
|
|
4135
|
+
env[envVar.key] = value;
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
const serverConfig = {
|
|
4139
|
+
command: config.command
|
|
4140
|
+
};
|
|
4141
|
+
if (config.args.length > 0) serverConfig.args = config.args;
|
|
4142
|
+
if (Object.keys(env).length > 0) serverConfig.env = env;
|
|
4143
|
+
mcpServers[mcp.name] = serverConfig;
|
|
4144
|
+
installedNames.push(mcp.name);
|
|
4145
|
+
console.log(` ${chalk4.green("\u2713")} ${mcp.name} configured`);
|
|
4146
|
+
}
|
|
4147
|
+
if (installedNames.length === 0) {
|
|
4148
|
+
return { installed: 0, names: [] };
|
|
4149
|
+
}
|
|
4150
|
+
if (targetAgent === "claude" || targetAgent === "both") {
|
|
4151
|
+
writeMcpJson(path15.join(dir, ".mcp.json"), mcpServers);
|
|
4152
|
+
}
|
|
4153
|
+
if (targetAgent === "cursor" || targetAgent === "both") {
|
|
4154
|
+
const cursorDir = path15.join(dir, ".cursor");
|
|
4155
|
+
if (!fs17.existsSync(cursorDir)) fs17.mkdirSync(cursorDir, { recursive: true });
|
|
4156
|
+
writeMcpJson(path15.join(cursorDir, "mcp.json"), mcpServers);
|
|
4157
|
+
}
|
|
4158
|
+
return { installed: installedNames.length, names: installedNames };
|
|
4159
|
+
}
|
|
4160
|
+
function writeMcpJson(filePath, mcpServers) {
|
|
4161
|
+
let existing = {};
|
|
4162
|
+
try {
|
|
4163
|
+
if (fs17.existsSync(filePath)) {
|
|
4164
|
+
const parsed = JSON.parse(fs17.readFileSync(filePath, "utf-8"));
|
|
4165
|
+
if (parsed.mcpServers) existing = parsed.mcpServers;
|
|
4166
|
+
}
|
|
4167
|
+
} catch {
|
|
4168
|
+
}
|
|
4169
|
+
const merged = { ...existing, ...mcpServers };
|
|
4170
|
+
fs17.writeFileSync(filePath, JSON.stringify({ mcpServers: merged }, null, 2) + "\n");
|
|
4171
|
+
}
|
|
4172
|
+
function getExistingMcpNames(fingerprint, targetAgent) {
|
|
4173
|
+
const names = [];
|
|
4174
|
+
if (targetAgent === "claude" || targetAgent === "both") {
|
|
4175
|
+
if (fingerprint.existingConfigs.claudeMcpServers) {
|
|
4176
|
+
names.push(...Object.keys(fingerprint.existingConfigs.claudeMcpServers).map((k) => k.toLowerCase()));
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
if (targetAgent === "cursor" || targetAgent === "both") {
|
|
4180
|
+
if (fingerprint.existingConfigs.cursorMcpServers) {
|
|
4181
|
+
names.push(...Object.keys(fingerprint.existingConfigs.cursorMcpServers).map((k) => k.toLowerCase()));
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4184
|
+
return names;
|
|
4185
|
+
}
|
|
4186
|
+
function promptInput2(question) {
|
|
4187
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
4188
|
+
return new Promise((resolve2) => {
|
|
4189
|
+
rl.question(chalk4.cyan(`${question}: `), (answer) => {
|
|
4190
|
+
rl.close();
|
|
4191
|
+
resolve2(answer.trim());
|
|
4192
|
+
});
|
|
4193
|
+
});
|
|
4194
|
+
}
|
|
4195
|
+
async function interactiveSelect(candidates) {
|
|
4196
|
+
if (!process.stdin.isTTY) {
|
|
4197
|
+
console.log(chalk4.bold("\n Available MCP servers:\n"));
|
|
4198
|
+
for (const c of candidates) {
|
|
4199
|
+
const vendorTag = c.vendor ? chalk4.blue(" (vendor)") : "";
|
|
4200
|
+
console.log(` ${String(c.score).padStart(3)} ${c.name}${vendorTag} ${chalk4.dim(c.reason)}`);
|
|
4201
|
+
}
|
|
4202
|
+
console.log("");
|
|
4203
|
+
return null;
|
|
4204
|
+
}
|
|
4205
|
+
const selected = /* @__PURE__ */ new Set();
|
|
4206
|
+
let cursor = 0;
|
|
4207
|
+
const { stdin, stdout } = process;
|
|
4208
|
+
let lineCount = 0;
|
|
4209
|
+
function render() {
|
|
4210
|
+
const lines = [];
|
|
4211
|
+
lines.push(chalk4.bold(" Select MCP servers to install:"));
|
|
4212
|
+
lines.push("");
|
|
4213
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
4214
|
+
const c = candidates[i];
|
|
4215
|
+
const check = selected.has(i) ? chalk4.green("[x]") : "[ ]";
|
|
4216
|
+
const ptr = i === cursor ? chalk4.cyan(">") : " ";
|
|
4217
|
+
const scoreColor = c.score >= 90 ? chalk4.green : c.score >= 70 ? chalk4.yellow : chalk4.dim;
|
|
4218
|
+
const vendorTag = c.vendor ? chalk4.blue(" (vendor)") : "";
|
|
4219
|
+
lines.push(` ${ptr} ${check} ${scoreColor(String(c.score).padStart(3))} ${c.name}${vendorTag} ${chalk4.dim(c.reason.slice(0, 40))}`);
|
|
4220
|
+
}
|
|
4221
|
+
lines.push("");
|
|
4222
|
+
lines.push(chalk4.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q skip"));
|
|
4223
|
+
return lines.join("\n");
|
|
4224
|
+
}
|
|
4225
|
+
function draw(initial) {
|
|
4226
|
+
if (!initial && lineCount > 0) {
|
|
4227
|
+
stdout.write(`\x1B[${lineCount}A`);
|
|
4228
|
+
}
|
|
4229
|
+
stdout.write("\x1B[0J");
|
|
4230
|
+
const output = render();
|
|
4231
|
+
stdout.write(output + "\n");
|
|
4232
|
+
lineCount = output.split("\n").length;
|
|
4233
|
+
}
|
|
4234
|
+
return new Promise((resolve2) => {
|
|
4235
|
+
console.log("");
|
|
4236
|
+
draw(true);
|
|
4237
|
+
stdin.setRawMode(true);
|
|
4238
|
+
stdin.resume();
|
|
4239
|
+
stdin.setEncoding("utf8");
|
|
4240
|
+
function cleanup() {
|
|
4241
|
+
stdin.removeListener("data", onData);
|
|
4242
|
+
stdin.setRawMode(false);
|
|
4243
|
+
stdin.pause();
|
|
4244
|
+
}
|
|
4245
|
+
function onData(key) {
|
|
4246
|
+
switch (key) {
|
|
4247
|
+
case "\x1B[A":
|
|
4248
|
+
cursor = (cursor - 1 + candidates.length) % candidates.length;
|
|
4249
|
+
draw(false);
|
|
4250
|
+
break;
|
|
4251
|
+
case "\x1B[B":
|
|
4252
|
+
cursor = (cursor + 1) % candidates.length;
|
|
4253
|
+
draw(false);
|
|
4254
|
+
break;
|
|
4255
|
+
case " ":
|
|
4256
|
+
selected.has(cursor) ? selected.delete(cursor) : selected.add(cursor);
|
|
4257
|
+
draw(false);
|
|
4258
|
+
break;
|
|
4259
|
+
case "a":
|
|
4260
|
+
candidates.forEach((_, i) => selected.add(i));
|
|
4261
|
+
draw(false);
|
|
4262
|
+
break;
|
|
4263
|
+
case "n":
|
|
4264
|
+
selected.clear();
|
|
4265
|
+
draw(false);
|
|
4266
|
+
break;
|
|
4267
|
+
case "\r":
|
|
4268
|
+
case "\n":
|
|
4269
|
+
cleanup();
|
|
4270
|
+
if (selected.size === 0) {
|
|
4271
|
+
console.log(chalk4.dim("\n No MCP servers selected.\n"));
|
|
4272
|
+
resolve2(null);
|
|
4273
|
+
} else {
|
|
4274
|
+
resolve2(Array.from(selected).sort().map((i) => candidates[i]));
|
|
4275
|
+
}
|
|
4276
|
+
break;
|
|
4277
|
+
case "q":
|
|
4278
|
+
case "\x1B":
|
|
4279
|
+
case "":
|
|
4280
|
+
cleanup();
|
|
4281
|
+
console.log(chalk4.dim("\n Skipped MCP server installation.\n"));
|
|
4282
|
+
resolve2(null);
|
|
4283
|
+
break;
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
stdin.on("data", onData);
|
|
4287
|
+
});
|
|
4288
|
+
}
|
|
4289
|
+
|
|
3856
4290
|
// src/commands/onboard.ts
|
|
3857
4291
|
async function initCommand(options) {
|
|
3858
|
-
const brand =
|
|
3859
|
-
const title =
|
|
4292
|
+
const brand = chalk5.hex("#EB9D83");
|
|
4293
|
+
const title = chalk5.hex("#83D1EB");
|
|
3860
4294
|
console.log(brand.bold(`
|
|
3861
4295
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
3862
4296
|
\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
@@ -3865,19 +4299,20 @@ async function initCommand(options) {
|
|
|
3865
4299
|
\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
3866
4300
|
\u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
|
|
3867
4301
|
`));
|
|
3868
|
-
console.log(
|
|
4302
|
+
console.log(chalk5.dim(" Onboard your project for AI-assisted development\n"));
|
|
3869
4303
|
console.log(title.bold(" Welcome to Caliber\n"));
|
|
3870
|
-
console.log(
|
|
3871
|
-
console.log(
|
|
4304
|
+
console.log(chalk5.dim(" Caliber analyzes your codebase and creates tailored config files"));
|
|
4305
|
+
console.log(chalk5.dim(" so your AI coding agents understand your project from day one.\n"));
|
|
3872
4306
|
console.log(title.bold(" How onboarding works:\n"));
|
|
3873
|
-
console.log(
|
|
3874
|
-
console.log(
|
|
3875
|
-
console.log(
|
|
3876
|
-
console.log(
|
|
3877
|
-
console.log(
|
|
4307
|
+
console.log(chalk5.dim(" 1. Connect Set up your LLM provider"));
|
|
4308
|
+
console.log(chalk5.dim(" 2. Discover Analyze your code, dependencies, and structure"));
|
|
4309
|
+
console.log(chalk5.dim(" 3. Generate Create config files tailored to your project"));
|
|
4310
|
+
console.log(chalk5.dim(" 4. Review Preview, refine, and apply the changes"));
|
|
4311
|
+
console.log(chalk5.dim(" 5. Enhance Discover MCP servers for your tools\n"));
|
|
4312
|
+
console.log(title.bold(" Step 1/5 \u2014 Connect your LLM\n"));
|
|
3878
4313
|
let config = loadConfig();
|
|
3879
4314
|
if (!config) {
|
|
3880
|
-
console.log(
|
|
4315
|
+
console.log(chalk5.dim(" No LLM provider set yet. Choose how to run Caliber:\n"));
|
|
3881
4316
|
try {
|
|
3882
4317
|
await runInteractiveProviderSetup({
|
|
3883
4318
|
selectMessage: "How do you want to use Caliber? (choose LLM provider)"
|
|
@@ -3888,23 +4323,23 @@ async function initCommand(options) {
|
|
|
3888
4323
|
}
|
|
3889
4324
|
config = loadConfig();
|
|
3890
4325
|
if (!config) {
|
|
3891
|
-
console.log(
|
|
4326
|
+
console.log(chalk5.red(" Setup was cancelled or failed.\n"));
|
|
3892
4327
|
throw new Error("__exit__");
|
|
3893
4328
|
}
|
|
3894
|
-
console.log(
|
|
4329
|
+
console.log(chalk5.green(" \u2713 Provider saved. Let's continue.\n"));
|
|
3895
4330
|
}
|
|
3896
4331
|
const displayModel = config.model === "default" && config.provider === "claude-cli" ? process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)" : config.model;
|
|
3897
|
-
const fastModel =
|
|
4332
|
+
const fastModel = getFastModel();
|
|
3898
4333
|
const modelLine = fastModel ? ` Provider: ${config.provider} | Model: ${displayModel} | Scan: ${fastModel}` : ` Provider: ${config.provider} | Model: ${displayModel}`;
|
|
3899
|
-
console.log(
|
|
3900
|
-
console.log(title.bold(" Step 2/
|
|
3901
|
-
console.log(
|
|
3902
|
-
const spinner =
|
|
4334
|
+
console.log(chalk5.dim(modelLine + "\n"));
|
|
4335
|
+
console.log(title.bold(" Step 2/5 \u2014 Discover your project\n"));
|
|
4336
|
+
console.log(chalk5.dim(" Learning about your languages, dependencies, structure, and existing configs.\n"));
|
|
4337
|
+
const spinner = ora2("Analyzing project...").start();
|
|
3903
4338
|
const fingerprint = collectFingerprint(process.cwd());
|
|
3904
4339
|
await enrichFingerprintWithLLM(fingerprint, process.cwd());
|
|
3905
4340
|
spinner.succeed("Project analyzed");
|
|
3906
|
-
console.log(
|
|
3907
|
-
console.log(
|
|
4341
|
+
console.log(chalk5.dim(` Languages: ${fingerprint.languages.join(", ") || "none detected"}`));
|
|
4342
|
+
console.log(chalk5.dim(` Files: ${fingerprint.fileTree.length} found
|
|
3908
4343
|
`));
|
|
3909
4344
|
const targetAgent = options.agent || await promptAgent();
|
|
3910
4345
|
const preScore = computeLocalScore(process.cwd(), targetAgent);
|
|
@@ -3922,13 +4357,13 @@ async function initCommand(options) {
|
|
|
3922
4357
|
displayScoreSummary(baselineScore);
|
|
3923
4358
|
const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length);
|
|
3924
4359
|
if (hasExistingConfig && baselineScore.score === 100) {
|
|
3925
|
-
console.log(
|
|
3926
|
-
console.log(
|
|
4360
|
+
console.log(chalk5.bold.green(" Your setup is already optimal \u2014 nothing to change.\n"));
|
|
4361
|
+
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber onboard --force") + chalk5.dim(" to regenerate anyway.\n"));
|
|
3927
4362
|
if (!options.force) return;
|
|
3928
4363
|
}
|
|
3929
4364
|
const isEmpty = fingerprint.fileTree.length < 3;
|
|
3930
4365
|
if (isEmpty) {
|
|
3931
|
-
fingerprint.description = await
|
|
4366
|
+
fingerprint.description = await promptInput3("What will you build in this project?");
|
|
3932
4367
|
}
|
|
3933
4368
|
let failingChecks;
|
|
3934
4369
|
let passingChecks;
|
|
@@ -3938,25 +4373,25 @@ async function initCommand(options) {
|
|
|
3938
4373
|
passingChecks = baselineScore.checks.filter((c) => c.passed).map((c) => ({ name: c.name }));
|
|
3939
4374
|
currentScore = baselineScore.score;
|
|
3940
4375
|
if (failingChecks.length > 0) {
|
|
3941
|
-
console.log(title.bold(" Step 3/
|
|
3942
|
-
console.log(
|
|
4376
|
+
console.log(title.bold(" Step 3/5 \u2014 Fine-tuning\n"));
|
|
4377
|
+
console.log(chalk5.dim(` Your setup scores ${baselineScore.score}/100 \u2014 fixing ${failingChecks.length} remaining issue${failingChecks.length === 1 ? "" : "s"}:
|
|
3943
4378
|
`));
|
|
3944
4379
|
for (const check of failingChecks) {
|
|
3945
|
-
console.log(
|
|
4380
|
+
console.log(chalk5.dim(` \u2022 ${check.name}`));
|
|
3946
4381
|
}
|
|
3947
4382
|
console.log("");
|
|
3948
4383
|
}
|
|
3949
4384
|
} else if (hasExistingConfig) {
|
|
3950
|
-
console.log(title.bold(" Step 3/
|
|
3951
|
-
console.log(
|
|
3952
|
-
console.log(
|
|
4385
|
+
console.log(title.bold(" Step 3/5 \u2014 Improve your setup\n"));
|
|
4386
|
+
console.log(chalk5.dim(" Reviewing your existing configs against your codebase"));
|
|
4387
|
+
console.log(chalk5.dim(" and preparing improvements.\n"));
|
|
3953
4388
|
} else {
|
|
3954
|
-
console.log(title.bold(" Step 3/
|
|
3955
|
-
console.log(
|
|
4389
|
+
console.log(title.bold(" Step 3/5 \u2014 Build your agent setup\n"));
|
|
4390
|
+
console.log(chalk5.dim(" Creating config files tailored to your project.\n"));
|
|
3956
4391
|
}
|
|
3957
|
-
console.log(
|
|
4392
|
+
console.log(chalk5.dim(" This can take a couple of minutes depending on your model and provider.\n"));
|
|
3958
4393
|
const genStartTime = Date.now();
|
|
3959
|
-
const genSpinner =
|
|
4394
|
+
const genSpinner = ora2("Generating setup...").start();
|
|
3960
4395
|
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
|
|
3961
4396
|
genMessages.start();
|
|
3962
4397
|
let generatedSetup = null;
|
|
@@ -3996,8 +4431,8 @@ async function initCommand(options) {
|
|
|
3996
4431
|
if (!generatedSetup) {
|
|
3997
4432
|
genSpinner.fail("Failed to generate setup.");
|
|
3998
4433
|
if (rawOutput) {
|
|
3999
|
-
console.log(
|
|
4000
|
-
console.log(
|
|
4434
|
+
console.log(chalk5.dim("\nRaw LLM output (JSON parse failed):"));
|
|
4435
|
+
console.log(chalk5.dim(rawOutput.slice(0, 500)));
|
|
4001
4436
|
}
|
|
4002
4437
|
throw new Error("__exit__");
|
|
4003
4438
|
}
|
|
@@ -4005,17 +4440,17 @@ async function initCommand(options) {
|
|
|
4005
4440
|
const mins = Math.floor(elapsedMs / 6e4);
|
|
4006
4441
|
const secs = Math.floor(elapsedMs % 6e4 / 1e3);
|
|
4007
4442
|
const timeStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
|
|
4008
|
-
genSpinner.succeed(`Setup generated ${
|
|
4443
|
+
genSpinner.succeed(`Setup generated ${chalk5.dim(`in ${timeStr}`)}`);
|
|
4009
4444
|
printSetupSummary(generatedSetup);
|
|
4010
4445
|
const sessionHistory = [];
|
|
4011
4446
|
sessionHistory.push({
|
|
4012
4447
|
role: "assistant",
|
|
4013
4448
|
content: summarizeSetup("Initial generation", generatedSetup)
|
|
4014
4449
|
});
|
|
4015
|
-
console.log(title.bold(" Step 4/
|
|
4450
|
+
console.log(title.bold(" Step 4/5 \u2014 Review and apply\n"));
|
|
4016
4451
|
const setupFiles = collectSetupFiles(generatedSetup);
|
|
4017
4452
|
const staged = stageFiles(setupFiles, process.cwd());
|
|
4018
|
-
console.log(
|
|
4453
|
+
console.log(chalk5.dim(` ${chalk5.green(`${staged.newFiles} new`)} / ${chalk5.yellow(`${staged.modifiedFiles} modified`)} file${staged.newFiles + staged.modifiedFiles !== 1 ? "s" : ""}
|
|
4019
4454
|
`));
|
|
4020
4455
|
const wantsReview = await promptWantsReview();
|
|
4021
4456
|
if (wantsReview) {
|
|
@@ -4027,12 +4462,12 @@ async function initCommand(options) {
|
|
|
4027
4462
|
generatedSetup = await refineLoop(generatedSetup, targetAgent, sessionHistory);
|
|
4028
4463
|
if (!generatedSetup) {
|
|
4029
4464
|
cleanupStaging();
|
|
4030
|
-
console.log(
|
|
4465
|
+
console.log(chalk5.dim("Refinement cancelled. No files were modified."));
|
|
4031
4466
|
return;
|
|
4032
4467
|
}
|
|
4033
4468
|
const updatedFiles = collectSetupFiles(generatedSetup);
|
|
4034
4469
|
const restaged = stageFiles(updatedFiles, process.cwd());
|
|
4035
|
-
console.log(
|
|
4470
|
+
console.log(chalk5.dim(` ${chalk5.green(`${restaged.newFiles} new`)} / ${chalk5.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
|
|
4036
4471
|
`));
|
|
4037
4472
|
printSetupSummary(generatedSetup);
|
|
4038
4473
|
await openReview("terminal", restaged.stagedFiles);
|
|
@@ -4040,37 +4475,56 @@ async function initCommand(options) {
|
|
|
4040
4475
|
}
|
|
4041
4476
|
cleanupStaging();
|
|
4042
4477
|
if (action === "decline") {
|
|
4043
|
-
console.log(
|
|
4478
|
+
console.log(chalk5.dim("Setup declined. No files were modified."));
|
|
4044
4479
|
return;
|
|
4045
4480
|
}
|
|
4046
4481
|
if (options.dryRun) {
|
|
4047
|
-
console.log(
|
|
4482
|
+
console.log(chalk5.yellow("\n[Dry run] Would write the following files:"));
|
|
4048
4483
|
console.log(JSON.stringify(generatedSetup, null, 2));
|
|
4049
4484
|
return;
|
|
4050
4485
|
}
|
|
4051
|
-
const writeSpinner =
|
|
4486
|
+
const writeSpinner = ora2("Writing config files...").start();
|
|
4052
4487
|
try {
|
|
4053
4488
|
const result = writeSetup(generatedSetup);
|
|
4054
4489
|
writeSpinner.succeed("Config files written");
|
|
4055
|
-
console.log(
|
|
4490
|
+
console.log(chalk5.bold("\nFiles created/updated:"));
|
|
4056
4491
|
for (const file of result.written) {
|
|
4057
|
-
console.log(` ${
|
|
4492
|
+
console.log(` ${chalk5.green("\u2713")} ${file}`);
|
|
4058
4493
|
}
|
|
4059
4494
|
if (result.deleted.length > 0) {
|
|
4060
|
-
console.log(
|
|
4495
|
+
console.log(chalk5.bold("\nFiles removed:"));
|
|
4061
4496
|
for (const file of result.deleted) {
|
|
4062
|
-
console.log(` ${
|
|
4497
|
+
console.log(` ${chalk5.red("\u2717")} ${file}`);
|
|
4063
4498
|
}
|
|
4064
4499
|
}
|
|
4065
4500
|
if (result.backupDir) {
|
|
4066
|
-
console.log(
|
|
4501
|
+
console.log(chalk5.dim(`
|
|
4067
4502
|
Backups saved to ${result.backupDir}`));
|
|
4068
4503
|
}
|
|
4069
4504
|
} catch (err) {
|
|
4070
4505
|
writeSpinner.fail("Failed to write files");
|
|
4071
|
-
console.error(
|
|
4506
|
+
console.error(chalk5.red(err instanceof Error ? err.message : "Unknown error"));
|
|
4072
4507
|
throw new Error("__exit__");
|
|
4073
4508
|
}
|
|
4509
|
+
console.log(title.bold("\n Step 5/5 \u2014 Enhance with MCP servers\n"));
|
|
4510
|
+
console.log(chalk5.dim(" MCP servers connect your AI agents to external tools and services"));
|
|
4511
|
+
console.log(chalk5.dim(" like databases, APIs, and platforms your project depends on.\n"));
|
|
4512
|
+
if (fingerprint.tools.length > 0) {
|
|
4513
|
+
try {
|
|
4514
|
+
const mcpResult = await discoverAndInstallMcps(targetAgent, fingerprint, process.cwd());
|
|
4515
|
+
if (mcpResult.installed > 0) {
|
|
4516
|
+
console.log(chalk5.bold(`
|
|
4517
|
+
${mcpResult.installed} MCP server${mcpResult.installed > 1 ? "s" : ""} configured`));
|
|
4518
|
+
for (const name of mcpResult.names) {
|
|
4519
|
+
console.log(` ${chalk5.green("\u2713")} ${name}`);
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
} catch (err) {
|
|
4523
|
+
console.log(chalk5.dim(" MCP discovery skipped: " + (err instanceof Error ? err.message : "unknown error")));
|
|
4524
|
+
}
|
|
4525
|
+
} else {
|
|
4526
|
+
console.log(chalk5.dim(" No external tools or services detected \u2014 skipping MCP discovery.\n"));
|
|
4527
|
+
}
|
|
4074
4528
|
ensurePermissions();
|
|
4075
4529
|
const sha = getCurrentHeadSha();
|
|
4076
4530
|
writeState({
|
|
@@ -4080,56 +4534,56 @@ async function initCommand(options) {
|
|
|
4080
4534
|
});
|
|
4081
4535
|
console.log("");
|
|
4082
4536
|
console.log(title.bold(" Keep your configs fresh\n"));
|
|
4083
|
-
console.log(
|
|
4537
|
+
console.log(chalk5.dim(" Caliber can automatically update your agent configs when your code changes.\n"));
|
|
4084
4538
|
const hookChoice = await promptHookType(targetAgent);
|
|
4085
4539
|
if (hookChoice === "claude" || hookChoice === "both") {
|
|
4086
4540
|
const hookResult = installHook();
|
|
4087
4541
|
if (hookResult.installed) {
|
|
4088
|
-
console.log(` ${
|
|
4089
|
-
console.log(
|
|
4542
|
+
console.log(` ${chalk5.green("\u2713")} Claude Code hook installed \u2014 docs update on session end`);
|
|
4543
|
+
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber hooks remove") + chalk5.dim(" to disable"));
|
|
4090
4544
|
} else if (hookResult.alreadyInstalled) {
|
|
4091
|
-
console.log(
|
|
4545
|
+
console.log(chalk5.dim(" Claude Code hook already installed"));
|
|
4092
4546
|
}
|
|
4093
4547
|
const learnResult = installLearningHooks();
|
|
4094
4548
|
if (learnResult.installed) {
|
|
4095
|
-
console.log(` ${
|
|
4096
|
-
console.log(
|
|
4549
|
+
console.log(` ${chalk5.green("\u2713")} Learning hooks installed \u2014 session insights captured automatically`);
|
|
4550
|
+
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber learn remove") + chalk5.dim(" to disable"));
|
|
4097
4551
|
} else if (learnResult.alreadyInstalled) {
|
|
4098
|
-
console.log(
|
|
4552
|
+
console.log(chalk5.dim(" Learning hooks already installed"));
|
|
4099
4553
|
}
|
|
4100
4554
|
}
|
|
4101
4555
|
if (hookChoice === "precommit" || hookChoice === "both") {
|
|
4102
4556
|
const precommitResult = installPreCommitHook();
|
|
4103
4557
|
if (precommitResult.installed) {
|
|
4104
|
-
console.log(` ${
|
|
4105
|
-
console.log(
|
|
4558
|
+
console.log(` ${chalk5.green("\u2713")} Pre-commit hook installed \u2014 docs refresh before each commit`);
|
|
4559
|
+
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber hooks remove-precommit") + chalk5.dim(" to disable"));
|
|
4106
4560
|
} else if (precommitResult.alreadyInstalled) {
|
|
4107
|
-
console.log(
|
|
4561
|
+
console.log(chalk5.dim(" Pre-commit hook already installed"));
|
|
4108
4562
|
} else {
|
|
4109
|
-
console.log(
|
|
4563
|
+
console.log(chalk5.yellow(" Could not install pre-commit hook (not a git repository?)"));
|
|
4110
4564
|
}
|
|
4111
4565
|
}
|
|
4112
4566
|
if (hookChoice === "skip") {
|
|
4113
|
-
console.log(
|
|
4567
|
+
console.log(chalk5.dim(" Skipped auto-refresh hooks. Run ") + chalk5.hex("#83D1EB")("caliber hooks install") + chalk5.dim(" later to enable."));
|
|
4114
4568
|
}
|
|
4115
4569
|
const afterScore = computeLocalScore(process.cwd(), targetAgent);
|
|
4116
4570
|
if (afterScore.score < baselineScore.score) {
|
|
4117
4571
|
console.log("");
|
|
4118
|
-
console.log(
|
|
4572
|
+
console.log(chalk5.yellow(` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`));
|
|
4119
4573
|
try {
|
|
4120
4574
|
const { restored, removed } = undoSetup();
|
|
4121
4575
|
if (restored.length > 0 || removed.length > 0) {
|
|
4122
|
-
console.log(
|
|
4576
|
+
console.log(chalk5.dim(` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`));
|
|
4123
4577
|
}
|
|
4124
4578
|
} catch {
|
|
4125
4579
|
}
|
|
4126
|
-
console.log(
|
|
4580
|
+
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber onboard --force") + chalk5.dim(" to override.\n"));
|
|
4127
4581
|
return;
|
|
4128
4582
|
}
|
|
4129
4583
|
displayScoreDelta(baselineScore, afterScore);
|
|
4130
|
-
console.log(
|
|
4131
|
-
console.log(
|
|
4132
|
-
console.log(
|
|
4584
|
+
console.log(chalk5.bold.green(" Onboarding complete! Your project is ready for AI-assisted development."));
|
|
4585
|
+
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber undo") + chalk5.dim(" to revert changes.\n"));
|
|
4586
|
+
console.log(chalk5.bold(" Next steps:\n"));
|
|
4133
4587
|
console.log(` ${title("caliber score")} See your full config breakdown`);
|
|
4134
4588
|
console.log(` ${title("caliber recommend")} Discover community skills for your stack`);
|
|
4135
4589
|
console.log(` ${title("caliber undo")} Revert all changes from this run`);
|
|
@@ -4137,7 +4591,7 @@ async function initCommand(options) {
|
|
|
4137
4591
|
}
|
|
4138
4592
|
async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
4139
4593
|
while (true) {
|
|
4140
|
-
const message = await
|
|
4594
|
+
const message = await promptInput3("\nWhat would you like to change?");
|
|
4141
4595
|
if (!message || message.toLowerCase() === "done" || message.toLowerCase() === "accept") {
|
|
4142
4596
|
return currentSetup;
|
|
4143
4597
|
}
|
|
@@ -4146,12 +4600,12 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
|
4146
4600
|
}
|
|
4147
4601
|
const isValid = await classifyRefineIntent(message);
|
|
4148
4602
|
if (!isValid) {
|
|
4149
|
-
console.log(
|
|
4150
|
-
console.log(
|
|
4151
|
-
console.log(
|
|
4603
|
+
console.log(chalk5.dim(" This doesn't look like a config change request."));
|
|
4604
|
+
console.log(chalk5.dim(" Describe what to add, remove, or modify in your configs."));
|
|
4605
|
+
console.log(chalk5.dim(' Type "done" to accept the current setup.\n'));
|
|
4152
4606
|
continue;
|
|
4153
4607
|
}
|
|
4154
|
-
const refineSpinner =
|
|
4608
|
+
const refineSpinner = ora2("Refining setup...").start();
|
|
4155
4609
|
const refineMessages = new SpinnerMessages(refineSpinner, REFINE_MESSAGES);
|
|
4156
4610
|
refineMessages.start();
|
|
4157
4611
|
const refined = await refineSetup(
|
|
@@ -4169,21 +4623,21 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
|
4169
4623
|
});
|
|
4170
4624
|
refineSpinner.succeed("Setup updated");
|
|
4171
4625
|
printSetupSummary(refined);
|
|
4172
|
-
console.log(
|
|
4626
|
+
console.log(chalk5.dim('Type "done" to accept, or describe more changes.'));
|
|
4173
4627
|
} else {
|
|
4174
4628
|
refineSpinner.fail("Refinement failed \u2014 could not parse AI response.");
|
|
4175
|
-
console.log(
|
|
4629
|
+
console.log(chalk5.dim('Try rephrasing your request, or type "done" to keep the current setup.'));
|
|
4176
4630
|
}
|
|
4177
4631
|
}
|
|
4178
4632
|
}
|
|
4179
4633
|
function summarizeSetup(action, setup) {
|
|
4180
4634
|
const descriptions = setup.fileDescriptions;
|
|
4181
|
-
const files = descriptions ? Object.entries(descriptions).map(([
|
|
4635
|
+
const files = descriptions ? Object.entries(descriptions).map(([path23, desc]) => ` ${path23}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
|
|
4182
4636
|
return `${action}. Files:
|
|
4183
4637
|
${files}`;
|
|
4184
4638
|
}
|
|
4185
4639
|
async function classifyRefineIntent(message) {
|
|
4186
|
-
const fastModel =
|
|
4640
|
+
const fastModel = getFastModel();
|
|
4187
4641
|
try {
|
|
4188
4642
|
const result = await llmJsonCall({
|
|
4189
4643
|
system: `You classify whether a user message is a valid request to modify AI agent config files (CLAUDE.md, .cursorrules, skills).
|
|
@@ -4200,7 +4654,7 @@ Return {"valid": true} or {"valid": false}. Nothing else.`,
|
|
|
4200
4654
|
}
|
|
4201
4655
|
}
|
|
4202
4656
|
async function evaluateDismissals(failingChecks, fingerprint) {
|
|
4203
|
-
const fastModel =
|
|
4657
|
+
const fastModel = getFastModel();
|
|
4204
4658
|
const checkList = failingChecks.map((c) => ({
|
|
4205
4659
|
id: c.id,
|
|
4206
4660
|
name: c.name,
|
|
@@ -4229,10 +4683,10 @@ ${JSON.stringify(checkList, null, 2)}`,
|
|
|
4229
4683
|
return [];
|
|
4230
4684
|
}
|
|
4231
4685
|
}
|
|
4232
|
-
function
|
|
4233
|
-
const rl =
|
|
4686
|
+
function promptInput3(question) {
|
|
4687
|
+
const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
|
|
4234
4688
|
return new Promise((resolve2) => {
|
|
4235
|
-
rl.question(
|
|
4689
|
+
rl.question(chalk5.cyan(`${question} `), (answer) => {
|
|
4236
4690
|
rl.close();
|
|
4237
4691
|
resolve2(answer.trim());
|
|
4238
4692
|
});
|
|
@@ -4294,7 +4748,7 @@ async function openReview(method, stagedFiles) {
|
|
|
4294
4748
|
originalPath: f.originalPath,
|
|
4295
4749
|
proposedPath: f.proposedPath
|
|
4296
4750
|
})));
|
|
4297
|
-
console.log(
|
|
4751
|
+
console.log(chalk5.dim(" Diffs opened in your editor.\n"));
|
|
4298
4752
|
return;
|
|
4299
4753
|
}
|
|
4300
4754
|
const fileInfos = stagedFiles.map((file) => {
|
|
@@ -4325,8 +4779,8 @@ async function openReview(method, stagedFiles) {
|
|
|
4325
4779
|
async function interactiveDiffExplorer(files) {
|
|
4326
4780
|
if (!process.stdin.isTTY) {
|
|
4327
4781
|
for (const f of files) {
|
|
4328
|
-
const icon = f.isNew ?
|
|
4329
|
-
const stats = f.isNew ?
|
|
4782
|
+
const icon = f.isNew ? chalk5.green("+") : chalk5.yellow("~");
|
|
4783
|
+
const stats = f.isNew ? chalk5.dim(`${f.lines} lines`) : `${chalk5.green(`+${f.added}`)} ${chalk5.red(`-${f.removed}`)}`;
|
|
4330
4784
|
console.log(` ${icon} ${f.relativePath} ${stats}`);
|
|
4331
4785
|
}
|
|
4332
4786
|
console.log("");
|
|
@@ -4342,47 +4796,47 @@ async function interactiveDiffExplorer(files) {
|
|
|
4342
4796
|
}
|
|
4343
4797
|
function renderFileList() {
|
|
4344
4798
|
const lines = [];
|
|
4345
|
-
lines.push(
|
|
4799
|
+
lines.push(chalk5.bold(" Review changes"));
|
|
4346
4800
|
lines.push("");
|
|
4347
4801
|
for (let i = 0; i < files.length; i++) {
|
|
4348
4802
|
const f = files[i];
|
|
4349
|
-
const ptr = i === cursor ?
|
|
4350
|
-
const icon = f.isNew ?
|
|
4351
|
-
const stats = f.isNew ?
|
|
4803
|
+
const ptr = i === cursor ? chalk5.cyan(">") : " ";
|
|
4804
|
+
const icon = f.isNew ? chalk5.green("+") : chalk5.yellow("~");
|
|
4805
|
+
const stats = f.isNew ? chalk5.dim(`${f.lines} lines`) : `${chalk5.green(`+${f.added}`)} ${chalk5.red(`-${f.removed}`)}`;
|
|
4352
4806
|
lines.push(` ${ptr} ${icon} ${f.relativePath} ${stats}`);
|
|
4353
4807
|
}
|
|
4354
4808
|
lines.push("");
|
|
4355
|
-
lines.push(
|
|
4809
|
+
lines.push(chalk5.dim(" \u2191\u2193 navigate \u23CE view diff q done"));
|
|
4356
4810
|
return lines.join("\n");
|
|
4357
4811
|
}
|
|
4358
4812
|
function renderDiff(index) {
|
|
4359
4813
|
const f = files[index];
|
|
4360
4814
|
const lines = [];
|
|
4361
|
-
const header = f.isNew ? ` ${
|
|
4815
|
+
const header = f.isNew ? ` ${chalk5.green("+")} ${f.relativePath} ${chalk5.dim("(new file)")}` : ` ${chalk5.yellow("~")} ${f.relativePath} ${chalk5.green(`+${f.added}`)} ${chalk5.red(`-${f.removed}`)}`;
|
|
4362
4816
|
lines.push(header);
|
|
4363
|
-
lines.push(
|
|
4817
|
+
lines.push(chalk5.dim(" " + "\u2500".repeat(60)));
|
|
4364
4818
|
const patchLines = f.patch.split("\n");
|
|
4365
4819
|
const bodyLines = patchLines.slice(4);
|
|
4366
4820
|
const maxVisible = getTermHeight() - 4;
|
|
4367
4821
|
const visibleLines = bodyLines.slice(scrollOffset, scrollOffset + maxVisible);
|
|
4368
4822
|
for (const line of visibleLines) {
|
|
4369
4823
|
if (line.startsWith("+")) {
|
|
4370
|
-
lines.push(
|
|
4824
|
+
lines.push(chalk5.green(" " + line));
|
|
4371
4825
|
} else if (line.startsWith("-")) {
|
|
4372
|
-
lines.push(
|
|
4826
|
+
lines.push(chalk5.red(" " + line));
|
|
4373
4827
|
} else if (line.startsWith("@@")) {
|
|
4374
|
-
lines.push(
|
|
4828
|
+
lines.push(chalk5.cyan(" " + line));
|
|
4375
4829
|
} else {
|
|
4376
|
-
lines.push(
|
|
4830
|
+
lines.push(chalk5.dim(" " + line));
|
|
4377
4831
|
}
|
|
4378
4832
|
}
|
|
4379
4833
|
const totalBody = bodyLines.length;
|
|
4380
4834
|
if (totalBody > maxVisible) {
|
|
4381
4835
|
const pct = Math.round((scrollOffset + maxVisible) / totalBody * 100);
|
|
4382
|
-
lines.push(
|
|
4836
|
+
lines.push(chalk5.dim(` \u2500\u2500 ${Math.min(pct, 100)}% \u2500\u2500`));
|
|
4383
4837
|
}
|
|
4384
4838
|
lines.push("");
|
|
4385
|
-
lines.push(
|
|
4839
|
+
lines.push(chalk5.dim(" \u2191\u2193 scroll \u23B5/esc back to file list"));
|
|
4386
4840
|
return lines.join("\n");
|
|
4387
4841
|
}
|
|
4388
4842
|
function draw(initial) {
|
|
@@ -4476,46 +4930,46 @@ function printSetupSummary(setup) {
|
|
|
4476
4930
|
const fileDescriptions = setup.fileDescriptions;
|
|
4477
4931
|
const deletions = setup.deletions;
|
|
4478
4932
|
console.log("");
|
|
4479
|
-
console.log(
|
|
4933
|
+
console.log(chalk5.bold(" Proposed changes:\n"));
|
|
4480
4934
|
const getDescription = (filePath) => {
|
|
4481
4935
|
return fileDescriptions?.[filePath];
|
|
4482
4936
|
};
|
|
4483
4937
|
if (claude) {
|
|
4484
4938
|
if (claude.claudeMd) {
|
|
4485
|
-
const icon = fs18.existsSync("CLAUDE.md") ?
|
|
4939
|
+
const icon = fs18.existsSync("CLAUDE.md") ? chalk5.yellow("~") : chalk5.green("+");
|
|
4486
4940
|
const desc = getDescription("CLAUDE.md");
|
|
4487
|
-
console.log(` ${icon} ${
|
|
4488
|
-
if (desc) console.log(
|
|
4941
|
+
console.log(` ${icon} ${chalk5.bold("CLAUDE.md")}`);
|
|
4942
|
+
if (desc) console.log(chalk5.dim(` ${desc}`));
|
|
4489
4943
|
console.log("");
|
|
4490
4944
|
}
|
|
4491
4945
|
const skills = claude.skills;
|
|
4492
4946
|
if (Array.isArray(skills) && skills.length > 0) {
|
|
4493
4947
|
for (const skill of skills) {
|
|
4494
4948
|
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
4495
|
-
const icon = fs18.existsSync(skillPath) ?
|
|
4949
|
+
const icon = fs18.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
4496
4950
|
const desc = getDescription(skillPath);
|
|
4497
|
-
console.log(` ${icon} ${
|
|
4498
|
-
console.log(
|
|
4951
|
+
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
4952
|
+
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
4499
4953
|
console.log("");
|
|
4500
4954
|
}
|
|
4501
4955
|
}
|
|
4502
4956
|
}
|
|
4503
4957
|
if (cursor) {
|
|
4504
4958
|
if (cursor.cursorrules) {
|
|
4505
|
-
const icon = fs18.existsSync(".cursorrules") ?
|
|
4959
|
+
const icon = fs18.existsSync(".cursorrules") ? chalk5.yellow("~") : chalk5.green("+");
|
|
4506
4960
|
const desc = getDescription(".cursorrules");
|
|
4507
|
-
console.log(` ${icon} ${
|
|
4508
|
-
if (desc) console.log(
|
|
4961
|
+
console.log(` ${icon} ${chalk5.bold(".cursorrules")}`);
|
|
4962
|
+
if (desc) console.log(chalk5.dim(` ${desc}`));
|
|
4509
4963
|
console.log("");
|
|
4510
4964
|
}
|
|
4511
4965
|
const cursorSkills = cursor.skills;
|
|
4512
4966
|
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
4513
4967
|
for (const skill of cursorSkills) {
|
|
4514
4968
|
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
4515
|
-
const icon = fs18.existsSync(skillPath) ?
|
|
4969
|
+
const icon = fs18.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
4516
4970
|
const desc = getDescription(skillPath);
|
|
4517
|
-
console.log(` ${icon} ${
|
|
4518
|
-
console.log(
|
|
4971
|
+
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
4972
|
+
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
4519
4973
|
console.log("");
|
|
4520
4974
|
}
|
|
4521
4975
|
}
|
|
@@ -4523,32 +4977,32 @@ function printSetupSummary(setup) {
|
|
|
4523
4977
|
if (Array.isArray(rules) && rules.length > 0) {
|
|
4524
4978
|
for (const rule of rules) {
|
|
4525
4979
|
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
4526
|
-
const icon = fs18.existsSync(rulePath) ?
|
|
4980
|
+
const icon = fs18.existsSync(rulePath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
4527
4981
|
const desc = getDescription(rulePath);
|
|
4528
|
-
console.log(` ${icon} ${
|
|
4982
|
+
console.log(` ${icon} ${chalk5.bold(rulePath)}`);
|
|
4529
4983
|
if (desc) {
|
|
4530
|
-
console.log(
|
|
4984
|
+
console.log(chalk5.dim(` ${desc}`));
|
|
4531
4985
|
} else {
|
|
4532
4986
|
const firstLine = rule.content.split("\n").filter((l) => l.trim() && !l.trim().startsWith("#"))[0];
|
|
4533
|
-
if (firstLine) console.log(
|
|
4987
|
+
if (firstLine) console.log(chalk5.dim(` ${firstLine.trim().slice(0, 80)}`));
|
|
4534
4988
|
}
|
|
4535
4989
|
console.log("");
|
|
4536
4990
|
}
|
|
4537
4991
|
}
|
|
4538
4992
|
}
|
|
4539
4993
|
if (!fs18.existsSync("AGENTS.md")) {
|
|
4540
|
-
console.log(` ${
|
|
4541
|
-
console.log(
|
|
4994
|
+
console.log(` ${chalk5.green("+")} ${chalk5.bold("AGENTS.md")}`);
|
|
4995
|
+
console.log(chalk5.dim(" Cross-agent coordination file"));
|
|
4542
4996
|
console.log("");
|
|
4543
4997
|
}
|
|
4544
4998
|
if (Array.isArray(deletions) && deletions.length > 0) {
|
|
4545
4999
|
for (const del of deletions) {
|
|
4546
|
-
console.log(` ${
|
|
4547
|
-
console.log(
|
|
5000
|
+
console.log(` ${chalk5.red("-")} ${chalk5.bold(del.filePath)}`);
|
|
5001
|
+
console.log(chalk5.dim(` ${del.reason}`));
|
|
4548
5002
|
console.log("");
|
|
4549
5003
|
}
|
|
4550
5004
|
}
|
|
4551
|
-
console.log(` ${
|
|
5005
|
+
console.log(` ${chalk5.green("+")} ${chalk5.dim("new")} ${chalk5.yellow("~")} ${chalk5.dim("modified")} ${chalk5.red("-")} ${chalk5.dim("removed")}`);
|
|
4552
5006
|
console.log("");
|
|
4553
5007
|
}
|
|
4554
5008
|
function buildSkillContent(skill) {
|
|
@@ -4629,10 +5083,10 @@ ${agentRefs.join(" ")}
|
|
|
4629
5083
|
}
|
|
4630
5084
|
|
|
4631
5085
|
// src/commands/undo.ts
|
|
4632
|
-
import
|
|
4633
|
-
import
|
|
5086
|
+
import chalk6 from "chalk";
|
|
5087
|
+
import ora3 from "ora";
|
|
4634
5088
|
function undoCommand() {
|
|
4635
|
-
const spinner =
|
|
5089
|
+
const spinner = ora3("Reverting setup...").start();
|
|
4636
5090
|
try {
|
|
4637
5091
|
const { restored, removed } = undoSetup();
|
|
4638
5092
|
if (restored.length === 0 && removed.length === 0) {
|
|
@@ -4641,26 +5095,26 @@ function undoCommand() {
|
|
|
4641
5095
|
}
|
|
4642
5096
|
spinner.succeed("Setup reverted successfully.\n");
|
|
4643
5097
|
if (restored.length > 0) {
|
|
4644
|
-
console.log(
|
|
5098
|
+
console.log(chalk6.cyan(" Restored from backup:"));
|
|
4645
5099
|
for (const file of restored) {
|
|
4646
|
-
console.log(` ${
|
|
5100
|
+
console.log(` ${chalk6.green("\u21A9")} ${file}`);
|
|
4647
5101
|
}
|
|
4648
5102
|
}
|
|
4649
5103
|
if (removed.length > 0) {
|
|
4650
|
-
console.log(
|
|
5104
|
+
console.log(chalk6.cyan(" Removed:"));
|
|
4651
5105
|
for (const file of removed) {
|
|
4652
|
-
console.log(` ${
|
|
5106
|
+
console.log(` ${chalk6.red("\u2717")} ${file}`);
|
|
4653
5107
|
}
|
|
4654
5108
|
}
|
|
4655
5109
|
console.log("");
|
|
4656
5110
|
} catch (err) {
|
|
4657
|
-
spinner.fail(
|
|
5111
|
+
spinner.fail(chalk6.red(err instanceof Error ? err.message : "Undo failed"));
|
|
4658
5112
|
throw new Error("__exit__");
|
|
4659
5113
|
}
|
|
4660
5114
|
}
|
|
4661
5115
|
|
|
4662
5116
|
// src/commands/status.ts
|
|
4663
|
-
import
|
|
5117
|
+
import chalk7 from "chalk";
|
|
4664
5118
|
import fs19 from "fs";
|
|
4665
5119
|
async function statusCommand(options) {
|
|
4666
5120
|
const config = loadConfig();
|
|
@@ -4674,45 +5128,45 @@ async function statusCommand(options) {
|
|
|
4674
5128
|
}, null, 2));
|
|
4675
5129
|
return;
|
|
4676
5130
|
}
|
|
4677
|
-
console.log(
|
|
5131
|
+
console.log(chalk7.bold("\nCaliber Status\n"));
|
|
4678
5132
|
if (config) {
|
|
4679
|
-
console.log(` LLM: ${
|
|
5133
|
+
console.log(` LLM: ${chalk7.green(config.provider)} (${config.model})`);
|
|
4680
5134
|
} else {
|
|
4681
|
-
console.log(` LLM: ${
|
|
5135
|
+
console.log(` LLM: ${chalk7.yellow("Not configured")} \u2014 run ${chalk7.hex("#83D1EB")("caliber config")}`);
|
|
4682
5136
|
}
|
|
4683
5137
|
if (!manifest) {
|
|
4684
|
-
console.log(` Setup: ${
|
|
4685
|
-
console.log(
|
|
5138
|
+
console.log(` Setup: ${chalk7.dim("No setup applied")}`);
|
|
5139
|
+
console.log(chalk7.dim("\n Run ") + chalk7.hex("#83D1EB")("caliber onboard") + chalk7.dim(" to get started.\n"));
|
|
4686
5140
|
return;
|
|
4687
5141
|
}
|
|
4688
|
-
console.log(` Files managed: ${
|
|
5142
|
+
console.log(` Files managed: ${chalk7.cyan(manifest.entries.length.toString())}`);
|
|
4689
5143
|
for (const entry of manifest.entries) {
|
|
4690
5144
|
const exists = fs19.existsSync(entry.path);
|
|
4691
|
-
const icon = exists ?
|
|
5145
|
+
const icon = exists ? chalk7.green("\u2713") : chalk7.red("\u2717");
|
|
4692
5146
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
4693
5147
|
}
|
|
4694
5148
|
console.log("");
|
|
4695
5149
|
}
|
|
4696
5150
|
|
|
4697
5151
|
// src/commands/regenerate.ts
|
|
4698
|
-
import
|
|
4699
|
-
import
|
|
5152
|
+
import chalk8 from "chalk";
|
|
5153
|
+
import ora4 from "ora";
|
|
4700
5154
|
import confirm from "@inquirer/confirm";
|
|
4701
5155
|
async function regenerateCommand(options) {
|
|
4702
5156
|
const config = loadConfig();
|
|
4703
5157
|
if (!config) {
|
|
4704
|
-
console.log(
|
|
5158
|
+
console.log(chalk8.red("No LLM provider configured. Run ") + chalk8.hex("#83D1EB")("caliber config") + chalk8.red(" (e.g. choose Cursor) or set ANTHROPIC_API_KEY."));
|
|
4705
5159
|
throw new Error("__exit__");
|
|
4706
5160
|
}
|
|
4707
5161
|
const manifest = readManifest();
|
|
4708
5162
|
if (!manifest) {
|
|
4709
|
-
console.log(
|
|
5163
|
+
console.log(chalk8.yellow("No existing setup found. Run ") + chalk8.hex("#83D1EB")("caliber onboard") + chalk8.yellow(" first."));
|
|
4710
5164
|
throw new Error("__exit__");
|
|
4711
5165
|
}
|
|
4712
|
-
const spinner =
|
|
5166
|
+
const spinner = ora4("Re-analyzing project...").start();
|
|
4713
5167
|
const fingerprint = collectFingerprint(process.cwd());
|
|
4714
5168
|
spinner.succeed("Project re-analyzed");
|
|
4715
|
-
const genSpinner =
|
|
5169
|
+
const genSpinner = ora4("Regenerating setup...").start();
|
|
4716
5170
|
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES);
|
|
4717
5171
|
genMessages.start();
|
|
4718
5172
|
let generatedSetup = null;
|
|
@@ -4749,37 +5203,37 @@ async function regenerateCommand(options) {
|
|
|
4749
5203
|
}
|
|
4750
5204
|
genSpinner.succeed("Setup regenerated");
|
|
4751
5205
|
if (options.dryRun) {
|
|
4752
|
-
console.log(
|
|
5206
|
+
console.log(chalk8.yellow("\n[Dry run] Would write:"));
|
|
4753
5207
|
console.log(JSON.stringify(generatedSetup, null, 2));
|
|
4754
5208
|
return;
|
|
4755
5209
|
}
|
|
4756
5210
|
const shouldApply = await confirm({ message: "Apply regenerated setup?", default: true });
|
|
4757
5211
|
if (!shouldApply) {
|
|
4758
|
-
console.log(
|
|
5212
|
+
console.log(chalk8.dim("Regeneration cancelled."));
|
|
4759
5213
|
return;
|
|
4760
5214
|
}
|
|
4761
|
-
const writeSpinner =
|
|
5215
|
+
const writeSpinner = ora4("Updating config files...").start();
|
|
4762
5216
|
const result = writeSetup(generatedSetup);
|
|
4763
5217
|
writeSpinner.succeed("Config files updated");
|
|
4764
5218
|
for (const file of result.written) {
|
|
4765
|
-
console.log(` ${
|
|
5219
|
+
console.log(` ${chalk8.green("\u2713")} ${file}`);
|
|
4766
5220
|
}
|
|
4767
5221
|
console.log("");
|
|
4768
5222
|
}
|
|
4769
5223
|
|
|
4770
5224
|
// src/commands/recommend.ts
|
|
4771
|
-
import
|
|
4772
|
-
import
|
|
5225
|
+
import chalk9 from "chalk";
|
|
5226
|
+
import ora5 from "ora";
|
|
4773
5227
|
import { mkdirSync, readFileSync as readFileSync7, readdirSync as readdirSync5, existsSync as existsSync9, writeFileSync } from "fs";
|
|
4774
5228
|
import { join as join8, dirname as dirname2 } from "path";
|
|
4775
5229
|
|
|
4776
5230
|
// src/scanner/index.ts
|
|
4777
5231
|
import fs20 from "fs";
|
|
4778
|
-
import
|
|
5232
|
+
import path16 from "path";
|
|
4779
5233
|
import crypto2 from "crypto";
|
|
4780
5234
|
function scanLocalState(dir) {
|
|
4781
5235
|
const items = [];
|
|
4782
|
-
const claudeMdPath =
|
|
5236
|
+
const claudeMdPath = path16.join(dir, "CLAUDE.md");
|
|
4783
5237
|
if (fs20.existsSync(claudeMdPath)) {
|
|
4784
5238
|
items.push({
|
|
4785
5239
|
type: "rule",
|
|
@@ -4789,10 +5243,10 @@ function scanLocalState(dir) {
|
|
|
4789
5243
|
path: claudeMdPath
|
|
4790
5244
|
});
|
|
4791
5245
|
}
|
|
4792
|
-
const skillsDir =
|
|
5246
|
+
const skillsDir = path16.join(dir, ".claude", "skills");
|
|
4793
5247
|
if (fs20.existsSync(skillsDir)) {
|
|
4794
5248
|
for (const file of fs20.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
4795
|
-
const filePath =
|
|
5249
|
+
const filePath = path16.join(skillsDir, file);
|
|
4796
5250
|
items.push({
|
|
4797
5251
|
type: "skill",
|
|
4798
5252
|
platform: "claude",
|
|
@@ -4802,7 +5256,7 @@ function scanLocalState(dir) {
|
|
|
4802
5256
|
});
|
|
4803
5257
|
}
|
|
4804
5258
|
}
|
|
4805
|
-
const mcpJsonPath =
|
|
5259
|
+
const mcpJsonPath = path16.join(dir, ".mcp.json");
|
|
4806
5260
|
if (fs20.existsSync(mcpJsonPath)) {
|
|
4807
5261
|
try {
|
|
4808
5262
|
const mcpJson = JSON.parse(fs20.readFileSync(mcpJsonPath, "utf-8"));
|
|
@@ -4820,7 +5274,7 @@ function scanLocalState(dir) {
|
|
|
4820
5274
|
} catch {
|
|
4821
5275
|
}
|
|
4822
5276
|
}
|
|
4823
|
-
const cursorrulesPath =
|
|
5277
|
+
const cursorrulesPath = path16.join(dir, ".cursorrules");
|
|
4824
5278
|
if (fs20.existsSync(cursorrulesPath)) {
|
|
4825
5279
|
items.push({
|
|
4826
5280
|
type: "rule",
|
|
@@ -4830,10 +5284,10 @@ function scanLocalState(dir) {
|
|
|
4830
5284
|
path: cursorrulesPath
|
|
4831
5285
|
});
|
|
4832
5286
|
}
|
|
4833
|
-
const cursorRulesDir =
|
|
5287
|
+
const cursorRulesDir = path16.join(dir, ".cursor", "rules");
|
|
4834
5288
|
if (fs20.existsSync(cursorRulesDir)) {
|
|
4835
5289
|
for (const file of fs20.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
4836
|
-
const filePath =
|
|
5290
|
+
const filePath = path16.join(cursorRulesDir, file);
|
|
4837
5291
|
items.push({
|
|
4838
5292
|
type: "rule",
|
|
4839
5293
|
platform: "cursor",
|
|
@@ -4843,11 +5297,11 @@ function scanLocalState(dir) {
|
|
|
4843
5297
|
});
|
|
4844
5298
|
}
|
|
4845
5299
|
}
|
|
4846
|
-
const cursorSkillsDir =
|
|
5300
|
+
const cursorSkillsDir = path16.join(dir, ".cursor", "skills");
|
|
4847
5301
|
if (fs20.existsSync(cursorSkillsDir)) {
|
|
4848
5302
|
try {
|
|
4849
5303
|
for (const name of fs20.readdirSync(cursorSkillsDir)) {
|
|
4850
|
-
const skillFile =
|
|
5304
|
+
const skillFile = path16.join(cursorSkillsDir, name, "SKILL.md");
|
|
4851
5305
|
if (fs20.existsSync(skillFile)) {
|
|
4852
5306
|
items.push({
|
|
4853
5307
|
type: "skill",
|
|
@@ -4861,7 +5315,7 @@ function scanLocalState(dir) {
|
|
|
4861
5315
|
} catch {
|
|
4862
5316
|
}
|
|
4863
5317
|
}
|
|
4864
|
-
const cursorMcpPath =
|
|
5318
|
+
const cursorMcpPath = path16.join(dir, ".cursor", "mcp.json");
|
|
4865
5319
|
if (fs20.existsSync(cursorMcpPath)) {
|
|
4866
5320
|
try {
|
|
4867
5321
|
const mcpJson = JSON.parse(fs20.readFileSync(cursorMcpPath, "utf-8"));
|
|
@@ -5040,7 +5494,7 @@ async function searchAllProviders(technologies, platform) {
|
|
|
5040
5494
|
}
|
|
5041
5495
|
return combined;
|
|
5042
5496
|
}
|
|
5043
|
-
async function
|
|
5497
|
+
async function scoreWithLLM2(candidates, projectContext, technologies) {
|
|
5044
5498
|
const candidateList = candidates.map((c, i) => `${i}. "${c.name}" \u2014 ${c.reason || "no description"}`).join("\n");
|
|
5045
5499
|
const scored = await llmJsonCall({
|
|
5046
5500
|
system: `You evaluate whether AI agent skills and tools are relevant to a specific software project.
|
|
@@ -5193,11 +5647,11 @@ async function recommendCommand(options) {
|
|
|
5193
5647
|
...extractTopDeps()
|
|
5194
5648
|
].filter(Boolean))];
|
|
5195
5649
|
if (technologies.length === 0) {
|
|
5196
|
-
console.log(
|
|
5650
|
+
console.log(chalk9.yellow("Could not detect any languages or dependencies. Try running from a project root."));
|
|
5197
5651
|
throw new Error("__exit__");
|
|
5198
5652
|
}
|
|
5199
5653
|
const primaryPlatform = platforms.includes("claude") ? "claude" : platforms[0];
|
|
5200
|
-
const searchSpinner =
|
|
5654
|
+
const searchSpinner = ora5("Searching skill registries...").start();
|
|
5201
5655
|
const allCandidates = await searchAllProviders(technologies, primaryPlatform);
|
|
5202
5656
|
if (!allCandidates.length) {
|
|
5203
5657
|
searchSpinner.succeed("No skills found matching your tech stack.");
|
|
@@ -5210,15 +5664,15 @@ async function recommendCommand(options) {
|
|
|
5210
5664
|
return;
|
|
5211
5665
|
}
|
|
5212
5666
|
searchSpinner.succeed(
|
|
5213
|
-
`Found ${allCandidates.length} skills` + (filteredCount > 0 ?
|
|
5667
|
+
`Found ${allCandidates.length} skills` + (filteredCount > 0 ? chalk9.dim(` (${filteredCount} already installed)`) : "")
|
|
5214
5668
|
);
|
|
5215
5669
|
let results;
|
|
5216
5670
|
const config = loadConfig();
|
|
5217
5671
|
if (config) {
|
|
5218
|
-
const scoreSpinner =
|
|
5672
|
+
const scoreSpinner = ora5("Scoring relevance for your project...").start();
|
|
5219
5673
|
try {
|
|
5220
5674
|
const projectContext = buildProjectContext(process.cwd());
|
|
5221
|
-
results = await
|
|
5675
|
+
results = await scoreWithLLM2(newCandidates, projectContext, technologies);
|
|
5222
5676
|
if (results.length === 0) {
|
|
5223
5677
|
scoreSpinner.succeed("No highly relevant skills found for your specific project.");
|
|
5224
5678
|
return;
|
|
@@ -5231,7 +5685,7 @@ async function recommendCommand(options) {
|
|
|
5231
5685
|
} else {
|
|
5232
5686
|
results = newCandidates.slice(0, 20);
|
|
5233
5687
|
}
|
|
5234
|
-
const fetchSpinner =
|
|
5688
|
+
const fetchSpinner = ora5("Verifying skill availability...").start();
|
|
5235
5689
|
const contentMap = /* @__PURE__ */ new Map();
|
|
5236
5690
|
await Promise.all(results.map(async (rec) => {
|
|
5237
5691
|
const content = await fetchSkillContent(rec);
|
|
@@ -5244,14 +5698,14 @@ async function recommendCommand(options) {
|
|
|
5244
5698
|
}
|
|
5245
5699
|
const unavailableCount = results.length - available.length;
|
|
5246
5700
|
fetchSpinner.succeed(
|
|
5247
|
-
`${available.length} installable skill${available.length > 1 ? "s" : ""}` + (unavailableCount > 0 ?
|
|
5701
|
+
`${available.length} installable skill${available.length > 1 ? "s" : ""}` + (unavailableCount > 0 ? chalk9.dim(` (${unavailableCount} unavailable)`) : "")
|
|
5248
5702
|
);
|
|
5249
|
-
const selected = await
|
|
5703
|
+
const selected = await interactiveSelect2(available);
|
|
5250
5704
|
if (selected?.length) {
|
|
5251
5705
|
await installSkills(selected, platforms, contentMap);
|
|
5252
5706
|
}
|
|
5253
5707
|
}
|
|
5254
|
-
async function
|
|
5708
|
+
async function interactiveSelect2(recs) {
|
|
5255
5709
|
if (!process.stdin.isTTY) {
|
|
5256
5710
|
printRecommendations(recs);
|
|
5257
5711
|
return null;
|
|
@@ -5263,27 +5717,27 @@ async function interactiveSelect(recs) {
|
|
|
5263
5717
|
const hasScores = recs.some((r) => r.score > 0);
|
|
5264
5718
|
function render() {
|
|
5265
5719
|
const lines = [];
|
|
5266
|
-
lines.push(
|
|
5720
|
+
lines.push(chalk9.bold(" Recommendations"));
|
|
5267
5721
|
lines.push("");
|
|
5268
5722
|
if (hasScores) {
|
|
5269
|
-
lines.push(` ${
|
|
5723
|
+
lines.push(` ${chalk9.dim("Score".padEnd(7))} ${chalk9.dim("Name".padEnd(28))} ${chalk9.dim("Why")}`);
|
|
5270
5724
|
} else {
|
|
5271
|
-
lines.push(` ${
|
|
5725
|
+
lines.push(` ${chalk9.dim("Name".padEnd(30))} ${chalk9.dim("Technology".padEnd(18))} ${chalk9.dim("Source")}`);
|
|
5272
5726
|
}
|
|
5273
|
-
lines.push(
|
|
5727
|
+
lines.push(chalk9.dim(" " + "\u2500".repeat(70)));
|
|
5274
5728
|
for (let i = 0; i < recs.length; i++) {
|
|
5275
5729
|
const rec = recs[i];
|
|
5276
|
-
const check = selected.has(i) ?
|
|
5277
|
-
const ptr = i === cursor ?
|
|
5730
|
+
const check = selected.has(i) ? chalk9.green("[x]") : "[ ]";
|
|
5731
|
+
const ptr = i === cursor ? chalk9.cyan(">") : " ";
|
|
5278
5732
|
if (hasScores) {
|
|
5279
|
-
const scoreColor = rec.score >= 90 ?
|
|
5280
|
-
lines.push(` ${ptr} ${check} ${scoreColor(String(rec.score).padStart(3))} ${rec.name.padEnd(26)} ${
|
|
5733
|
+
const scoreColor = rec.score >= 90 ? chalk9.green : rec.score >= 70 ? chalk9.yellow : chalk9.dim;
|
|
5734
|
+
lines.push(` ${ptr} ${check} ${scoreColor(String(rec.score).padStart(3))} ${rec.name.padEnd(26)} ${chalk9.dim(rec.reason.slice(0, 40))}`);
|
|
5281
5735
|
} else {
|
|
5282
|
-
lines.push(` ${ptr} ${check} ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${
|
|
5736
|
+
lines.push(` ${ptr} ${check} ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk9.dim(rec.source_url || "")}`);
|
|
5283
5737
|
}
|
|
5284
5738
|
}
|
|
5285
5739
|
lines.push("");
|
|
5286
|
-
lines.push(
|
|
5740
|
+
lines.push(chalk9.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q cancel"));
|
|
5287
5741
|
return lines.join("\n");
|
|
5288
5742
|
}
|
|
5289
5743
|
function draw(initial) {
|
|
@@ -5332,7 +5786,7 @@ async function interactiveSelect(recs) {
|
|
|
5332
5786
|
case "\n":
|
|
5333
5787
|
cleanup();
|
|
5334
5788
|
if (selected.size === 0) {
|
|
5335
|
-
console.log(
|
|
5789
|
+
console.log(chalk9.dim("\n No skills selected.\n"));
|
|
5336
5790
|
resolve2(null);
|
|
5337
5791
|
} else {
|
|
5338
5792
|
resolve2(Array.from(selected).sort().map((i) => recs[i]));
|
|
@@ -5342,7 +5796,7 @@ async function interactiveSelect(recs) {
|
|
|
5342
5796
|
case "\x1B":
|
|
5343
5797
|
case "":
|
|
5344
5798
|
cleanup();
|
|
5345
|
-
console.log(
|
|
5799
|
+
console.log(chalk9.dim("\n Cancelled.\n"));
|
|
5346
5800
|
resolve2(null);
|
|
5347
5801
|
break;
|
|
5348
5802
|
}
|
|
@@ -5388,7 +5842,7 @@ async function fetchSkillContent(rec) {
|
|
|
5388
5842
|
return null;
|
|
5389
5843
|
}
|
|
5390
5844
|
async function installSkills(recs, platforms, contentMap) {
|
|
5391
|
-
const spinner =
|
|
5845
|
+
const spinner = ora5(`Installing ${recs.length} skill${recs.length > 1 ? "s" : ""}...`).start();
|
|
5392
5846
|
const installed = [];
|
|
5393
5847
|
for (const rec of recs) {
|
|
5394
5848
|
const content = contentMap.get(rec.slug);
|
|
@@ -5404,7 +5858,7 @@ async function installSkills(recs, platforms, contentMap) {
|
|
|
5404
5858
|
if (installed.length > 0) {
|
|
5405
5859
|
spinner.succeed(`Installed ${installed.length} file${installed.length > 1 ? "s" : ""}`);
|
|
5406
5860
|
for (const p of installed) {
|
|
5407
|
-
console.log(
|
|
5861
|
+
console.log(chalk9.green(` \u2713 ${p}`));
|
|
5408
5862
|
}
|
|
5409
5863
|
} else {
|
|
5410
5864
|
spinner.fail("No skills were installed");
|
|
@@ -5413,25 +5867,25 @@ async function installSkills(recs, platforms, contentMap) {
|
|
|
5413
5867
|
}
|
|
5414
5868
|
function printRecommendations(recs) {
|
|
5415
5869
|
const hasScores = recs.some((r) => r.score > 0);
|
|
5416
|
-
console.log(
|
|
5870
|
+
console.log(chalk9.bold("\n Recommendations\n"));
|
|
5417
5871
|
if (hasScores) {
|
|
5418
|
-
console.log(` ${
|
|
5872
|
+
console.log(` ${chalk9.dim("Score".padEnd(7))} ${chalk9.dim("Name".padEnd(28))} ${chalk9.dim("Why")}`);
|
|
5419
5873
|
} else {
|
|
5420
|
-
console.log(` ${
|
|
5874
|
+
console.log(` ${chalk9.dim("Name".padEnd(30))} ${chalk9.dim("Technology".padEnd(18))} ${chalk9.dim("Source")}`);
|
|
5421
5875
|
}
|
|
5422
|
-
console.log(
|
|
5876
|
+
console.log(chalk9.dim(" " + "\u2500".repeat(70)));
|
|
5423
5877
|
for (const rec of recs) {
|
|
5424
5878
|
if (hasScores) {
|
|
5425
|
-
console.log(` ${String(rec.score).padStart(3)} ${rec.name.padEnd(26)} ${
|
|
5879
|
+
console.log(` ${String(rec.score).padStart(3)} ${rec.name.padEnd(26)} ${chalk9.dim(rec.reason.slice(0, 50))}`);
|
|
5426
5880
|
} else {
|
|
5427
|
-
console.log(` ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${
|
|
5881
|
+
console.log(` ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk9.dim(rec.source_url || "")}`);
|
|
5428
5882
|
}
|
|
5429
5883
|
}
|
|
5430
5884
|
console.log("");
|
|
5431
5885
|
}
|
|
5432
5886
|
|
|
5433
5887
|
// src/commands/score.ts
|
|
5434
|
-
import
|
|
5888
|
+
import chalk10 from "chalk";
|
|
5435
5889
|
async function scoreCommand(options) {
|
|
5436
5890
|
const dir = process.cwd();
|
|
5437
5891
|
const target = options.agent ?? readState()?.targetAgent;
|
|
@@ -5445,23 +5899,23 @@ async function scoreCommand(options) {
|
|
|
5445
5899
|
return;
|
|
5446
5900
|
}
|
|
5447
5901
|
displayScore(result);
|
|
5448
|
-
const separator =
|
|
5902
|
+
const separator = chalk10.gray(" " + "\u2500".repeat(53));
|
|
5449
5903
|
console.log(separator);
|
|
5450
5904
|
if (result.score < 40) {
|
|
5451
|
-
console.log(
|
|
5905
|
+
console.log(chalk10.gray(" Run ") + chalk10.hex("#83D1EB")("caliber onboard") + chalk10.gray(" to generate a complete, optimized setup."));
|
|
5452
5906
|
} else if (result.score < 70) {
|
|
5453
|
-
console.log(
|
|
5907
|
+
console.log(chalk10.gray(" Run ") + chalk10.hex("#83D1EB")("caliber onboard") + chalk10.gray(" to improve your setup."));
|
|
5454
5908
|
} else {
|
|
5455
|
-
console.log(
|
|
5909
|
+
console.log(chalk10.green(" Looking good!") + chalk10.gray(" Run ") + chalk10.hex("#83D1EB")("caliber update") + chalk10.gray(" to keep it fresh."));
|
|
5456
5910
|
}
|
|
5457
5911
|
console.log("");
|
|
5458
5912
|
}
|
|
5459
5913
|
|
|
5460
5914
|
// src/commands/refresh.ts
|
|
5461
5915
|
import fs22 from "fs";
|
|
5462
|
-
import
|
|
5463
|
-
import
|
|
5464
|
-
import
|
|
5916
|
+
import path18 from "path";
|
|
5917
|
+
import chalk11 from "chalk";
|
|
5918
|
+
import ora6 from "ora";
|
|
5465
5919
|
|
|
5466
5920
|
// src/lib/git-diff.ts
|
|
5467
5921
|
import { execSync as execSync8 } from "child_process";
|
|
@@ -5537,7 +5991,7 @@ function collectDiff(lastSha) {
|
|
|
5537
5991
|
|
|
5538
5992
|
// src/writers/refresh.ts
|
|
5539
5993
|
import fs21 from "fs";
|
|
5540
|
-
import
|
|
5994
|
+
import path17 from "path";
|
|
5541
5995
|
function writeRefreshDocs(docs) {
|
|
5542
5996
|
const written = [];
|
|
5543
5997
|
if (docs.claudeMd) {
|
|
@@ -5553,19 +6007,19 @@ function writeRefreshDocs(docs) {
|
|
|
5553
6007
|
written.push(".cursorrules");
|
|
5554
6008
|
}
|
|
5555
6009
|
if (docs.cursorRules) {
|
|
5556
|
-
const rulesDir =
|
|
6010
|
+
const rulesDir = path17.join(".cursor", "rules");
|
|
5557
6011
|
if (!fs21.existsSync(rulesDir)) fs21.mkdirSync(rulesDir, { recursive: true });
|
|
5558
6012
|
for (const rule of docs.cursorRules) {
|
|
5559
|
-
const filePath =
|
|
6013
|
+
const filePath = path17.join(rulesDir, rule.filename);
|
|
5560
6014
|
fs21.writeFileSync(filePath, rule.content);
|
|
5561
6015
|
written.push(filePath);
|
|
5562
6016
|
}
|
|
5563
6017
|
}
|
|
5564
6018
|
if (docs.claudeSkills) {
|
|
5565
|
-
const skillsDir =
|
|
6019
|
+
const skillsDir = path17.join(".claude", "skills");
|
|
5566
6020
|
if (!fs21.existsSync(skillsDir)) fs21.mkdirSync(skillsDir, { recursive: true });
|
|
5567
6021
|
for (const skill of docs.claudeSkills) {
|
|
5568
|
-
const filePath =
|
|
6022
|
+
const filePath = path17.join(skillsDir, skill.filename);
|
|
5569
6023
|
fs21.writeFileSync(filePath, skill.content);
|
|
5570
6024
|
written.push(filePath);
|
|
5571
6025
|
}
|
|
@@ -5644,8 +6098,8 @@ function discoverGitRepos(parentDir) {
|
|
|
5644
6098
|
const entries = fs22.readdirSync(parentDir, { withFileTypes: true });
|
|
5645
6099
|
for (const entry of entries) {
|
|
5646
6100
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
5647
|
-
const childPath =
|
|
5648
|
-
if (fs22.existsSync(
|
|
6101
|
+
const childPath = path18.join(parentDir, entry.name);
|
|
6102
|
+
if (fs22.existsSync(path18.join(childPath, ".git"))) {
|
|
5649
6103
|
repos.push(childPath);
|
|
5650
6104
|
}
|
|
5651
6105
|
}
|
|
@@ -5655,7 +6109,7 @@ function discoverGitRepos(parentDir) {
|
|
|
5655
6109
|
}
|
|
5656
6110
|
async function refreshSingleRepo(repoDir, options) {
|
|
5657
6111
|
const quiet = !!options.quiet;
|
|
5658
|
-
const prefix = options.label ? `${
|
|
6112
|
+
const prefix = options.label ? `${chalk11.bold(options.label)} ` : "";
|
|
5659
6113
|
const state = readState();
|
|
5660
6114
|
const lastSha = state?.lastRefreshSha ?? null;
|
|
5661
6115
|
const diff = collectDiff(lastSha);
|
|
@@ -5664,10 +6118,10 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
5664
6118
|
if (currentSha) {
|
|
5665
6119
|
writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
5666
6120
|
}
|
|
5667
|
-
log(quiet,
|
|
6121
|
+
log(quiet, chalk11.dim(`${prefix}No changes since last refresh.`));
|
|
5668
6122
|
return;
|
|
5669
6123
|
}
|
|
5670
|
-
const spinner = quiet ? null :
|
|
6124
|
+
const spinner = quiet ? null : ora6(`${prefix}Analyzing changes...`).start();
|
|
5671
6125
|
const existingDocs = readExistingConfigs(repoDir);
|
|
5672
6126
|
const fingerprint = collectFingerprint(repoDir);
|
|
5673
6127
|
const projectContext = {
|
|
@@ -5696,10 +6150,10 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
5696
6150
|
if (options.dryRun) {
|
|
5697
6151
|
spinner?.info(`${prefix}Dry run \u2014 would update:`);
|
|
5698
6152
|
for (const doc of response.docsUpdated) {
|
|
5699
|
-
console.log(` ${
|
|
6153
|
+
console.log(` ${chalk11.yellow("~")} ${doc}`);
|
|
5700
6154
|
}
|
|
5701
6155
|
if (response.changesSummary) {
|
|
5702
|
-
console.log(
|
|
6156
|
+
console.log(chalk11.dim(`
|
|
5703
6157
|
${response.changesSummary}`));
|
|
5704
6158
|
}
|
|
5705
6159
|
return;
|
|
@@ -5707,10 +6161,10 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
5707
6161
|
const written = writeRefreshDocs(response.updatedDocs);
|
|
5708
6162
|
spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
|
|
5709
6163
|
for (const file of written) {
|
|
5710
|
-
log(quiet, ` ${
|
|
6164
|
+
log(quiet, ` ${chalk11.green("\u2713")} ${file}`);
|
|
5711
6165
|
}
|
|
5712
6166
|
if (response.changesSummary) {
|
|
5713
|
-
log(quiet,
|
|
6167
|
+
log(quiet, chalk11.dim(`
|
|
5714
6168
|
${response.changesSummary}`));
|
|
5715
6169
|
}
|
|
5716
6170
|
if (currentSha) {
|
|
@@ -5723,7 +6177,7 @@ async function refreshCommand(options) {
|
|
|
5723
6177
|
const config = loadConfig();
|
|
5724
6178
|
if (!config) {
|
|
5725
6179
|
if (quiet) return;
|
|
5726
|
-
console.log(
|
|
6180
|
+
console.log(chalk11.red("No LLM provider configured. Run ") + chalk11.hex("#83D1EB")("caliber config") + chalk11.red(" (e.g. choose Cursor) or set an API key."));
|
|
5727
6181
|
throw new Error("__exit__");
|
|
5728
6182
|
}
|
|
5729
6183
|
if (isGitRepo()) {
|
|
@@ -5733,20 +6187,20 @@ async function refreshCommand(options) {
|
|
|
5733
6187
|
const repos = discoverGitRepos(process.cwd());
|
|
5734
6188
|
if (repos.length === 0) {
|
|
5735
6189
|
if (quiet) return;
|
|
5736
|
-
console.log(
|
|
6190
|
+
console.log(chalk11.red("Not inside a git repository and no git repos found in child directories."));
|
|
5737
6191
|
throw new Error("__exit__");
|
|
5738
6192
|
}
|
|
5739
|
-
log(quiet,
|
|
6193
|
+
log(quiet, chalk11.dim(`Found ${repos.length} git repo${repos.length === 1 ? "" : "s"}
|
|
5740
6194
|
`));
|
|
5741
6195
|
const originalDir = process.cwd();
|
|
5742
6196
|
for (const repo of repos) {
|
|
5743
|
-
const repoName =
|
|
6197
|
+
const repoName = path18.basename(repo);
|
|
5744
6198
|
try {
|
|
5745
6199
|
process.chdir(repo);
|
|
5746
6200
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
5747
6201
|
} catch (err) {
|
|
5748
6202
|
if (err instanceof Error && err.message === "__exit__") continue;
|
|
5749
|
-
log(quiet,
|
|
6203
|
+
log(quiet, chalk11.yellow(`${repoName}: refresh failed \u2014 ${err instanceof Error ? err.message : "unknown error"}`));
|
|
5750
6204
|
}
|
|
5751
6205
|
}
|
|
5752
6206
|
process.chdir(originalDir);
|
|
@@ -5754,13 +6208,13 @@ async function refreshCommand(options) {
|
|
|
5754
6208
|
if (err instanceof Error && err.message === "__exit__") throw err;
|
|
5755
6209
|
if (quiet) return;
|
|
5756
6210
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
5757
|
-
console.log(
|
|
6211
|
+
console.log(chalk11.red(`Refresh failed: ${msg}`));
|
|
5758
6212
|
throw new Error("__exit__");
|
|
5759
6213
|
}
|
|
5760
6214
|
}
|
|
5761
6215
|
|
|
5762
6216
|
// src/commands/hooks.ts
|
|
5763
|
-
import
|
|
6217
|
+
import chalk12 from "chalk";
|
|
5764
6218
|
var HOOKS = [
|
|
5765
6219
|
{
|
|
5766
6220
|
id: "session-end",
|
|
@@ -5780,13 +6234,13 @@ var HOOKS = [
|
|
|
5780
6234
|
}
|
|
5781
6235
|
];
|
|
5782
6236
|
function printStatus() {
|
|
5783
|
-
console.log(
|
|
6237
|
+
console.log(chalk12.bold("\n Hooks\n"));
|
|
5784
6238
|
for (const hook of HOOKS) {
|
|
5785
6239
|
const installed = hook.isInstalled();
|
|
5786
|
-
const icon = installed ?
|
|
5787
|
-
const state = installed ?
|
|
6240
|
+
const icon = installed ? chalk12.green("\u2713") : chalk12.dim("\u2717");
|
|
6241
|
+
const state = installed ? chalk12.green("enabled") : chalk12.dim("disabled");
|
|
5788
6242
|
console.log(` ${icon} ${hook.label.padEnd(26)} ${state}`);
|
|
5789
|
-
console.log(
|
|
6243
|
+
console.log(chalk12.dim(` ${hook.description}`));
|
|
5790
6244
|
}
|
|
5791
6245
|
console.log("");
|
|
5792
6246
|
}
|
|
@@ -5795,9 +6249,9 @@ async function hooksCommand(options) {
|
|
|
5795
6249
|
for (const hook of HOOKS) {
|
|
5796
6250
|
const result = hook.install();
|
|
5797
6251
|
if (result.alreadyInstalled) {
|
|
5798
|
-
console.log(
|
|
6252
|
+
console.log(chalk12.dim(` ${hook.label} already enabled.`));
|
|
5799
6253
|
} else {
|
|
5800
|
-
console.log(
|
|
6254
|
+
console.log(chalk12.green(" \u2713") + ` ${hook.label} enabled`);
|
|
5801
6255
|
}
|
|
5802
6256
|
}
|
|
5803
6257
|
return;
|
|
@@ -5806,9 +6260,9 @@ async function hooksCommand(options) {
|
|
|
5806
6260
|
for (const hook of HOOKS) {
|
|
5807
6261
|
const result = hook.remove();
|
|
5808
6262
|
if (result.notFound) {
|
|
5809
|
-
console.log(
|
|
6263
|
+
console.log(chalk12.dim(` ${hook.label} already disabled.`));
|
|
5810
6264
|
} else {
|
|
5811
|
-
console.log(
|
|
6265
|
+
console.log(chalk12.green(" \u2713") + ` ${hook.label} removed`);
|
|
5812
6266
|
}
|
|
5813
6267
|
}
|
|
5814
6268
|
return;
|
|
@@ -5823,18 +6277,18 @@ async function hooksCommand(options) {
|
|
|
5823
6277
|
const states = HOOKS.map((h) => h.isInstalled());
|
|
5824
6278
|
function render() {
|
|
5825
6279
|
const lines = [];
|
|
5826
|
-
lines.push(
|
|
6280
|
+
lines.push(chalk12.bold(" Hooks"));
|
|
5827
6281
|
lines.push("");
|
|
5828
6282
|
for (let i = 0; i < HOOKS.length; i++) {
|
|
5829
6283
|
const hook = HOOKS[i];
|
|
5830
6284
|
const enabled = states[i];
|
|
5831
|
-
const toggle = enabled ?
|
|
5832
|
-
const ptr = i === cursor ?
|
|
6285
|
+
const toggle = enabled ? chalk12.green("[on] ") : chalk12.dim("[off]");
|
|
6286
|
+
const ptr = i === cursor ? chalk12.cyan(">") : " ";
|
|
5833
6287
|
lines.push(` ${ptr} ${toggle} ${hook.label}`);
|
|
5834
|
-
lines.push(
|
|
6288
|
+
lines.push(chalk12.dim(` ${hook.description}`));
|
|
5835
6289
|
}
|
|
5836
6290
|
lines.push("");
|
|
5837
|
-
lines.push(
|
|
6291
|
+
lines.push(chalk12.dim(" \u2191\u2193 navigate \u23B5 toggle a all on n all off \u23CE apply q cancel"));
|
|
5838
6292
|
return lines.join("\n");
|
|
5839
6293
|
}
|
|
5840
6294
|
function draw(initial) {
|
|
@@ -5865,16 +6319,16 @@ async function hooksCommand(options) {
|
|
|
5865
6319
|
const wantEnabled = states[i];
|
|
5866
6320
|
if (wantEnabled && !wasInstalled) {
|
|
5867
6321
|
hook.install();
|
|
5868
|
-
console.log(
|
|
6322
|
+
console.log(chalk12.green(" \u2713") + ` ${hook.label} enabled`);
|
|
5869
6323
|
changed++;
|
|
5870
6324
|
} else if (!wantEnabled && wasInstalled) {
|
|
5871
6325
|
hook.remove();
|
|
5872
|
-
console.log(
|
|
6326
|
+
console.log(chalk12.green(" \u2713") + ` ${hook.label} disabled`);
|
|
5873
6327
|
changed++;
|
|
5874
6328
|
}
|
|
5875
6329
|
}
|
|
5876
6330
|
if (changed === 0) {
|
|
5877
|
-
console.log(
|
|
6331
|
+
console.log(chalk12.dim(" No changes."));
|
|
5878
6332
|
}
|
|
5879
6333
|
console.log("");
|
|
5880
6334
|
}
|
|
@@ -5910,7 +6364,7 @@ async function hooksCommand(options) {
|
|
|
5910
6364
|
case "\x1B":
|
|
5911
6365
|
case "":
|
|
5912
6366
|
cleanup();
|
|
5913
|
-
console.log(
|
|
6367
|
+
console.log(chalk12.dim("\n Cancelled.\n"));
|
|
5914
6368
|
resolve2();
|
|
5915
6369
|
break;
|
|
5916
6370
|
}
|
|
@@ -5920,43 +6374,43 @@ async function hooksCommand(options) {
|
|
|
5920
6374
|
}
|
|
5921
6375
|
|
|
5922
6376
|
// src/commands/config.ts
|
|
5923
|
-
import
|
|
6377
|
+
import chalk13 from "chalk";
|
|
5924
6378
|
async function configCommand() {
|
|
5925
6379
|
const existing = loadConfig();
|
|
5926
6380
|
if (existing) {
|
|
5927
|
-
console.log(
|
|
5928
|
-
console.log(` Provider: ${
|
|
5929
|
-
console.log(` Model: ${
|
|
6381
|
+
console.log(chalk13.bold("\nCurrent Configuration\n"));
|
|
6382
|
+
console.log(` Provider: ${chalk13.cyan(existing.provider)}`);
|
|
6383
|
+
console.log(` Model: ${chalk13.cyan(existing.model)}`);
|
|
5930
6384
|
if (existing.apiKey) {
|
|
5931
6385
|
const masked = existing.apiKey.slice(0, 8) + "..." + existing.apiKey.slice(-4);
|
|
5932
|
-
console.log(` API Key: ${
|
|
6386
|
+
console.log(` API Key: ${chalk13.dim(masked)}`);
|
|
5933
6387
|
}
|
|
5934
6388
|
if (existing.provider === "cursor") {
|
|
5935
|
-
console.log(` Seat: ${
|
|
6389
|
+
console.log(` Seat: ${chalk13.dim("Cursor (agent acp)")}`);
|
|
5936
6390
|
}
|
|
5937
6391
|
if (existing.provider === "claude-cli") {
|
|
5938
|
-
console.log(` Seat: ${
|
|
6392
|
+
console.log(` Seat: ${chalk13.dim("Claude Code (claude -p)")}`);
|
|
5939
6393
|
}
|
|
5940
6394
|
if (existing.baseUrl) {
|
|
5941
|
-
console.log(` Base URL: ${
|
|
6395
|
+
console.log(` Base URL: ${chalk13.dim(existing.baseUrl)}`);
|
|
5942
6396
|
}
|
|
5943
6397
|
if (existing.vertexProjectId) {
|
|
5944
|
-
console.log(` Vertex Project: ${
|
|
5945
|
-
console.log(` Vertex Region: ${
|
|
6398
|
+
console.log(` Vertex Project: ${chalk13.dim(existing.vertexProjectId)}`);
|
|
6399
|
+
console.log(` Vertex Region: ${chalk13.dim(existing.vertexRegion || "us-east5")}`);
|
|
5946
6400
|
}
|
|
5947
|
-
console.log(` Source: ${
|
|
6401
|
+
console.log(` Source: ${chalk13.dim(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.VERTEX_PROJECT_ID || process.env.CALIBER_USE_CURSOR_SEAT || process.env.CALIBER_USE_CLAUDE_CLI ? "environment variables" : getConfigFilePath())}`);
|
|
5948
6402
|
console.log("");
|
|
5949
6403
|
}
|
|
5950
6404
|
await runInteractiveProviderSetup();
|
|
5951
|
-
console.log(
|
|
5952
|
-
console.log(
|
|
6405
|
+
console.log(chalk13.green("\n\u2713 Configuration saved"));
|
|
6406
|
+
console.log(chalk13.dim(` ${getConfigFilePath()}
|
|
5953
6407
|
`));
|
|
5954
|
-
console.log(
|
|
5955
|
-
console.log(
|
|
6408
|
+
console.log(chalk13.dim(" You can also set environment variables instead:"));
|
|
6409
|
+
console.log(chalk13.dim(" ANTHROPIC_API_KEY, OPENAI_API_KEY, VERTEX_PROJECT_ID, CALIBER_USE_CURSOR_SEAT=1, or CALIBER_USE_CLAUDE_CLI=1\n"));
|
|
5956
6410
|
}
|
|
5957
6411
|
|
|
5958
6412
|
// src/commands/learn.ts
|
|
5959
|
-
import
|
|
6413
|
+
import chalk14 from "chalk";
|
|
5960
6414
|
|
|
5961
6415
|
// src/learner/stdin.ts
|
|
5962
6416
|
var STDIN_TIMEOUT_MS = 5e3;
|
|
@@ -5988,7 +6442,7 @@ function readStdin() {
|
|
|
5988
6442
|
// src/learner/storage.ts
|
|
5989
6443
|
init_constants();
|
|
5990
6444
|
import fs23 from "fs";
|
|
5991
|
-
import
|
|
6445
|
+
import path19 from "path";
|
|
5992
6446
|
var MAX_RESPONSE_LENGTH = 2e3;
|
|
5993
6447
|
var DEFAULT_STATE = {
|
|
5994
6448
|
sessionId: null,
|
|
@@ -6001,10 +6455,10 @@ function ensureLearningDir() {
|
|
|
6001
6455
|
}
|
|
6002
6456
|
}
|
|
6003
6457
|
function sessionFilePath() {
|
|
6004
|
-
return
|
|
6458
|
+
return path19.join(LEARNING_DIR, LEARNING_SESSION_FILE);
|
|
6005
6459
|
}
|
|
6006
6460
|
function stateFilePath() {
|
|
6007
|
-
return
|
|
6461
|
+
return path19.join(LEARNING_DIR, LEARNING_STATE_FILE);
|
|
6008
6462
|
}
|
|
6009
6463
|
function truncateResponse(response) {
|
|
6010
6464
|
const str = JSON.stringify(response);
|
|
@@ -6058,7 +6512,7 @@ function resetState() {
|
|
|
6058
6512
|
|
|
6059
6513
|
// src/learner/writer.ts
|
|
6060
6514
|
import fs24 from "fs";
|
|
6061
|
-
import
|
|
6515
|
+
import path20 from "path";
|
|
6062
6516
|
var LEARNED_START = "<!-- caliber:learned -->";
|
|
6063
6517
|
var LEARNED_END = "<!-- /caliber:learned -->";
|
|
6064
6518
|
function writeLearnedContent(update) {
|
|
@@ -6096,9 +6550,9 @@ ${LEARNED_END}`;
|
|
|
6096
6550
|
fs24.writeFileSync(claudeMdPath, updated);
|
|
6097
6551
|
}
|
|
6098
6552
|
function writeLearnedSkill(skill) {
|
|
6099
|
-
const skillDir =
|
|
6553
|
+
const skillDir = path20.join(".claude", "skills", skill.name);
|
|
6100
6554
|
if (!fs24.existsSync(skillDir)) fs24.mkdirSync(skillDir, { recursive: true });
|
|
6101
|
-
const skillPath =
|
|
6555
|
+
const skillPath = path20.join(skillDir, "SKILL.md");
|
|
6102
6556
|
if (!skill.isNew && fs24.existsSync(skillPath)) {
|
|
6103
6557
|
const existing = fs24.readFileSync(skillPath, "utf-8");
|
|
6104
6558
|
fs24.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
@@ -6257,53 +6711,53 @@ async function learnFinalizeCommand() {
|
|
|
6257
6711
|
async function learnInstallCommand() {
|
|
6258
6712
|
const result = installLearningHooks();
|
|
6259
6713
|
if (result.alreadyInstalled) {
|
|
6260
|
-
console.log(
|
|
6714
|
+
console.log(chalk14.dim("Learning hooks already installed."));
|
|
6261
6715
|
return;
|
|
6262
6716
|
}
|
|
6263
|
-
console.log(
|
|
6264
|
-
console.log(
|
|
6265
|
-
console.log(
|
|
6717
|
+
console.log(chalk14.green("\u2713") + " Learning hooks installed in .claude/settings.json");
|
|
6718
|
+
console.log(chalk14.dim(" PostToolUse, PostToolUseFailure, and SessionEnd hooks active."));
|
|
6719
|
+
console.log(chalk14.dim(" Session learnings will be written to CLAUDE.md and skills."));
|
|
6266
6720
|
}
|
|
6267
6721
|
async function learnRemoveCommand() {
|
|
6268
6722
|
const result = removeLearningHooks();
|
|
6269
6723
|
if (result.notFound) {
|
|
6270
|
-
console.log(
|
|
6724
|
+
console.log(chalk14.dim("Learning hooks not found."));
|
|
6271
6725
|
return;
|
|
6272
6726
|
}
|
|
6273
|
-
console.log(
|
|
6727
|
+
console.log(chalk14.green("\u2713") + " Learning hooks removed from .claude/settings.json");
|
|
6274
6728
|
}
|
|
6275
6729
|
async function learnStatusCommand() {
|
|
6276
6730
|
const installed = areLearningHooksInstalled();
|
|
6277
6731
|
const state = readState2();
|
|
6278
6732
|
const eventCount = getEventCount();
|
|
6279
|
-
console.log(
|
|
6733
|
+
console.log(chalk14.bold("Session Learning Status"));
|
|
6280
6734
|
console.log();
|
|
6281
6735
|
if (installed) {
|
|
6282
|
-
console.log(
|
|
6736
|
+
console.log(chalk14.green("\u2713") + " Learning hooks are " + chalk14.green("installed"));
|
|
6283
6737
|
} else {
|
|
6284
|
-
console.log(
|
|
6285
|
-
console.log(
|
|
6738
|
+
console.log(chalk14.dim("\u2717") + " Learning hooks are " + chalk14.yellow("not installed"));
|
|
6739
|
+
console.log(chalk14.dim(" Run `caliber learn install` to enable session learning."));
|
|
6286
6740
|
}
|
|
6287
6741
|
console.log();
|
|
6288
|
-
console.log(`Events recorded: ${
|
|
6289
|
-
console.log(`Total this session: ${
|
|
6742
|
+
console.log(`Events recorded: ${chalk14.cyan(String(eventCount))}`);
|
|
6743
|
+
console.log(`Total this session: ${chalk14.cyan(String(state.eventCount))}`);
|
|
6290
6744
|
if (state.lastAnalysisTimestamp) {
|
|
6291
|
-
console.log(`Last analysis: ${
|
|
6745
|
+
console.log(`Last analysis: ${chalk14.cyan(state.lastAnalysisTimestamp)}`);
|
|
6292
6746
|
} else {
|
|
6293
|
-
console.log(`Last analysis: ${
|
|
6747
|
+
console.log(`Last analysis: ${chalk14.dim("none")}`);
|
|
6294
6748
|
}
|
|
6295
6749
|
const learnedSection = readLearnedSection();
|
|
6296
6750
|
if (learnedSection) {
|
|
6297
6751
|
const lineCount = learnedSection.split("\n").filter(Boolean).length;
|
|
6298
6752
|
console.log(`
|
|
6299
|
-
Learned items in CLAUDE.md: ${
|
|
6753
|
+
Learned items in CLAUDE.md: ${chalk14.cyan(String(lineCount))}`);
|
|
6300
6754
|
}
|
|
6301
6755
|
}
|
|
6302
6756
|
|
|
6303
6757
|
// src/cli.ts
|
|
6304
|
-
var __dirname =
|
|
6758
|
+
var __dirname = path21.dirname(fileURLToPath(import.meta.url));
|
|
6305
6759
|
var pkg = JSON.parse(
|
|
6306
|
-
fs25.readFileSync(
|
|
6760
|
+
fs25.readFileSync(path21.resolve(__dirname, "..", "package.json"), "utf-8")
|
|
6307
6761
|
);
|
|
6308
6762
|
var program = new Command();
|
|
6309
6763
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
|
|
@@ -6326,20 +6780,20 @@ learn.command("status").description("Show learning system status").action(learnS
|
|
|
6326
6780
|
|
|
6327
6781
|
// src/utils/version-check.ts
|
|
6328
6782
|
import fs26 from "fs";
|
|
6329
|
-
import
|
|
6783
|
+
import path22 from "path";
|
|
6330
6784
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6331
6785
|
import { execSync as execSync9 } from "child_process";
|
|
6332
|
-
import
|
|
6333
|
-
import
|
|
6786
|
+
import chalk15 from "chalk";
|
|
6787
|
+
import ora7 from "ora";
|
|
6334
6788
|
import confirm2 from "@inquirer/confirm";
|
|
6335
|
-
var __dirname_vc =
|
|
6789
|
+
var __dirname_vc = path22.dirname(fileURLToPath2(import.meta.url));
|
|
6336
6790
|
var pkg2 = JSON.parse(
|
|
6337
|
-
fs26.readFileSync(
|
|
6791
|
+
fs26.readFileSync(path22.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
6338
6792
|
);
|
|
6339
6793
|
function getInstalledVersion() {
|
|
6340
6794
|
try {
|
|
6341
6795
|
const globalRoot = execSync9("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
6342
|
-
const pkgPath =
|
|
6796
|
+
const pkgPath = path22.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
6343
6797
|
return JSON.parse(fs26.readFileSync(pkgPath, "utf-8")).version;
|
|
6344
6798
|
} catch {
|
|
6345
6799
|
return null;
|
|
@@ -6363,17 +6817,17 @@ async function checkForUpdates() {
|
|
|
6363
6817
|
const isInteractive = process.stdin.isTTY === true;
|
|
6364
6818
|
if (!isInteractive) {
|
|
6365
6819
|
console.log(
|
|
6366
|
-
|
|
6820
|
+
chalk15.yellow(
|
|
6367
6821
|
`
|
|
6368
6822
|
Update available: ${current} -> ${latest}
|
|
6369
|
-
Run ${
|
|
6823
|
+
Run ${chalk15.bold("npm install -g @rely-ai/caliber")} to upgrade.
|
|
6370
6824
|
`
|
|
6371
6825
|
)
|
|
6372
6826
|
);
|
|
6373
6827
|
return;
|
|
6374
6828
|
}
|
|
6375
6829
|
console.log(
|
|
6376
|
-
|
|
6830
|
+
chalk15.yellow(`
|
|
6377
6831
|
Update available: ${current} -> ${latest}`)
|
|
6378
6832
|
);
|
|
6379
6833
|
const shouldUpdate = await confirm2({ message: "Would you like to update now? (Y/n)", default: true });
|
|
@@ -6381,7 +6835,7 @@ Update available: ${current} -> ${latest}`)
|
|
|
6381
6835
|
console.log();
|
|
6382
6836
|
return;
|
|
6383
6837
|
}
|
|
6384
|
-
const spinner =
|
|
6838
|
+
const spinner = ora7("Updating caliber...").start();
|
|
6385
6839
|
try {
|
|
6386
6840
|
execSync9(`npm install -g @rely-ai/caliber@${latest}`, {
|
|
6387
6841
|
stdio: "pipe",
|
|
@@ -6391,13 +6845,13 @@ Update available: ${current} -> ${latest}`)
|
|
|
6391
6845
|
const installed = getInstalledVersion();
|
|
6392
6846
|
if (installed !== latest) {
|
|
6393
6847
|
spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
|
|
6394
|
-
console.log(
|
|
6848
|
+
console.log(chalk15.yellow(`Run ${chalk15.bold(`npm install -g @rely-ai/caliber@${latest}`)} manually.
|
|
6395
6849
|
`));
|
|
6396
6850
|
return;
|
|
6397
6851
|
}
|
|
6398
|
-
spinner.succeed(
|
|
6852
|
+
spinner.succeed(chalk15.green(`Updated to ${latest}`));
|
|
6399
6853
|
const args = process.argv.slice(2);
|
|
6400
|
-
console.log(
|
|
6854
|
+
console.log(chalk15.dim(`
|
|
6401
6855
|
Restarting: caliber ${args.join(" ")}
|
|
6402
6856
|
`));
|
|
6403
6857
|
execSync9(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
@@ -6410,11 +6864,11 @@ Restarting: caliber ${args.join(" ")}
|
|
|
6410
6864
|
if (err instanceof Error) {
|
|
6411
6865
|
const stderr = err.stderr;
|
|
6412
6866
|
const errMsg = stderr ? String(stderr).trim().split("\n").pop() : err.message.split("\n")[0];
|
|
6413
|
-
if (errMsg && !errMsg.includes("SIGTERM")) console.log(
|
|
6867
|
+
if (errMsg && !errMsg.includes("SIGTERM")) console.log(chalk15.dim(` ${errMsg}`));
|
|
6414
6868
|
}
|
|
6415
6869
|
console.log(
|
|
6416
|
-
|
|
6417
|
-
`Run ${
|
|
6870
|
+
chalk15.yellow(
|
|
6871
|
+
`Run ${chalk15.bold(`npm install -g @rely-ai/caliber@${latest}`)} manually to upgrade.
|
|
6418
6872
|
`
|
|
6419
6873
|
)
|
|
6420
6874
|
);
|