@rely-ai/caliber 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin.js +1743 -1681
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -51,17 +51,17 @@ var init_constants = __esm({
|
|
|
51
51
|
|
|
52
52
|
// src/cli.ts
|
|
53
53
|
import { Command } from "commander";
|
|
54
|
-
import
|
|
54
|
+
import fs28 from "fs";
|
|
55
55
|
import path22 from "path";
|
|
56
56
|
import { fileURLToPath } from "url";
|
|
57
57
|
|
|
58
58
|
// src/commands/onboard.ts
|
|
59
|
-
import
|
|
60
|
-
import
|
|
59
|
+
import chalk7 from "chalk";
|
|
60
|
+
import ora3 from "ora";
|
|
61
61
|
import readline4 from "readline";
|
|
62
|
-
import
|
|
62
|
+
import select4 from "@inquirer/select";
|
|
63
63
|
import checkbox from "@inquirer/checkbox";
|
|
64
|
-
import
|
|
64
|
+
import fs22 from "fs";
|
|
65
65
|
|
|
66
66
|
// src/fingerprint/index.ts
|
|
67
67
|
import fs6 from "fs";
|
|
@@ -1242,11 +1242,23 @@ AgentSetup schema:
|
|
|
1242
1242
|
|
|
1243
1243
|
Do NOT generate mcpServers \u2014 MCP configuration is managed separately.
|
|
1244
1244
|
|
|
1245
|
-
All skills follow the OpenSkills standard (agentskills.io):
|
|
1246
|
-
-
|
|
1247
|
-
-
|
|
1248
|
-
-
|
|
1249
|
-
|
|
1245
|
+
All skills follow the OpenSkills standard (agentskills.io). Anthropic's official skill guide defines three levels of progressive disclosure:
|
|
1246
|
+
- Level 1 (YAML frontmatter): Always loaded. Must have enough info for the agent to decide when to activate the skill.
|
|
1247
|
+
- Level 2 (SKILL.md body): Loaded when the skill is relevant. Contains full instructions.
|
|
1248
|
+
- Level 3 (references/): Only loaded on demand for deep detail.
|
|
1249
|
+
|
|
1250
|
+
Skill field requirements:
|
|
1251
|
+
- "name": kebab-case (lowercase letters, numbers, hyphens only). Becomes the directory name.
|
|
1252
|
+
- "description": MUST include WHAT it does + WHEN to use it with specific trigger phrases. Example: "Manages database migrations. Use when user says 'run migration', 'create migration', 'db schema change', or modifies files in db/migrations/."
|
|
1253
|
+
- "content": markdown body only \u2014 do NOT include YAML frontmatter, it is generated from name+description.
|
|
1254
|
+
|
|
1255
|
+
Skill content structure \u2014 follow this template:
|
|
1256
|
+
1. A heading with the skill name
|
|
1257
|
+
2. "## Instructions" \u2014 clear, numbered steps. Be specific: include exact commands, file paths, parameter names.
|
|
1258
|
+
3. "## Examples" \u2014 at least one example showing: User says \u2192 Actions taken \u2192 Result
|
|
1259
|
+
4. "## Troubleshooting" (optional) \u2014 common errors and how to fix them
|
|
1260
|
+
|
|
1261
|
+
Keep skill content under 200 lines. Focus on actionable instructions, not documentation prose.
|
|
1250
1262
|
|
|
1251
1263
|
The "fileDescriptions" object MUST include a one-liner for every file that will be created or modified. Use actual file paths as keys (e.g. "CLAUDE.md", "AGENTS.md", ".claude/skills/my-skill/SKILL.md", ".agents/skills/my-skill/SKILL.md", ".cursor/skills/my-skill/SKILL.md", ".cursor/rules/my-rule.mdc"). Each description should explain why the change is needed, be concise and lowercase.
|
|
1252
1264
|
|
|
@@ -2163,6 +2175,12 @@ function cleanupStaging() {
|
|
|
2163
2175
|
}
|
|
2164
2176
|
}
|
|
2165
2177
|
|
|
2178
|
+
// src/utils/review.ts
|
|
2179
|
+
import chalk from "chalk";
|
|
2180
|
+
import fs14 from "fs";
|
|
2181
|
+
import select from "@inquirer/select";
|
|
2182
|
+
import { createTwoFilesPatch } from "diff";
|
|
2183
|
+
|
|
2166
2184
|
// src/utils/editor.ts
|
|
2167
2185
|
import { execSync as execSync4, spawn as spawn3 } from "child_process";
|
|
2168
2186
|
function commandExists(cmd) {
|
|
@@ -2201,11 +2219,206 @@ function openDiffsInEditor(editor, files) {
|
|
|
2201
2219
|
}
|
|
2202
2220
|
}
|
|
2203
2221
|
|
|
2204
|
-
// src/
|
|
2205
|
-
|
|
2222
|
+
// src/utils/review.ts
|
|
2223
|
+
async function promptWantsReview() {
|
|
2224
|
+
return select({
|
|
2225
|
+
message: "Would you like to review the diffs before deciding?",
|
|
2226
|
+
choices: [
|
|
2227
|
+
{ name: "Yes, show me the diffs", value: true },
|
|
2228
|
+
{ name: "No, continue", value: false }
|
|
2229
|
+
]
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
async function promptReviewMethod() {
|
|
2233
|
+
const available = detectAvailableEditors();
|
|
2234
|
+
if (available.length === 1) return "terminal";
|
|
2235
|
+
const choices = available.map((method) => {
|
|
2236
|
+
switch (method) {
|
|
2237
|
+
case "cursor":
|
|
2238
|
+
return { name: "Cursor (diff view)", value: "cursor" };
|
|
2239
|
+
case "vscode":
|
|
2240
|
+
return { name: "VS Code (diff view)", value: "vscode" };
|
|
2241
|
+
case "terminal":
|
|
2242
|
+
return { name: "Terminal", value: "terminal" };
|
|
2243
|
+
}
|
|
2244
|
+
});
|
|
2245
|
+
return select({ message: "How would you like to review the changes?", choices });
|
|
2246
|
+
}
|
|
2247
|
+
async function openReview(method, stagedFiles) {
|
|
2248
|
+
if (method === "cursor" || method === "vscode") {
|
|
2249
|
+
openDiffsInEditor(method, stagedFiles.map((f) => ({
|
|
2250
|
+
originalPath: f.originalPath,
|
|
2251
|
+
proposedPath: f.proposedPath
|
|
2252
|
+
})));
|
|
2253
|
+
console.log(chalk.dim(" Diffs opened in your editor.\n"));
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
const fileInfos = stagedFiles.map((file) => {
|
|
2257
|
+
const proposed = fs14.readFileSync(file.proposedPath, "utf-8");
|
|
2258
|
+
const current = file.currentPath ? fs14.readFileSync(file.currentPath, "utf-8") : "";
|
|
2259
|
+
const patch = createTwoFilesPatch(
|
|
2260
|
+
file.isNew ? "/dev/null" : file.relativePath,
|
|
2261
|
+
file.relativePath,
|
|
2262
|
+
current,
|
|
2263
|
+
proposed
|
|
2264
|
+
);
|
|
2265
|
+
let added = 0, removed = 0;
|
|
2266
|
+
for (const line of patch.split("\n")) {
|
|
2267
|
+
if (line.startsWith("+") && !line.startsWith("+++")) added++;
|
|
2268
|
+
if (line.startsWith("-") && !line.startsWith("---")) removed++;
|
|
2269
|
+
}
|
|
2270
|
+
return {
|
|
2271
|
+
relativePath: file.relativePath,
|
|
2272
|
+
isNew: file.isNew,
|
|
2273
|
+
added,
|
|
2274
|
+
removed,
|
|
2275
|
+
lines: proposed.split("\n").length,
|
|
2276
|
+
patch
|
|
2277
|
+
};
|
|
2278
|
+
});
|
|
2279
|
+
await interactiveDiffExplorer(fileInfos);
|
|
2280
|
+
}
|
|
2281
|
+
async function interactiveDiffExplorer(files) {
|
|
2282
|
+
if (!process.stdin.isTTY) {
|
|
2283
|
+
for (const f of files) {
|
|
2284
|
+
const icon = f.isNew ? chalk.green("+") : chalk.yellow("~");
|
|
2285
|
+
const stats = f.isNew ? chalk.dim(`${f.lines} lines`) : `${chalk.green(`+${f.added}`)} ${chalk.red(`-${f.removed}`)}`;
|
|
2286
|
+
console.log(` ${icon} ${f.relativePath} ${stats}`);
|
|
2287
|
+
}
|
|
2288
|
+
console.log("");
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
const { stdin, stdout } = process;
|
|
2292
|
+
let cursor = 0;
|
|
2293
|
+
let viewing = null;
|
|
2294
|
+
let scrollOffset = 0;
|
|
2295
|
+
let lineCount = 0;
|
|
2296
|
+
function getTermHeight() {
|
|
2297
|
+
return (stdout.rows || 24) - 4;
|
|
2298
|
+
}
|
|
2299
|
+
function renderFileList() {
|
|
2300
|
+
const lines = [];
|
|
2301
|
+
lines.push(chalk.bold(" Review changes"));
|
|
2302
|
+
lines.push("");
|
|
2303
|
+
for (let i = 0; i < files.length; i++) {
|
|
2304
|
+
const f = files[i];
|
|
2305
|
+
const ptr = i === cursor ? chalk.cyan(">") : " ";
|
|
2306
|
+
const icon = f.isNew ? chalk.green("+") : chalk.yellow("~");
|
|
2307
|
+
const stats = f.isNew ? chalk.dim(`${f.lines} lines`) : `${chalk.green(`+${f.added}`)} ${chalk.red(`-${f.removed}`)}`;
|
|
2308
|
+
lines.push(` ${ptr} ${icon} ${f.relativePath} ${stats}`);
|
|
2309
|
+
}
|
|
2310
|
+
lines.push("");
|
|
2311
|
+
lines.push(chalk.dim(" \u2191\u2193 navigate \u23CE view diff q done"));
|
|
2312
|
+
return lines.join("\n");
|
|
2313
|
+
}
|
|
2314
|
+
function renderDiff(index) {
|
|
2315
|
+
const f = files[index];
|
|
2316
|
+
const lines = [];
|
|
2317
|
+
const header = f.isNew ? ` ${chalk.green("+")} ${f.relativePath} ${chalk.dim("(new file)")}` : ` ${chalk.yellow("~")} ${f.relativePath} ${chalk.green(`+${f.added}`)} ${chalk.red(`-${f.removed}`)}`;
|
|
2318
|
+
lines.push(header);
|
|
2319
|
+
lines.push(chalk.dim(" " + "\u2500".repeat(60)));
|
|
2320
|
+
const patchLines = f.patch.split("\n");
|
|
2321
|
+
const bodyLines = patchLines.slice(4);
|
|
2322
|
+
const maxVisible = getTermHeight() - 4;
|
|
2323
|
+
const visibleLines = bodyLines.slice(scrollOffset, scrollOffset + maxVisible);
|
|
2324
|
+
for (const line of visibleLines) {
|
|
2325
|
+
if (line.startsWith("+")) {
|
|
2326
|
+
lines.push(chalk.green(" " + line));
|
|
2327
|
+
} else if (line.startsWith("-")) {
|
|
2328
|
+
lines.push(chalk.red(" " + line));
|
|
2329
|
+
} else if (line.startsWith("@@")) {
|
|
2330
|
+
lines.push(chalk.cyan(" " + line));
|
|
2331
|
+
} else {
|
|
2332
|
+
lines.push(chalk.dim(" " + line));
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
const totalBody = bodyLines.length;
|
|
2336
|
+
if (totalBody > maxVisible) {
|
|
2337
|
+
const pct = Math.round((scrollOffset + maxVisible) / totalBody * 100);
|
|
2338
|
+
lines.push(chalk.dim(` \u2500\u2500 ${Math.min(pct, 100)}% \u2500\u2500`));
|
|
2339
|
+
}
|
|
2340
|
+
lines.push("");
|
|
2341
|
+
lines.push(chalk.dim(" \u2191\u2193 scroll \u23B5/esc back to file list"));
|
|
2342
|
+
return lines.join("\n");
|
|
2343
|
+
}
|
|
2344
|
+
function draw(initial) {
|
|
2345
|
+
if (!initial && lineCount > 0) {
|
|
2346
|
+
stdout.write(`\x1B[${lineCount}A`);
|
|
2347
|
+
}
|
|
2348
|
+
stdout.write("\x1B[0J");
|
|
2349
|
+
const output = viewing !== null ? renderDiff(viewing) : renderFileList();
|
|
2350
|
+
stdout.write(output + "\n");
|
|
2351
|
+
lineCount = output.split("\n").length;
|
|
2352
|
+
}
|
|
2353
|
+
return new Promise((resolve2) => {
|
|
2354
|
+
console.log("");
|
|
2355
|
+
draw(true);
|
|
2356
|
+
stdin.setRawMode(true);
|
|
2357
|
+
stdin.resume();
|
|
2358
|
+
stdin.setEncoding("utf8");
|
|
2359
|
+
function cleanup() {
|
|
2360
|
+
stdin.removeListener("data", onData);
|
|
2361
|
+
stdin.setRawMode(false);
|
|
2362
|
+
stdin.pause();
|
|
2363
|
+
}
|
|
2364
|
+
function onData(key) {
|
|
2365
|
+
if (viewing !== null) {
|
|
2366
|
+
const f = files[viewing];
|
|
2367
|
+
const totalBody = f.patch.split("\n").length - 4;
|
|
2368
|
+
const maxVisible = getTermHeight() - 4;
|
|
2369
|
+
switch (key) {
|
|
2370
|
+
case "\x1B[A":
|
|
2371
|
+
scrollOffset = Math.max(0, scrollOffset - 1);
|
|
2372
|
+
draw(false);
|
|
2373
|
+
break;
|
|
2374
|
+
case "\x1B[B":
|
|
2375
|
+
scrollOffset = Math.min(Math.max(0, totalBody - maxVisible), scrollOffset + 1);
|
|
2376
|
+
draw(false);
|
|
2377
|
+
break;
|
|
2378
|
+
case " ":
|
|
2379
|
+
case "\x1B":
|
|
2380
|
+
viewing = null;
|
|
2381
|
+
scrollOffset = 0;
|
|
2382
|
+
draw(false);
|
|
2383
|
+
break;
|
|
2384
|
+
case "q":
|
|
2385
|
+
case "":
|
|
2386
|
+
cleanup();
|
|
2387
|
+
console.log("");
|
|
2388
|
+
resolve2();
|
|
2389
|
+
break;
|
|
2390
|
+
}
|
|
2391
|
+
} else {
|
|
2392
|
+
switch (key) {
|
|
2393
|
+
case "\x1B[A":
|
|
2394
|
+
cursor = (cursor - 1 + files.length) % files.length;
|
|
2395
|
+
draw(false);
|
|
2396
|
+
break;
|
|
2397
|
+
case "\x1B[B":
|
|
2398
|
+
cursor = (cursor + 1) % files.length;
|
|
2399
|
+
draw(false);
|
|
2400
|
+
break;
|
|
2401
|
+
case "\r":
|
|
2402
|
+
case "\n":
|
|
2403
|
+
viewing = cursor;
|
|
2404
|
+
scrollOffset = 0;
|
|
2405
|
+
draw(false);
|
|
2406
|
+
break;
|
|
2407
|
+
case "q":
|
|
2408
|
+
case "":
|
|
2409
|
+
cleanup();
|
|
2410
|
+
console.log("");
|
|
2411
|
+
resolve2();
|
|
2412
|
+
break;
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
stdin.on("data", onData);
|
|
2417
|
+
});
|
|
2418
|
+
}
|
|
2206
2419
|
|
|
2207
2420
|
// src/commands/setup-files.ts
|
|
2208
|
-
import
|
|
2421
|
+
import fs15 from "fs";
|
|
2209
2422
|
function buildSkillContent(skill) {
|
|
2210
2423
|
const frontmatter = `---
|
|
2211
2424
|
name: ${skill.name}
|
|
@@ -2253,7 +2466,7 @@ function collectSetupFiles(setup) {
|
|
|
2253
2466
|
}
|
|
2254
2467
|
}
|
|
2255
2468
|
}
|
|
2256
|
-
if (!
|
|
2469
|
+
if (!fs15.existsSync("AGENTS.md") && !(codex && codex.agentsMd)) {
|
|
2257
2470
|
const agentRefs = [];
|
|
2258
2471
|
if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
|
|
2259
2472
|
if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
|
|
@@ -2272,24 +2485,24 @@ ${agentRefs.join(" ")}
|
|
|
2272
2485
|
}
|
|
2273
2486
|
|
|
2274
2487
|
// src/lib/hooks.ts
|
|
2275
|
-
import
|
|
2488
|
+
import fs16 from "fs";
|
|
2276
2489
|
import path12 from "path";
|
|
2277
2490
|
import { execSync as execSync5 } from "child_process";
|
|
2278
2491
|
var SETTINGS_PATH = path12.join(".claude", "settings.json");
|
|
2279
2492
|
var HOOK_COMMAND = "caliber refresh --quiet";
|
|
2280
2493
|
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
2281
2494
|
function readSettings() {
|
|
2282
|
-
if (!
|
|
2495
|
+
if (!fs16.existsSync(SETTINGS_PATH)) return {};
|
|
2283
2496
|
try {
|
|
2284
|
-
return JSON.parse(
|
|
2497
|
+
return JSON.parse(fs16.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
2285
2498
|
} catch {
|
|
2286
2499
|
return {};
|
|
2287
2500
|
}
|
|
2288
2501
|
}
|
|
2289
2502
|
function writeSettings(settings) {
|
|
2290
2503
|
const dir = path12.dirname(SETTINGS_PATH);
|
|
2291
|
-
if (!
|
|
2292
|
-
|
|
2504
|
+
if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
|
|
2505
|
+
fs16.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
2293
2506
|
}
|
|
2294
2507
|
function findHookIndex(sessionEnd) {
|
|
2295
2508
|
return sessionEnd.findIndex(
|
|
@@ -2358,8 +2571,8 @@ function getPreCommitPath() {
|
|
|
2358
2571
|
}
|
|
2359
2572
|
function isPreCommitHookInstalled() {
|
|
2360
2573
|
const hookPath = getPreCommitPath();
|
|
2361
|
-
if (!hookPath || !
|
|
2362
|
-
const content =
|
|
2574
|
+
if (!hookPath || !fs16.existsSync(hookPath)) return false;
|
|
2575
|
+
const content = fs16.readFileSync(hookPath, "utf-8");
|
|
2363
2576
|
return content.includes(PRECOMMIT_START);
|
|
2364
2577
|
}
|
|
2365
2578
|
function installPreCommitHook() {
|
|
@@ -2369,40 +2582,40 @@ function installPreCommitHook() {
|
|
|
2369
2582
|
const hookPath = getPreCommitPath();
|
|
2370
2583
|
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
2371
2584
|
const hooksDir = path12.dirname(hookPath);
|
|
2372
|
-
if (!
|
|
2585
|
+
if (!fs16.existsSync(hooksDir)) fs16.mkdirSync(hooksDir, { recursive: true });
|
|
2373
2586
|
let content = "";
|
|
2374
|
-
if (
|
|
2375
|
-
content =
|
|
2587
|
+
if (fs16.existsSync(hookPath)) {
|
|
2588
|
+
content = fs16.readFileSync(hookPath, "utf-8");
|
|
2376
2589
|
if (!content.endsWith("\n")) content += "\n";
|
|
2377
2590
|
content += "\n" + PRECOMMIT_BLOCK + "\n";
|
|
2378
2591
|
} else {
|
|
2379
2592
|
content = "#!/bin/sh\n\n" + PRECOMMIT_BLOCK + "\n";
|
|
2380
2593
|
}
|
|
2381
|
-
|
|
2382
|
-
|
|
2594
|
+
fs16.writeFileSync(hookPath, content);
|
|
2595
|
+
fs16.chmodSync(hookPath, 493);
|
|
2383
2596
|
return { installed: true, alreadyInstalled: false };
|
|
2384
2597
|
}
|
|
2385
2598
|
function removePreCommitHook() {
|
|
2386
2599
|
const hookPath = getPreCommitPath();
|
|
2387
|
-
if (!hookPath || !
|
|
2600
|
+
if (!hookPath || !fs16.existsSync(hookPath)) {
|
|
2388
2601
|
return { removed: false, notFound: true };
|
|
2389
2602
|
}
|
|
2390
|
-
let content =
|
|
2603
|
+
let content = fs16.readFileSync(hookPath, "utf-8");
|
|
2391
2604
|
if (!content.includes(PRECOMMIT_START)) {
|
|
2392
2605
|
return { removed: false, notFound: true };
|
|
2393
2606
|
}
|
|
2394
2607
|
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
2395
2608
|
content = content.replace(regex, "\n");
|
|
2396
2609
|
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
2397
|
-
|
|
2610
|
+
fs16.unlinkSync(hookPath);
|
|
2398
2611
|
} else {
|
|
2399
|
-
|
|
2612
|
+
fs16.writeFileSync(hookPath, content);
|
|
2400
2613
|
}
|
|
2401
2614
|
return { removed: true, notFound: false };
|
|
2402
2615
|
}
|
|
2403
2616
|
|
|
2404
2617
|
// src/lib/learning-hooks.ts
|
|
2405
|
-
import
|
|
2618
|
+
import fs17 from "fs";
|
|
2406
2619
|
import path13 from "path";
|
|
2407
2620
|
var SETTINGS_PATH2 = path13.join(".claude", "settings.json");
|
|
2408
2621
|
var HOOK_CONFIGS = [
|
|
@@ -2423,17 +2636,17 @@ var HOOK_CONFIGS = [
|
|
|
2423
2636
|
}
|
|
2424
2637
|
];
|
|
2425
2638
|
function readSettings2() {
|
|
2426
|
-
if (!
|
|
2639
|
+
if (!fs17.existsSync(SETTINGS_PATH2)) return {};
|
|
2427
2640
|
try {
|
|
2428
|
-
return JSON.parse(
|
|
2641
|
+
return JSON.parse(fs17.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
2429
2642
|
} catch {
|
|
2430
2643
|
return {};
|
|
2431
2644
|
}
|
|
2432
2645
|
}
|
|
2433
2646
|
function writeSettings2(settings) {
|
|
2434
2647
|
const dir = path13.dirname(SETTINGS_PATH2);
|
|
2435
|
-
if (!
|
|
2436
|
-
|
|
2648
|
+
if (!fs17.existsSync(dir)) fs17.mkdirSync(dir, { recursive: true });
|
|
2649
|
+
fs17.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
2437
2650
|
}
|
|
2438
2651
|
function hasLearningHook(matchers, command) {
|
|
2439
2652
|
return matchers.some((entry) => entry.hooks?.some((h) => h.command === command));
|
|
@@ -2490,7 +2703,7 @@ function removeLearningHooks() {
|
|
|
2490
2703
|
|
|
2491
2704
|
// src/lib/state.ts
|
|
2492
2705
|
init_constants();
|
|
2493
|
-
import
|
|
2706
|
+
import fs18 from "fs";
|
|
2494
2707
|
import path14 from "path";
|
|
2495
2708
|
import { execSync as execSync6 } from "child_process";
|
|
2496
2709
|
var STATE_FILE = path14.join(CALIBER_DIR, ".caliber-state.json");
|
|
@@ -2504,8 +2717,8 @@ function normalizeTargetAgent(value) {
|
|
|
2504
2717
|
}
|
|
2505
2718
|
function readState() {
|
|
2506
2719
|
try {
|
|
2507
|
-
if (!
|
|
2508
|
-
const raw = JSON.parse(
|
|
2720
|
+
if (!fs18.existsSync(STATE_FILE)) return null;
|
|
2721
|
+
const raw = JSON.parse(fs18.readFileSync(STATE_FILE, "utf-8"));
|
|
2509
2722
|
if (raw.targetAgent) raw.targetAgent = normalizeTargetAgent(raw.targetAgent);
|
|
2510
2723
|
return raw;
|
|
2511
2724
|
} catch {
|
|
@@ -2513,10 +2726,10 @@ function readState() {
|
|
|
2513
2726
|
}
|
|
2514
2727
|
}
|
|
2515
2728
|
function writeState(state) {
|
|
2516
|
-
if (!
|
|
2517
|
-
|
|
2729
|
+
if (!fs18.existsSync(CALIBER_DIR)) {
|
|
2730
|
+
fs18.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
2518
2731
|
}
|
|
2519
|
-
|
|
2732
|
+
fs18.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
2520
2733
|
}
|
|
2521
2734
|
function getCurrentHeadSha() {
|
|
2522
2735
|
try {
|
|
@@ -2530,7 +2743,7 @@ function getCurrentHeadSha() {
|
|
|
2530
2743
|
}
|
|
2531
2744
|
|
|
2532
2745
|
// src/utils/spinner-messages.ts
|
|
2533
|
-
import
|
|
2746
|
+
import chalk2 from "chalk";
|
|
2534
2747
|
var GENERATION_MESSAGES = [
|
|
2535
2748
|
"Analyzing your project structure and dependencies...",
|
|
2536
2749
|
"Mapping out build commands and test workflows...",
|
|
@@ -2580,9 +2793,9 @@ var SpinnerMessages = class {
|
|
|
2580
2793
|
this.currentBaseMessage = this.messages[0];
|
|
2581
2794
|
this.updateSpinnerText();
|
|
2582
2795
|
if (this.showElapsedTime) {
|
|
2583
|
-
this.spinner.suffixText =
|
|
2796
|
+
this.spinner.suffixText = chalk2.dim(`(${this.formatElapsed()})`);
|
|
2584
2797
|
this.elapsedTimer = setInterval(() => {
|
|
2585
|
-
this.spinner.suffixText =
|
|
2798
|
+
this.spinner.suffixText = chalk2.dim(`(${this.formatElapsed()})`);
|
|
2586
2799
|
}, 1e3);
|
|
2587
2800
|
}
|
|
2588
2801
|
this.timer = setInterval(() => {
|
|
@@ -2617,13 +2830,13 @@ var SpinnerMessages = class {
|
|
|
2617
2830
|
};
|
|
2618
2831
|
|
|
2619
2832
|
// src/commands/interactive-provider-setup.ts
|
|
2620
|
-
import
|
|
2833
|
+
import chalk3 from "chalk";
|
|
2621
2834
|
import readline2 from "readline";
|
|
2622
|
-
import
|
|
2835
|
+
import select2 from "@inquirer/select";
|
|
2623
2836
|
function promptInput(question) {
|
|
2624
2837
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
2625
2838
|
return new Promise((resolve2) => {
|
|
2626
|
-
rl.question(
|
|
2839
|
+
rl.question(chalk3.cyan(`${question} `), (answer) => {
|
|
2627
2840
|
rl.close();
|
|
2628
2841
|
resolve2(answer.trim());
|
|
2629
2842
|
});
|
|
@@ -2638,7 +2851,7 @@ var PROVIDER_CHOICES = [
|
|
|
2638
2851
|
];
|
|
2639
2852
|
async function runInteractiveProviderSetup(options) {
|
|
2640
2853
|
const message = options?.selectMessage ?? "Select LLM provider";
|
|
2641
|
-
const provider = await
|
|
2854
|
+
const provider = await select2({
|
|
2642
2855
|
message,
|
|
2643
2856
|
choices: PROVIDER_CHOICES
|
|
2644
2857
|
});
|
|
@@ -2646,19 +2859,19 @@ async function runInteractiveProviderSetup(options) {
|
|
|
2646
2859
|
switch (provider) {
|
|
2647
2860
|
case "claude-cli": {
|
|
2648
2861
|
config.model = "default";
|
|
2649
|
-
console.log(
|
|
2862
|
+
console.log(chalk3.dim(" Run `claude` once and log in with your Pro/Max/Team account if you haven't."));
|
|
2650
2863
|
break;
|
|
2651
2864
|
}
|
|
2652
2865
|
case "cursor": {
|
|
2653
2866
|
config.model = "default";
|
|
2654
|
-
console.log(
|
|
2867
|
+
console.log(chalk3.dim(" Run `agent login` if you haven't, or set CURSOR_API_KEY."));
|
|
2655
2868
|
break;
|
|
2656
2869
|
}
|
|
2657
2870
|
case "anthropic": {
|
|
2658
|
-
console.log(
|
|
2871
|
+
console.log(chalk3.dim(" Get a key at https://console.anthropic.com (same account as Claude Pro/Team/Max)."));
|
|
2659
2872
|
config.apiKey = await promptInput("Anthropic API key:");
|
|
2660
2873
|
if (!config.apiKey) {
|
|
2661
|
-
console.log(
|
|
2874
|
+
console.log(chalk3.red("API key is required."));
|
|
2662
2875
|
throw new Error("__exit__");
|
|
2663
2876
|
}
|
|
2664
2877
|
config.model = await promptInput(`Model (default: ${DEFAULT_MODELS.anthropic}):`) || DEFAULT_MODELS.anthropic;
|
|
@@ -2667,7 +2880,7 @@ async function runInteractiveProviderSetup(options) {
|
|
|
2667
2880
|
case "vertex": {
|
|
2668
2881
|
config.vertexProjectId = await promptInput("GCP Project ID:");
|
|
2669
2882
|
if (!config.vertexProjectId) {
|
|
2670
|
-
console.log(
|
|
2883
|
+
console.log(chalk3.red("Project ID is required."));
|
|
2671
2884
|
throw new Error("__exit__");
|
|
2672
2885
|
}
|
|
2673
2886
|
config.vertexRegion = await promptInput("Region (default: us-east5):") || "us-east5";
|
|
@@ -2678,7 +2891,7 @@ async function runInteractiveProviderSetup(options) {
|
|
|
2678
2891
|
case "openai": {
|
|
2679
2892
|
config.apiKey = await promptInput("API key:");
|
|
2680
2893
|
if (!config.apiKey) {
|
|
2681
|
-
console.log(
|
|
2894
|
+
console.log(chalk3.red("API key is required."));
|
|
2682
2895
|
throw new Error("__exit__");
|
|
2683
2896
|
}
|
|
2684
2897
|
config.baseUrl = await promptInput("Base URL (leave empty for OpenAI, or enter custom endpoint):") || void 0;
|
|
@@ -3737,22 +3950,22 @@ function checkBonus(dir) {
|
|
|
3737
3950
|
|
|
3738
3951
|
// src/scoring/dismissed.ts
|
|
3739
3952
|
init_constants();
|
|
3740
|
-
import
|
|
3953
|
+
import fs19 from "fs";
|
|
3741
3954
|
import path15 from "path";
|
|
3742
3955
|
var DISMISSED_FILE = path15.join(CALIBER_DIR, "dismissed-checks.json");
|
|
3743
3956
|
function readDismissedChecks() {
|
|
3744
3957
|
try {
|
|
3745
|
-
if (!
|
|
3746
|
-
return JSON.parse(
|
|
3958
|
+
if (!fs19.existsSync(DISMISSED_FILE)) return [];
|
|
3959
|
+
return JSON.parse(fs19.readFileSync(DISMISSED_FILE, "utf-8"));
|
|
3747
3960
|
} catch {
|
|
3748
3961
|
return [];
|
|
3749
3962
|
}
|
|
3750
3963
|
}
|
|
3751
3964
|
function writeDismissedChecks(checks) {
|
|
3752
|
-
if (!
|
|
3753
|
-
|
|
3965
|
+
if (!fs19.existsSync(CALIBER_DIR)) {
|
|
3966
|
+
fs19.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
3754
3967
|
}
|
|
3755
|
-
|
|
3968
|
+
fs19.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
|
|
3756
3969
|
}
|
|
3757
3970
|
function getDismissedIds() {
|
|
3758
3971
|
return new Set(readDismissedChecks().map((c) => c.id));
|
|
@@ -3816,7 +4029,7 @@ function computeLocalScore(dir, targetAgent) {
|
|
|
3816
4029
|
}
|
|
3817
4030
|
|
|
3818
4031
|
// src/scoring/display.ts
|
|
3819
|
-
import
|
|
4032
|
+
import chalk4 from "chalk";
|
|
3820
4033
|
var AGENT_DISPLAY_NAMES = {
|
|
3821
4034
|
claude: "Claude Code",
|
|
3822
4035
|
cursor: "Cursor",
|
|
@@ -3834,31 +4047,31 @@ var CATEGORY_ORDER = ["existence", "quality", "coverage", "accuracy", "freshness
|
|
|
3834
4047
|
function gradeColor(grade) {
|
|
3835
4048
|
switch (grade) {
|
|
3836
4049
|
case "A":
|
|
3837
|
-
return
|
|
4050
|
+
return chalk4.green;
|
|
3838
4051
|
case "B":
|
|
3839
|
-
return
|
|
4052
|
+
return chalk4.greenBright;
|
|
3840
4053
|
case "C":
|
|
3841
|
-
return
|
|
4054
|
+
return chalk4.yellow;
|
|
3842
4055
|
case "D":
|
|
3843
|
-
return
|
|
4056
|
+
return chalk4.hex("#f97316");
|
|
3844
4057
|
case "F":
|
|
3845
|
-
return
|
|
4058
|
+
return chalk4.red;
|
|
3846
4059
|
default:
|
|
3847
|
-
return
|
|
4060
|
+
return chalk4.white;
|
|
3848
4061
|
}
|
|
3849
4062
|
}
|
|
3850
4063
|
function progressBar(score, max, width = 40) {
|
|
3851
4064
|
const filled = Math.round(score / max * width);
|
|
3852
4065
|
const empty = width - filled;
|
|
3853
|
-
const bar =
|
|
4066
|
+
const bar = chalk4.hex("#f97316")("\u2593".repeat(filled)) + chalk4.gray("\u2591".repeat(empty));
|
|
3854
4067
|
return bar;
|
|
3855
4068
|
}
|
|
3856
4069
|
function formatCheck(check) {
|
|
3857
|
-
const icon = check.passed ?
|
|
3858
|
-
const points = check.passed ?
|
|
3859
|
-
const name = check.passed ?
|
|
3860
|
-
const detail = check.detail ?
|
|
3861
|
-
const suggestion = !check.passed && check.suggestion ?
|
|
4070
|
+
const icon = check.passed ? chalk4.green("\u2713") : check.earnedPoints < 0 ? chalk4.red("\u2717") : chalk4.gray("\u2717");
|
|
4071
|
+
const points = check.passed ? chalk4.green(`+${check.earnedPoints}`.padStart(4)) : check.earnedPoints < 0 ? chalk4.red(`${check.earnedPoints}`.padStart(4)) : chalk4.gray(" \u2014");
|
|
4072
|
+
const name = check.passed ? chalk4.white(check.name) : chalk4.gray(check.name);
|
|
4073
|
+
const detail = check.detail ? chalk4.gray(` (${check.detail})`) : "";
|
|
4074
|
+
const suggestion = !check.passed && check.suggestion ? chalk4.gray(`
|
|
3862
4075
|
\u2192 ${check.suggestion}`) : "";
|
|
3863
4076
|
return ` ${icon} ${name.padEnd(38)}${points}${detail}${suggestion}`;
|
|
3864
4077
|
}
|
|
@@ -3866,19 +4079,19 @@ function displayScore(result) {
|
|
|
3866
4079
|
const gc = gradeColor(result.grade);
|
|
3867
4080
|
const agentLabel = result.targetAgent.map((a) => AGENT_DISPLAY_NAMES[a] || a).join(" + ");
|
|
3868
4081
|
console.log("");
|
|
3869
|
-
console.log(
|
|
4082
|
+
console.log(chalk4.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
3870
4083
|
console.log("");
|
|
3871
|
-
console.log(` ${
|
|
4084
|
+
console.log(` ${chalk4.bold("Agent Config Score")} ${gc(chalk4.bold(`${result.score} / ${result.maxScore}`))} Grade ${gc(chalk4.bold(result.grade))}`);
|
|
3872
4085
|
console.log(` ${progressBar(result.score, result.maxScore)}`);
|
|
3873
|
-
console.log(
|
|
4086
|
+
console.log(chalk4.dim(` Target: ${agentLabel}`));
|
|
3874
4087
|
console.log("");
|
|
3875
|
-
console.log(
|
|
4088
|
+
console.log(chalk4.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
3876
4089
|
console.log("");
|
|
3877
4090
|
for (const category of CATEGORY_ORDER) {
|
|
3878
4091
|
const summary = result.categories[category];
|
|
3879
4092
|
const categoryChecks = result.checks.filter((c) => c.category === category);
|
|
3880
4093
|
console.log(
|
|
3881
|
-
|
|
4094
|
+
chalk4.gray(` ${CATEGORY_LABELS[category]}`) + chalk4.gray(" ".repeat(Math.max(1, 45 - CATEGORY_LABELS[category].length))) + chalk4.white(`${summary.earned}`) + chalk4.gray(` / ${summary.max}`)
|
|
3882
4095
|
);
|
|
3883
4096
|
for (const check of categoryChecks) {
|
|
3884
4097
|
console.log(formatCheck(check));
|
|
@@ -3891,48 +4104,48 @@ function displayScoreSummary(result) {
|
|
|
3891
4104
|
const agentLabel = result.targetAgent.map((a) => AGENT_DISPLAY_NAMES[a] || a).join(" + ");
|
|
3892
4105
|
console.log("");
|
|
3893
4106
|
console.log(
|
|
3894
|
-
|
|
4107
|
+
chalk4.gray(" ") + gc(`${result.score}/${result.maxScore}`) + chalk4.gray(` (Grade ${result.grade})`) + chalk4.gray(` \xB7 ${agentLabel}`) + chalk4.gray(` \xB7 ${progressBar(result.score, result.maxScore, 20)}`)
|
|
3895
4108
|
);
|
|
3896
4109
|
const failing = result.checks.filter((c) => !c.passed);
|
|
3897
4110
|
if (failing.length > 0) {
|
|
3898
4111
|
const shown = failing.slice(0, 5);
|
|
3899
4112
|
for (const check of shown) {
|
|
3900
|
-
console.log(
|
|
4113
|
+
console.log(chalk4.gray(` \u2717 ${check.name}`));
|
|
3901
4114
|
}
|
|
3902
4115
|
const remaining = failing.length - shown.length;
|
|
3903
4116
|
const moreText = remaining > 0 ? ` (+${remaining} more)` : "";
|
|
3904
|
-
console.log(
|
|
3905
|
-
Run ${
|
|
4117
|
+
console.log(chalk4.dim(`
|
|
4118
|
+
Run ${chalk4.hex("#83D1EB")("caliber score")} for details.${moreText}`));
|
|
3906
4119
|
}
|
|
3907
4120
|
console.log("");
|
|
3908
4121
|
}
|
|
3909
4122
|
function displayScoreDelta(before, after) {
|
|
3910
4123
|
const delta = after.score - before.score;
|
|
3911
4124
|
const deltaStr = delta >= 0 ? `+${delta}` : `${delta}`;
|
|
3912
|
-
const deltaColor = delta >= 0 ?
|
|
4125
|
+
const deltaColor = delta >= 0 ? chalk4.green : chalk4.red;
|
|
3913
4126
|
const beforeGc = gradeColor(before.grade);
|
|
3914
4127
|
const afterGc = gradeColor(after.grade);
|
|
3915
4128
|
console.log("");
|
|
3916
|
-
console.log(
|
|
4129
|
+
console.log(chalk4.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
3917
4130
|
console.log("");
|
|
3918
4131
|
console.log(
|
|
3919
|
-
` Score: ${beforeGc(`${before.score}`)} ${
|
|
4132
|
+
` Score: ${beforeGc(`${before.score}`)} ${chalk4.gray("\u2192")} ${afterGc(`${after.score}`)} ${deltaColor(deltaStr + " pts")} ${beforeGc(before.grade)} ${chalk4.gray("\u2192")} ${afterGc(after.grade)}`
|
|
3920
4133
|
);
|
|
3921
|
-
console.log(` ${progressBar(before.score, before.maxScore, 19)} ${
|
|
4134
|
+
console.log(` ${progressBar(before.score, before.maxScore, 19)} ${chalk4.gray("\u2192")} ${progressBar(after.score, after.maxScore, 19)}`);
|
|
3922
4135
|
console.log("");
|
|
3923
|
-
console.log(
|
|
4136
|
+
console.log(chalk4.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
3924
4137
|
console.log("");
|
|
3925
4138
|
const improved = after.checks.filter((ac) => {
|
|
3926
4139
|
const bc = before.checks.find((b) => b.id === ac.id);
|
|
3927
4140
|
return bc && ac.earnedPoints > bc.earnedPoints;
|
|
3928
4141
|
});
|
|
3929
4142
|
if (improved.length > 0) {
|
|
3930
|
-
console.log(
|
|
4143
|
+
console.log(chalk4.gray(" What improved:"));
|
|
3931
4144
|
for (const check of improved) {
|
|
3932
4145
|
const bc = before.checks.find((b) => b.id === check.id);
|
|
3933
4146
|
const gain = check.earnedPoints - bc.earnedPoints;
|
|
3934
4147
|
console.log(
|
|
3935
|
-
|
|
4148
|
+
chalk4.green(" +") + chalk4.white(` ${check.name.padEnd(50)}`) + chalk4.green(`+${gain}`)
|
|
3936
4149
|
);
|
|
3937
4150
|
}
|
|
3938
4151
|
console.log("");
|
|
@@ -3940,10 +4153,10 @@ function displayScoreDelta(before, after) {
|
|
|
3940
4153
|
}
|
|
3941
4154
|
|
|
3942
4155
|
// src/mcp/index.ts
|
|
3943
|
-
import
|
|
4156
|
+
import chalk5 from "chalk";
|
|
3944
4157
|
import ora from "ora";
|
|
3945
4158
|
import readline3 from "readline";
|
|
3946
|
-
import
|
|
4159
|
+
import fs20 from "fs";
|
|
3947
4160
|
import path16 from "path";
|
|
3948
4161
|
|
|
3949
4162
|
// src/mcp/search.ts
|
|
@@ -4218,38 +4431,38 @@ ${truncated}`,
|
|
|
4218
4431
|
|
|
4219
4432
|
// src/mcp/index.ts
|
|
4220
4433
|
async function discoverAndInstallMcps(targetAgent, fingerprint, dir) {
|
|
4221
|
-
console.log(
|
|
4434
|
+
console.log(chalk5.hex("#6366f1").bold("\n MCP Server Discovery\n"));
|
|
4222
4435
|
const toolDeps = fingerprint.tools;
|
|
4223
4436
|
if (toolDeps.length === 0) {
|
|
4224
|
-
console.log(
|
|
4437
|
+
console.log(chalk5.dim(" No external tools or services detected \u2014 skipping MCP discovery"));
|
|
4225
4438
|
return { installed: 0, names: [] };
|
|
4226
4439
|
}
|
|
4227
4440
|
const spinner = ora(`Searching MCP servers for ${toolDeps.length} detected tool${toolDeps.length === 1 ? "" : "s"}...`).start();
|
|
4228
|
-
console.log(
|
|
4441
|
+
console.log(chalk5.dim(` Detected: ${toolDeps.join(", ")}`));
|
|
4229
4442
|
const existingMcps = getExistingMcpNames(fingerprint, targetAgent);
|
|
4230
4443
|
const filteredDeps = toolDeps.filter((d) => {
|
|
4231
4444
|
const lower = d.toLowerCase();
|
|
4232
4445
|
return !existingMcps.some((name) => name.includes(lower) || lower.includes(name));
|
|
4233
4446
|
});
|
|
4234
4447
|
if (filteredDeps.length === 0) {
|
|
4235
|
-
spinner.succeed(
|
|
4448
|
+
spinner.succeed(chalk5.dim("All detected tools already have MCP servers configured"));
|
|
4236
4449
|
return { installed: 0, names: [] };
|
|
4237
4450
|
}
|
|
4238
4451
|
const candidates = await searchAllMcpSources(filteredDeps);
|
|
4239
4452
|
if (candidates.length === 0) {
|
|
4240
|
-
spinner.succeed(
|
|
4453
|
+
spinner.succeed(chalk5.dim("No MCP servers found for detected tools"));
|
|
4241
4454
|
return { installed: 0, names: [] };
|
|
4242
4455
|
}
|
|
4243
4456
|
spinner.succeed(`Found ${candidates.length} candidate${candidates.length === 1 ? "" : "s"} for ${filteredDeps.join(", ")}`);
|
|
4244
4457
|
const scoreSpinner = ora("Scoring MCP candidates...").start();
|
|
4245
4458
|
const scored = await validateAndScore(candidates, filteredDeps);
|
|
4246
4459
|
if (scored.length === 0) {
|
|
4247
|
-
scoreSpinner.succeed(
|
|
4248
|
-
console.log(
|
|
4460
|
+
scoreSpinner.succeed(chalk5.dim("No quality MCP servers passed validation"));
|
|
4461
|
+
console.log(chalk5.dim(` Candidates checked: ${candidates.map((c) => c.name).join(", ")}`));
|
|
4249
4462
|
return { installed: 0, names: [] };
|
|
4250
4463
|
}
|
|
4251
4464
|
scoreSpinner.succeed(`${scored.length} quality MCP server${scored.length === 1 ? "" : "s"} found`);
|
|
4252
|
-
console.log(
|
|
4465
|
+
console.log(chalk5.dim(` Scored: ${scored.map((c) => `${c.name} (${c.score})`).join(", ")}`));
|
|
4253
4466
|
const selected = await interactiveSelect(scored);
|
|
4254
4467
|
if (!selected || selected.length === 0) {
|
|
4255
4468
|
return { installed: 0, names: [] };
|
|
@@ -4257,18 +4470,18 @@ async function discoverAndInstallMcps(targetAgent, fingerprint, dir) {
|
|
|
4257
4470
|
const mcpServers = {};
|
|
4258
4471
|
const installedNames = [];
|
|
4259
4472
|
for (const mcp of selected) {
|
|
4260
|
-
console.log(
|
|
4473
|
+
console.log(chalk5.bold(`
|
|
4261
4474
|
Configuring ${mcp.name}...`));
|
|
4262
4475
|
const readme = await fetchReadme(mcp.repoFullName);
|
|
4263
4476
|
if (!readme) {
|
|
4264
|
-
console.log(
|
|
4265
|
-
console.log(
|
|
4477
|
+
console.log(chalk5.yellow(` Could not fetch README for ${mcp.repoFullName} \u2014 skipping`));
|
|
4478
|
+
console.log(chalk5.dim(` Manual setup: ${mcp.url}`));
|
|
4266
4479
|
continue;
|
|
4267
4480
|
}
|
|
4268
4481
|
const config = await extractMcpConfig(readme, mcp.name);
|
|
4269
4482
|
if (!config || !config.command) {
|
|
4270
|
-
console.log(
|
|
4271
|
-
console.log(
|
|
4483
|
+
console.log(chalk5.yellow(` Could not extract config for ${mcp.name} \u2014 skipping`));
|
|
4484
|
+
console.log(chalk5.dim(` Manual setup: ${mcp.url}`));
|
|
4272
4485
|
continue;
|
|
4273
4486
|
}
|
|
4274
4487
|
const env = {};
|
|
@@ -4286,7 +4499,7 @@ async function discoverAndInstallMcps(targetAgent, fingerprint, dir) {
|
|
|
4286
4499
|
if (Object.keys(env).length > 0) serverConfig.env = env;
|
|
4287
4500
|
mcpServers[mcp.name] = serverConfig;
|
|
4288
4501
|
installedNames.push(mcp.name);
|
|
4289
|
-
console.log(` ${
|
|
4502
|
+
console.log(` ${chalk5.green("\u2713")} ${mcp.name} configured`);
|
|
4290
4503
|
}
|
|
4291
4504
|
if (installedNames.length === 0) {
|
|
4292
4505
|
return { installed: 0, names: [] };
|
|
@@ -4296,7 +4509,7 @@ async function discoverAndInstallMcps(targetAgent, fingerprint, dir) {
|
|
|
4296
4509
|
}
|
|
4297
4510
|
if (targetAgent.includes("cursor")) {
|
|
4298
4511
|
const cursorDir = path16.join(dir, ".cursor");
|
|
4299
|
-
if (!
|
|
4512
|
+
if (!fs20.existsSync(cursorDir)) fs20.mkdirSync(cursorDir, { recursive: true });
|
|
4300
4513
|
writeMcpJson(path16.join(cursorDir, "mcp.json"), mcpServers);
|
|
4301
4514
|
}
|
|
4302
4515
|
return { installed: installedNames.length, names: installedNames };
|
|
@@ -4304,14 +4517,14 @@ async function discoverAndInstallMcps(targetAgent, fingerprint, dir) {
|
|
|
4304
4517
|
function writeMcpJson(filePath, mcpServers) {
|
|
4305
4518
|
let existing = {};
|
|
4306
4519
|
try {
|
|
4307
|
-
if (
|
|
4308
|
-
const parsed = JSON.parse(
|
|
4520
|
+
if (fs20.existsSync(filePath)) {
|
|
4521
|
+
const parsed = JSON.parse(fs20.readFileSync(filePath, "utf-8"));
|
|
4309
4522
|
if (parsed.mcpServers) existing = parsed.mcpServers;
|
|
4310
4523
|
}
|
|
4311
4524
|
} catch {
|
|
4312
4525
|
}
|
|
4313
4526
|
const merged = { ...existing, ...mcpServers };
|
|
4314
|
-
|
|
4527
|
+
fs20.writeFileSync(filePath, JSON.stringify({ mcpServers: merged }, null, 2) + "\n");
|
|
4315
4528
|
}
|
|
4316
4529
|
function getExistingMcpNames(fingerprint, targetAgent) {
|
|
4317
4530
|
const names = [];
|
|
@@ -4330,7 +4543,7 @@ function getExistingMcpNames(fingerprint, targetAgent) {
|
|
|
4330
4543
|
function promptInput2(question) {
|
|
4331
4544
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
4332
4545
|
return new Promise((resolve2) => {
|
|
4333
|
-
rl.question(
|
|
4546
|
+
rl.question(chalk5.cyan(`${question}: `), (answer) => {
|
|
4334
4547
|
rl.close();
|
|
4335
4548
|
resolve2(answer.trim());
|
|
4336
4549
|
});
|
|
@@ -4338,10 +4551,10 @@ function promptInput2(question) {
|
|
|
4338
4551
|
}
|
|
4339
4552
|
async function interactiveSelect(candidates) {
|
|
4340
4553
|
if (!process.stdin.isTTY) {
|
|
4341
|
-
console.log(
|
|
4554
|
+
console.log(chalk5.bold("\n Available MCP servers:\n"));
|
|
4342
4555
|
for (const c of candidates) {
|
|
4343
|
-
const vendorTag = c.vendor ?
|
|
4344
|
-
console.log(` ${String(c.score).padStart(3)} ${c.name}${vendorTag} ${
|
|
4556
|
+
const vendorTag = c.vendor ? chalk5.blue(" (vendor)") : "";
|
|
4557
|
+
console.log(` ${String(c.score).padStart(3)} ${c.name}${vendorTag} ${chalk5.dim(c.reason)}`);
|
|
4345
4558
|
}
|
|
4346
4559
|
console.log("");
|
|
4347
4560
|
return null;
|
|
@@ -4352,18 +4565,18 @@ async function interactiveSelect(candidates) {
|
|
|
4352
4565
|
let lineCount = 0;
|
|
4353
4566
|
function render() {
|
|
4354
4567
|
const lines = [];
|
|
4355
|
-
lines.push(
|
|
4568
|
+
lines.push(chalk5.bold(" Select MCP servers to install:"));
|
|
4356
4569
|
lines.push("");
|
|
4357
4570
|
for (let i = 0; i < candidates.length; i++) {
|
|
4358
4571
|
const c = candidates[i];
|
|
4359
|
-
const check = selected.has(i) ?
|
|
4360
|
-
const ptr = i === cursor ?
|
|
4361
|
-
const scoreColor = c.score >= 90 ?
|
|
4362
|
-
const vendorTag = c.vendor ?
|
|
4363
|
-
lines.push(` ${ptr} ${check} ${scoreColor(String(c.score).padStart(3))} ${c.name}${vendorTag} ${
|
|
4572
|
+
const check = selected.has(i) ? chalk5.green("[x]") : "[ ]";
|
|
4573
|
+
const ptr = i === cursor ? chalk5.cyan(">") : " ";
|
|
4574
|
+
const scoreColor = c.score >= 90 ? chalk5.green : c.score >= 70 ? chalk5.yellow : chalk5.dim;
|
|
4575
|
+
const vendorTag = c.vendor ? chalk5.blue(" (vendor)") : "";
|
|
4576
|
+
lines.push(` ${ptr} ${check} ${scoreColor(String(c.score).padStart(3))} ${c.name}${vendorTag} ${chalk5.dim(c.reason.slice(0, 40))}`);
|
|
4364
4577
|
}
|
|
4365
4578
|
lines.push("");
|
|
4366
|
-
lines.push(
|
|
4579
|
+
lines.push(chalk5.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q skip"));
|
|
4367
4580
|
return lines.join("\n");
|
|
4368
4581
|
}
|
|
4369
4582
|
function draw(initial) {
|
|
@@ -4412,7 +4625,7 @@ async function interactiveSelect(candidates) {
|
|
|
4412
4625
|
case "\n":
|
|
4413
4626
|
cleanup();
|
|
4414
4627
|
if (selected.size === 0) {
|
|
4415
|
-
console.log(
|
|
4628
|
+
console.log(chalk5.dim("\n No MCP servers selected.\n"));
|
|
4416
4629
|
resolve2(null);
|
|
4417
4630
|
} else {
|
|
4418
4631
|
resolve2(Array.from(selected).sort().map((i) => candidates[i]));
|
|
@@ -4422,7 +4635,7 @@ async function interactiveSelect(candidates) {
|
|
|
4422
4635
|
case "\x1B":
|
|
4423
4636
|
case "":
|
|
4424
4637
|
cleanup();
|
|
4425
|
-
console.log(
|
|
4638
|
+
console.log(chalk5.dim("\n Skipped MCP server installation.\n"));
|
|
4426
4639
|
resolve2(null);
|
|
4427
4640
|
break;
|
|
4428
4641
|
}
|
|
@@ -4431,586 +4644,570 @@ async function interactiveSelect(candidates) {
|
|
|
4431
4644
|
});
|
|
4432
4645
|
}
|
|
4433
4646
|
|
|
4434
|
-
// src/commands/
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
console.log(title.bold(" Step 1/5 \u2014 Connect your LLM\n"));
|
|
4457
|
-
let config = loadConfig();
|
|
4458
|
-
if (!config) {
|
|
4459
|
-
console.log(chalk5.dim(" No LLM provider set yet. Choose how to run Caliber:\n"));
|
|
4460
|
-
try {
|
|
4461
|
-
await runInteractiveProviderSetup({
|
|
4462
|
-
selectMessage: "How do you want to use Caliber? (choose LLM provider)"
|
|
4463
|
-
});
|
|
4464
|
-
} catch (err) {
|
|
4465
|
-
if (err.message === "__exit__") throw err;
|
|
4466
|
-
throw err;
|
|
4467
|
-
}
|
|
4468
|
-
config = loadConfig();
|
|
4469
|
-
if (!config) {
|
|
4470
|
-
console.log(chalk5.red(" Setup was cancelled or failed.\n"));
|
|
4471
|
-
throw new Error("__exit__");
|
|
4472
|
-
}
|
|
4473
|
-
console.log(chalk5.green(" \u2713 Provider saved. Let's continue.\n"));
|
|
4474
|
-
}
|
|
4475
|
-
const displayModel = config.model === "default" && config.provider === "claude-cli" ? process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)" : config.model;
|
|
4476
|
-
const fastModel = getFastModel();
|
|
4477
|
-
const modelLine = fastModel ? ` Provider: ${config.provider} | Model: ${displayModel} | Scan: ${fastModel}` : ` Provider: ${config.provider} | Model: ${displayModel}`;
|
|
4478
|
-
console.log(chalk5.dim(modelLine + "\n"));
|
|
4479
|
-
console.log(title.bold(" Step 2/5 \u2014 Discover your project\n"));
|
|
4480
|
-
console.log(chalk5.dim(" Learning about your languages, dependencies, structure, and existing configs.\n"));
|
|
4481
|
-
const spinner = ora2("Analyzing project...").start();
|
|
4482
|
-
const fingerprint = collectFingerprint(process.cwd());
|
|
4483
|
-
await enrichFingerprintWithLLM(fingerprint, process.cwd());
|
|
4484
|
-
spinner.succeed("Project analyzed");
|
|
4485
|
-
console.log(chalk5.dim(` Languages: ${fingerprint.languages.join(", ") || "none detected"}`));
|
|
4486
|
-
console.log(chalk5.dim(` Files: ${fingerprint.fileTree.length} found
|
|
4487
|
-
`));
|
|
4488
|
-
const targetAgent = options.agent || await promptAgent();
|
|
4489
|
-
const preScore = computeLocalScore(process.cwd(), targetAgent);
|
|
4490
|
-
const failingForDismissal = preScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
|
|
4491
|
-
if (failingForDismissal.length > 0) {
|
|
4492
|
-
const newDismissals = await evaluateDismissals(failingForDismissal, fingerprint);
|
|
4493
|
-
if (newDismissals.length > 0) {
|
|
4494
|
-
const existing = readDismissedChecks();
|
|
4495
|
-
const existingIds = new Set(existing.map((d) => d.id));
|
|
4496
|
-
const merged = [...existing, ...newDismissals.filter((d) => !existingIds.has(d.id))];
|
|
4497
|
-
writeDismissedChecks(merged);
|
|
4498
|
-
}
|
|
4499
|
-
}
|
|
4500
|
-
const baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
4501
|
-
displayScoreSummary(baselineScore);
|
|
4502
|
-
const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length || fingerprint.existingConfigs.agentsMd);
|
|
4503
|
-
const NON_LLM_CHECKS = /* @__PURE__ */ new Set(["hooks_configured", "agents_md_exists", "permissions_configured", "mcp_servers"]);
|
|
4504
|
-
if (hasExistingConfig && baselineScore.score === 100) {
|
|
4505
|
-
console.log(chalk5.bold.green(" Your setup is already optimal \u2014 nothing to change.\n"));
|
|
4506
|
-
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber onboard --force") + chalk5.dim(" to regenerate anyway.\n"));
|
|
4507
|
-
if (!options.force) return;
|
|
4647
|
+
// src/commands/recommend.ts
|
|
4648
|
+
import chalk6 from "chalk";
|
|
4649
|
+
import ora2 from "ora";
|
|
4650
|
+
import select3 from "@inquirer/select";
|
|
4651
|
+
import { mkdirSync, readFileSync as readFileSync7, readdirSync as readdirSync5, existsSync as existsSync9, writeFileSync } from "fs";
|
|
4652
|
+
import { join as join8, dirname as dirname2 } from "path";
|
|
4653
|
+
|
|
4654
|
+
// src/scanner/index.ts
|
|
4655
|
+
import fs21 from "fs";
|
|
4656
|
+
import path17 from "path";
|
|
4657
|
+
import crypto2 from "crypto";
|
|
4658
|
+
function scanLocalState(dir) {
|
|
4659
|
+
const items = [];
|
|
4660
|
+
const claudeMdPath = path17.join(dir, "CLAUDE.md");
|
|
4661
|
+
if (fs21.existsSync(claudeMdPath)) {
|
|
4662
|
+
items.push({
|
|
4663
|
+
type: "rule",
|
|
4664
|
+
platform: "claude",
|
|
4665
|
+
name: "CLAUDE.md",
|
|
4666
|
+
contentHash: hashFile(claudeMdPath),
|
|
4667
|
+
path: claudeMdPath
|
|
4668
|
+
});
|
|
4508
4669
|
}
|
|
4509
|
-
const
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4670
|
+
const skillsDir = path17.join(dir, ".claude", "skills");
|
|
4671
|
+
if (fs21.existsSync(skillsDir)) {
|
|
4672
|
+
for (const file of fs21.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
4673
|
+
const filePath = path17.join(skillsDir, file);
|
|
4674
|
+
items.push({
|
|
4675
|
+
type: "skill",
|
|
4676
|
+
platform: "claude",
|
|
4677
|
+
name: file,
|
|
4678
|
+
contentHash: hashFile(filePath),
|
|
4679
|
+
path: filePath
|
|
4680
|
+
});
|
|
4519
4681
|
}
|
|
4520
|
-
console.log("");
|
|
4521
|
-
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber onboard --force") + chalk5.dim(" to regenerate anyway.\n"));
|
|
4522
|
-
return;
|
|
4523
|
-
}
|
|
4524
|
-
const isEmpty = fingerprint.fileTree.length < 3;
|
|
4525
|
-
if (isEmpty) {
|
|
4526
|
-
fingerprint.description = await promptInput3("What will you build in this project?");
|
|
4527
4682
|
}
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4683
|
+
const mcpJsonPath = path17.join(dir, ".mcp.json");
|
|
4684
|
+
if (fs21.existsSync(mcpJsonPath)) {
|
|
4685
|
+
try {
|
|
4686
|
+
const mcpJson = JSON.parse(fs21.readFileSync(mcpJsonPath, "utf-8"));
|
|
4687
|
+
if (mcpJson.mcpServers) {
|
|
4688
|
+
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
4689
|
+
items.push({
|
|
4690
|
+
type: "mcp",
|
|
4691
|
+
platform: "claude",
|
|
4692
|
+
name,
|
|
4693
|
+
contentHash: hashJson(mcpJson.mcpServers[name]),
|
|
4694
|
+
path: mcpJsonPath
|
|
4695
|
+
});
|
|
4696
|
+
}
|
|
4541
4697
|
}
|
|
4542
|
-
|
|
4698
|
+
} catch {
|
|
4543
4699
|
}
|
|
4544
|
-
} else if (hasExistingConfig) {
|
|
4545
|
-
console.log(title.bold(" Step 3/5 \u2014 Improve your setup\n"));
|
|
4546
|
-
console.log(chalk5.dim(" Reviewing your existing configs against your codebase"));
|
|
4547
|
-
console.log(chalk5.dim(" and preparing improvements.\n"));
|
|
4548
|
-
} else {
|
|
4549
|
-
console.log(title.bold(" Step 3/5 \u2014 Build your agent setup\n"));
|
|
4550
|
-
console.log(chalk5.dim(" Creating config files tailored to your project.\n"));
|
|
4551
4700
|
}
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
fingerprint,
|
|
4562
|
-
targetAgent,
|
|
4563
|
-
fingerprint.description,
|
|
4564
|
-
{
|
|
4565
|
-
onStatus: (status) => {
|
|
4566
|
-
genMessages.handleServerStatus(status);
|
|
4567
|
-
},
|
|
4568
|
-
onComplete: (setup) => {
|
|
4569
|
-
generatedSetup = setup;
|
|
4570
|
-
},
|
|
4571
|
-
onError: (error) => {
|
|
4572
|
-
genMessages.stop();
|
|
4573
|
-
genSpinner.fail(`Generation error: ${error}`);
|
|
4574
|
-
}
|
|
4575
|
-
},
|
|
4576
|
-
failingChecks,
|
|
4577
|
-
currentScore,
|
|
4578
|
-
passingChecks
|
|
4579
|
-
);
|
|
4580
|
-
if (!generatedSetup) {
|
|
4581
|
-
generatedSetup = result.setup;
|
|
4582
|
-
rawOutput = result.raw;
|
|
4583
|
-
}
|
|
4584
|
-
} catch (err) {
|
|
4585
|
-
genMessages.stop();
|
|
4586
|
-
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
4587
|
-
genSpinner.fail(`Generation failed: ${msg}`);
|
|
4588
|
-
throw new Error("__exit__");
|
|
4701
|
+
const agentsMdPath = path17.join(dir, "AGENTS.md");
|
|
4702
|
+
if (fs21.existsSync(agentsMdPath)) {
|
|
4703
|
+
items.push({
|
|
4704
|
+
type: "rule",
|
|
4705
|
+
platform: "codex",
|
|
4706
|
+
name: "AGENTS.md",
|
|
4707
|
+
contentHash: hashFile(agentsMdPath),
|
|
4708
|
+
path: agentsMdPath
|
|
4709
|
+
});
|
|
4589
4710
|
}
|
|
4590
|
-
|
|
4591
|
-
if (
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4711
|
+
const codexSkillsDir = path17.join(dir, ".agents", "skills");
|
|
4712
|
+
if (fs21.existsSync(codexSkillsDir)) {
|
|
4713
|
+
try {
|
|
4714
|
+
for (const name of fs21.readdirSync(codexSkillsDir)) {
|
|
4715
|
+
const skillFile = path17.join(codexSkillsDir, name, "SKILL.md");
|
|
4716
|
+
if (fs21.existsSync(skillFile)) {
|
|
4717
|
+
items.push({
|
|
4718
|
+
type: "skill",
|
|
4719
|
+
platform: "codex",
|
|
4720
|
+
name: `${name}/SKILL.md`,
|
|
4721
|
+
contentHash: hashFile(skillFile),
|
|
4722
|
+
path: skillFile
|
|
4723
|
+
});
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
} catch {
|
|
4596
4727
|
}
|
|
4597
|
-
throw new Error("__exit__");
|
|
4598
4728
|
}
|
|
4599
|
-
const
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
content: summarizeSetup("Initial generation", generatedSetup)
|
|
4609
|
-
});
|
|
4610
|
-
console.log(title.bold(" Step 4/5 \u2014 Review and apply\n"));
|
|
4611
|
-
const setupFiles = collectSetupFiles(generatedSetup);
|
|
4612
|
-
const staged = stageFiles(setupFiles, process.cwd());
|
|
4613
|
-
const totalChanges = staged.newFiles + staged.modifiedFiles;
|
|
4614
|
-
console.log(chalk5.dim(` ${chalk5.green(`${staged.newFiles} new`)} / ${chalk5.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}
|
|
4615
|
-
`));
|
|
4616
|
-
let action;
|
|
4617
|
-
if (totalChanges === 0) {
|
|
4618
|
-
console.log(chalk5.dim(" No changes needed \u2014 your configs are already up to date.\n"));
|
|
4619
|
-
cleanupStaging();
|
|
4620
|
-
action = "accept";
|
|
4621
|
-
} else {
|
|
4622
|
-
const wantsReview = await promptWantsReview();
|
|
4623
|
-
if (wantsReview) {
|
|
4624
|
-
const reviewMethod = await promptReviewMethod();
|
|
4625
|
-
await openReview(reviewMethod, staged.stagedFiles);
|
|
4626
|
-
}
|
|
4627
|
-
action = await promptReviewAction();
|
|
4729
|
+
const cursorrulesPath = path17.join(dir, ".cursorrules");
|
|
4730
|
+
if (fs21.existsSync(cursorrulesPath)) {
|
|
4731
|
+
items.push({
|
|
4732
|
+
type: "rule",
|
|
4733
|
+
platform: "cursor",
|
|
4734
|
+
name: ".cursorrules",
|
|
4735
|
+
contentHash: hashFile(cursorrulesPath),
|
|
4736
|
+
path: cursorrulesPath
|
|
4737
|
+
});
|
|
4628
4738
|
}
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4739
|
+
const cursorRulesDir = path17.join(dir, ".cursor", "rules");
|
|
4740
|
+
if (fs21.existsSync(cursorRulesDir)) {
|
|
4741
|
+
for (const file of fs21.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
4742
|
+
const filePath = path17.join(cursorRulesDir, file);
|
|
4743
|
+
items.push({
|
|
4744
|
+
type: "rule",
|
|
4745
|
+
platform: "cursor",
|
|
4746
|
+
name: file,
|
|
4747
|
+
contentHash: hashFile(filePath),
|
|
4748
|
+
path: filePath
|
|
4749
|
+
});
|
|
4635
4750
|
}
|
|
4636
|
-
const updatedFiles = collectSetupFiles(generatedSetup);
|
|
4637
|
-
const restaged = stageFiles(updatedFiles, process.cwd());
|
|
4638
|
-
console.log(chalk5.dim(` ${chalk5.green(`${restaged.newFiles} new`)} / ${chalk5.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
|
|
4639
|
-
`));
|
|
4640
|
-
printSetupSummary(generatedSetup);
|
|
4641
|
-
await openReview("terminal", restaged.stagedFiles);
|
|
4642
|
-
action = await promptReviewAction();
|
|
4643
|
-
}
|
|
4644
|
-
cleanupStaging();
|
|
4645
|
-
if (action === "decline") {
|
|
4646
|
-
console.log(chalk5.dim("Setup declined. No files were modified."));
|
|
4647
|
-
return;
|
|
4648
|
-
}
|
|
4649
|
-
if (options.dryRun) {
|
|
4650
|
-
console.log(chalk5.yellow("\n[Dry run] Would write the following files:"));
|
|
4651
|
-
console.log(JSON.stringify(generatedSetup, null, 2));
|
|
4652
|
-
return;
|
|
4653
4751
|
}
|
|
4654
|
-
const
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4752
|
+
const cursorSkillsDir = path17.join(dir, ".cursor", "skills");
|
|
4753
|
+
if (fs21.existsSync(cursorSkillsDir)) {
|
|
4754
|
+
try {
|
|
4755
|
+
for (const name of fs21.readdirSync(cursorSkillsDir)) {
|
|
4756
|
+
const skillFile = path17.join(cursorSkillsDir, name, "SKILL.md");
|
|
4757
|
+
if (fs21.existsSync(skillFile)) {
|
|
4758
|
+
items.push({
|
|
4759
|
+
type: "skill",
|
|
4760
|
+
platform: "cursor",
|
|
4761
|
+
name: `${name}/SKILL.md`,
|
|
4762
|
+
contentHash: hashFile(skillFile),
|
|
4763
|
+
path: skillFile
|
|
4764
|
+
});
|
|
4765
|
+
}
|
|
4666
4766
|
}
|
|
4767
|
+
} catch {
|
|
4667
4768
|
}
|
|
4668
|
-
if (result.backupDir) {
|
|
4669
|
-
console.log(chalk5.dim(`
|
|
4670
|
-
Backups saved to ${result.backupDir}`));
|
|
4671
|
-
}
|
|
4672
|
-
} catch (err) {
|
|
4673
|
-
writeSpinner.fail("Failed to write files");
|
|
4674
|
-
console.error(chalk5.red(err instanceof Error ? err.message : "Unknown error"));
|
|
4675
|
-
throw new Error("__exit__");
|
|
4676
4769
|
}
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
console.log(chalk5.dim(" like databases, APIs, and platforms your project depends on.\n"));
|
|
4680
|
-
if (fingerprint.tools.length > 0) {
|
|
4770
|
+
const cursorMcpPath = path17.join(dir, ".cursor", "mcp.json");
|
|
4771
|
+
if (fs21.existsSync(cursorMcpPath)) {
|
|
4681
4772
|
try {
|
|
4682
|
-
const
|
|
4683
|
-
if (
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4773
|
+
const mcpJson = JSON.parse(fs21.readFileSync(cursorMcpPath, "utf-8"));
|
|
4774
|
+
if (mcpJson.mcpServers) {
|
|
4775
|
+
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
4776
|
+
items.push({
|
|
4777
|
+
type: "mcp",
|
|
4778
|
+
platform: "cursor",
|
|
4779
|
+
name,
|
|
4780
|
+
contentHash: hashJson(mcpJson.mcpServers[name]),
|
|
4781
|
+
path: cursorMcpPath
|
|
4782
|
+
});
|
|
4688
4783
|
}
|
|
4689
4784
|
}
|
|
4690
|
-
} catch
|
|
4691
|
-
console.log(chalk5.dim(" MCP discovery skipped: " + (err instanceof Error ? err.message : "unknown error")));
|
|
4785
|
+
} catch {
|
|
4692
4786
|
}
|
|
4693
|
-
} else {
|
|
4694
|
-
console.log(chalk5.dim(" No external tools or services detected \u2014 skipping MCP discovery.\n"));
|
|
4695
4787
|
}
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
} else if (hookResult.alreadyInstalled) {
|
|
4713
|
-
console.log(chalk5.dim(" Claude Code hook already installed"));
|
|
4714
|
-
}
|
|
4715
|
-
const learnResult = installLearningHooks();
|
|
4716
|
-
if (learnResult.installed) {
|
|
4717
|
-
console.log(` ${chalk5.green("\u2713")} Learning hooks installed \u2014 session insights captured automatically`);
|
|
4718
|
-
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber learn remove") + chalk5.dim(" to disable"));
|
|
4719
|
-
} else if (learnResult.alreadyInstalled) {
|
|
4720
|
-
console.log(chalk5.dim(" Learning hooks already installed"));
|
|
4721
|
-
}
|
|
4788
|
+
return items;
|
|
4789
|
+
}
|
|
4790
|
+
function hashFile(filePath) {
|
|
4791
|
+
const text = fs21.readFileSync(filePath, "utf-8");
|
|
4792
|
+
return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
|
|
4793
|
+
}
|
|
4794
|
+
function hashJson(obj) {
|
|
4795
|
+
return crypto2.createHash("sha256").update(JSON.stringify(obj)).digest("hex");
|
|
4796
|
+
}
|
|
4797
|
+
|
|
4798
|
+
// src/commands/recommend.ts
|
|
4799
|
+
function detectLocalPlatforms() {
|
|
4800
|
+
const items = scanLocalState(process.cwd());
|
|
4801
|
+
const platforms = /* @__PURE__ */ new Set();
|
|
4802
|
+
for (const item of items) {
|
|
4803
|
+
platforms.add(item.platform);
|
|
4722
4804
|
}
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
} else if (precommitResult.alreadyInstalled) {
|
|
4729
|
-
console.log(chalk5.dim(" Pre-commit hook already installed"));
|
|
4730
|
-
} else {
|
|
4731
|
-
console.log(chalk5.yellow(" Could not install pre-commit hook (not a git repository?)"));
|
|
4732
|
-
}
|
|
4805
|
+
return platforms.size > 0 ? Array.from(platforms) : ["claude"];
|
|
4806
|
+
}
|
|
4807
|
+
function getSkillPath(platform, slug) {
|
|
4808
|
+
if (platform === "cursor") {
|
|
4809
|
+
return join8(".cursor", "skills", slug, "SKILL.md");
|
|
4733
4810
|
}
|
|
4734
|
-
if (
|
|
4735
|
-
|
|
4811
|
+
if (platform === "codex") {
|
|
4812
|
+
return join8(".agents", "skills", slug, "SKILL.md");
|
|
4736
4813
|
}
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4814
|
+
return join8(".claude", "skills", slug, "SKILL.md");
|
|
4815
|
+
}
|
|
4816
|
+
function getInstalledSkills() {
|
|
4817
|
+
const installed = /* @__PURE__ */ new Set();
|
|
4818
|
+
const dirs = [
|
|
4819
|
+
join8(process.cwd(), ".claude", "skills"),
|
|
4820
|
+
join8(process.cwd(), ".cursor", "skills"),
|
|
4821
|
+
join8(process.cwd(), ".agents", "skills")
|
|
4822
|
+
];
|
|
4823
|
+
for (const dir of dirs) {
|
|
4741
4824
|
try {
|
|
4742
|
-
const
|
|
4743
|
-
|
|
4744
|
-
|
|
4825
|
+
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
4826
|
+
for (const entry of entries) {
|
|
4827
|
+
if (entry.isDirectory()) {
|
|
4828
|
+
installed.add(entry.name.toLowerCase());
|
|
4829
|
+
}
|
|
4745
4830
|
}
|
|
4746
4831
|
} catch {
|
|
4747
4832
|
}
|
|
4748
|
-
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber onboard --force") + chalk5.dim(" to override.\n"));
|
|
4749
|
-
return;
|
|
4750
4833
|
}
|
|
4751
|
-
|
|
4752
|
-
console.log(chalk5.bold.green(" Onboarding complete! Your project is ready for AI-assisted development."));
|
|
4753
|
-
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber undo") + chalk5.dim(" to revert changes.\n"));
|
|
4754
|
-
console.log(chalk5.bold(" Next steps:\n"));
|
|
4755
|
-
console.log(` ${title("caliber score")} See your full config breakdown`);
|
|
4756
|
-
console.log(` ${title("caliber recommend")} Discover community skills for your stack`);
|
|
4757
|
-
console.log(` ${title("caliber undo")} Revert all changes from this run`);
|
|
4758
|
-
console.log("");
|
|
4834
|
+
return installed;
|
|
4759
4835
|
}
|
|
4760
|
-
async function
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4836
|
+
async function searchSkillsSh(technologies) {
|
|
4837
|
+
const bestBySlug = /* @__PURE__ */ new Map();
|
|
4838
|
+
for (const tech of technologies) {
|
|
4839
|
+
try {
|
|
4840
|
+
const resp = await fetch(`https://skills.sh/api/search?q=${encodeURIComponent(tech)}&limit=10`, {
|
|
4841
|
+
signal: AbortSignal.timeout(1e4)
|
|
4842
|
+
});
|
|
4843
|
+
if (!resp.ok) continue;
|
|
4844
|
+
const data = await resp.json();
|
|
4845
|
+
if (!data.skills?.length) continue;
|
|
4846
|
+
for (const skill of data.skills) {
|
|
4847
|
+
const existing = bestBySlug.get(skill.skillId);
|
|
4848
|
+
if (existing && existing.installs >= (skill.installs ?? 0)) continue;
|
|
4849
|
+
bestBySlug.set(skill.skillId, {
|
|
4850
|
+
name: skill.name,
|
|
4851
|
+
slug: skill.skillId,
|
|
4852
|
+
source_url: skill.source ? `https://github.com/${skill.source}` : "",
|
|
4853
|
+
score: 0,
|
|
4854
|
+
reason: skill.description || "",
|
|
4855
|
+
detected_technology: tech,
|
|
4856
|
+
item_type: "skill",
|
|
4857
|
+
installs: skill.installs ?? 0
|
|
4858
|
+
});
|
|
4859
|
+
}
|
|
4860
|
+
} catch {
|
|
4774
4861
|
continue;
|
|
4775
4862
|
}
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
currentSetup = refined;
|
|
4787
|
-
sessionHistory.push({ role: "user", content: message });
|
|
4788
|
-
sessionHistory.push({
|
|
4789
|
-
role: "assistant",
|
|
4790
|
-
content: summarizeSetup("Applied changes", refined)
|
|
4863
|
+
}
|
|
4864
|
+
return Array.from(bestBySlug.values());
|
|
4865
|
+
}
|
|
4866
|
+
async function searchTessl(technologies) {
|
|
4867
|
+
const results = [];
|
|
4868
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4869
|
+
for (const tech of technologies) {
|
|
4870
|
+
try {
|
|
4871
|
+
const resp = await fetch(`https://tessl.io/registry?q=${encodeURIComponent(tech)}`, {
|
|
4872
|
+
signal: AbortSignal.timeout(1e4)
|
|
4791
4873
|
});
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4874
|
+
if (!resp.ok) continue;
|
|
4875
|
+
const html = await resp.text();
|
|
4876
|
+
const linkMatches = html.matchAll(/\/registry\/skills\/github\/([^/]+)\/([^/]+)\/([^/"]+)/g);
|
|
4877
|
+
for (const match of linkMatches) {
|
|
4878
|
+
const [, org, repo, skillName] = match;
|
|
4879
|
+
const slug = `${org}-${repo}-${skillName}`.toLowerCase();
|
|
4880
|
+
if (seen.has(slug)) continue;
|
|
4881
|
+
seen.add(slug);
|
|
4882
|
+
results.push({
|
|
4883
|
+
name: skillName,
|
|
4884
|
+
slug,
|
|
4885
|
+
source_url: `https://github.com/${org}/${repo}`,
|
|
4886
|
+
score: 0,
|
|
4887
|
+
reason: `Skill from ${org}/${repo}`,
|
|
4888
|
+
detected_technology: tech,
|
|
4889
|
+
item_type: "skill"
|
|
4890
|
+
});
|
|
4891
|
+
}
|
|
4892
|
+
} catch {
|
|
4893
|
+
continue;
|
|
4798
4894
|
}
|
|
4799
4895
|
}
|
|
4896
|
+
return results;
|
|
4800
4897
|
}
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
const files = descriptions ? Object.entries(descriptions).map(([path24, desc]) => ` ${path24}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
|
|
4804
|
-
return `${action}. Files:
|
|
4805
|
-
${files}`;
|
|
4806
|
-
}
|
|
4807
|
-
async function classifyRefineIntent(message) {
|
|
4808
|
-
const fastModel = getFastModel();
|
|
4898
|
+
var AWESOME_CLAUDE_CODE_URL = "https://raw.githubusercontent.com/hesreallyhim/awesome-claude-code/main/README.md";
|
|
4899
|
+
async function searchAwesomeClaudeCode(technologies) {
|
|
4809
4900
|
try {
|
|
4810
|
-
const
|
|
4811
|
-
|
|
4812
|
-
Valid: requests to add, remove, change, or restructure config content. Examples: "add testing commands", "remove the terraform section", "make CLAUDE.md shorter".
|
|
4813
|
-
Invalid: questions, requests to show/display something, general chat, or anything that isn't a concrete config change.
|
|
4814
|
-
Return {"valid": true} or {"valid": false}. Nothing else.`,
|
|
4815
|
-
prompt: message,
|
|
4816
|
-
maxTokens: 20,
|
|
4817
|
-
...fastModel ? { model: fastModel } : {}
|
|
4901
|
+
const resp = await fetch(AWESOME_CLAUDE_CODE_URL, {
|
|
4902
|
+
signal: AbortSignal.timeout(1e4)
|
|
4818
4903
|
});
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
Failing checks:
|
|
4844
|
-
${JSON.stringify(checkList, null, 2)}`,
|
|
4845
|
-
maxTokens: 200,
|
|
4846
|
-
...fastModel ? { model: fastModel } : {}
|
|
4904
|
+
if (!resp.ok) return [];
|
|
4905
|
+
const markdown = await resp.text();
|
|
4906
|
+
const items = [];
|
|
4907
|
+
const itemPattern = /^[-*]\s+\[([^\]]+)\]\(([^)]+)\)(?:\s+by\s+\[[^\]]*\]\([^)]*\))?\s*[-–—:]\s*(.*)/gm;
|
|
4908
|
+
let match;
|
|
4909
|
+
while ((match = itemPattern.exec(markdown)) !== null) {
|
|
4910
|
+
const [, name, url, description] = match;
|
|
4911
|
+
if (url.startsWith("#")) continue;
|
|
4912
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4913
|
+
items.push({
|
|
4914
|
+
name: name.trim(),
|
|
4915
|
+
slug,
|
|
4916
|
+
source_url: url.trim(),
|
|
4917
|
+
score: 0,
|
|
4918
|
+
reason: description.trim().slice(0, 150),
|
|
4919
|
+
detected_technology: "claude-code",
|
|
4920
|
+
item_type: "skill"
|
|
4921
|
+
});
|
|
4922
|
+
}
|
|
4923
|
+
const techLower = technologies.map((t) => t.toLowerCase());
|
|
4924
|
+
return items.filter((item) => {
|
|
4925
|
+
const text = `${item.name} ${item.reason}`.toLowerCase();
|
|
4926
|
+
return techLower.some((t) => text.includes(t));
|
|
4847
4927
|
});
|
|
4848
|
-
if (!Array.isArray(result.dismissed)) return [];
|
|
4849
|
-
return result.dismissed.filter((d) => d.id && d.reason && failingChecks.some((c) => c.id === d.id)).map((d) => ({ id: d.id, reason: d.reason, dismissedAt: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
4850
4928
|
} catch {
|
|
4851
4929
|
return [];
|
|
4852
4930
|
}
|
|
4853
4931
|
}
|
|
4854
|
-
function
|
|
4855
|
-
const
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
}
|
|
4932
|
+
async function searchAllProviders(technologies, platform) {
|
|
4933
|
+
const searches = [
|
|
4934
|
+
searchSkillsSh(technologies),
|
|
4935
|
+
searchTessl(technologies)
|
|
4936
|
+
];
|
|
4937
|
+
if (platform === "claude" || !platform) {
|
|
4938
|
+
searches.push(searchAwesomeClaudeCode(technologies));
|
|
4939
|
+
}
|
|
4940
|
+
const results = await Promise.all(searches);
|
|
4941
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4942
|
+
const combined = [];
|
|
4943
|
+
for (const batch of results) {
|
|
4944
|
+
for (const result of batch) {
|
|
4945
|
+
const key = result.name.toLowerCase().replace(/[-_]/g, "");
|
|
4946
|
+
if (seen.has(key)) continue;
|
|
4947
|
+
seen.add(key);
|
|
4948
|
+
combined.push(result);
|
|
4949
|
+
}
|
|
4950
|
+
}
|
|
4951
|
+
return combined;
|
|
4862
4952
|
}
|
|
4863
|
-
async function
|
|
4864
|
-
const
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4953
|
+
async function scoreWithLLM2(candidates, projectContext, technologies) {
|
|
4954
|
+
const candidateList = candidates.map((c, i) => `${i}. "${c.name}" \u2014 ${c.reason || "no description"}`).join("\n");
|
|
4955
|
+
const scored = await llmJsonCall({
|
|
4956
|
+
system: `You evaluate whether AI agent skills and tools are relevant to a specific software project.
|
|
4957
|
+
Given a project context and a list of candidates, score each one's relevance from 0-100 and provide a brief reason (max 80 chars).
|
|
4958
|
+
|
|
4959
|
+
Return a JSON array where each element has:
|
|
4960
|
+
- "index": the candidate's index number
|
|
4961
|
+
- "score": relevance score 0-100
|
|
4962
|
+
- "reason": one-liner explaining why it fits or doesn't
|
|
4963
|
+
|
|
4964
|
+
Scoring guidelines:
|
|
4965
|
+
- 90-100: Directly matches a core technology or workflow in the project
|
|
4966
|
+
- 70-89: Relevant to the project's stack, patterns, or development workflow
|
|
4967
|
+
- 50-69: Tangentially related or generic but useful
|
|
4968
|
+
- 0-49: Not relevant to this project
|
|
4969
|
+
|
|
4970
|
+
Be selective. Prefer specific, high-quality matches over generic ones.
|
|
4971
|
+
A skill for "React testing" is only relevant if the project uses React.
|
|
4972
|
+
A generic "TypeScript best practices" skill is less valuable than one targeting the project's actual framework.
|
|
4973
|
+
Return ONLY the JSON array.`,
|
|
4974
|
+
prompt: `PROJECT CONTEXT:
|
|
4975
|
+
${projectContext}
|
|
4976
|
+
|
|
4977
|
+
DETECTED TECHNOLOGIES:
|
|
4978
|
+
${technologies.join(", ")}
|
|
4979
|
+
|
|
4980
|
+
CANDIDATES:
|
|
4981
|
+
${candidateList}`,
|
|
4982
|
+
maxTokens: 8e3
|
|
4875
4983
|
});
|
|
4876
|
-
return
|
|
4984
|
+
if (!Array.isArray(scored)) return [];
|
|
4985
|
+
return scored.filter((s) => s.score >= 60 && s.index >= 0 && s.index < candidates.length).sort((a, b) => b.score - a.score).slice(0, 20).map((s) => ({
|
|
4986
|
+
...candidates[s.index],
|
|
4987
|
+
score: s.score,
|
|
4988
|
+
reason: s.reason || candidates[s.index].reason
|
|
4989
|
+
}));
|
|
4877
4990
|
}
|
|
4878
|
-
|
|
4879
|
-
const
|
|
4880
|
-
const
|
|
4881
|
-
if (
|
|
4882
|
-
|
|
4991
|
+
function buildProjectContext(dir) {
|
|
4992
|
+
const parts = [];
|
|
4993
|
+
const fingerprint = collectFingerprint(dir);
|
|
4994
|
+
if (fingerprint.packageName) parts.push(`Package: ${fingerprint.packageName}`);
|
|
4995
|
+
if (fingerprint.languages.length > 0) parts.push(`Languages: ${fingerprint.languages.join(", ")}`);
|
|
4996
|
+
if (fingerprint.frameworks.length > 0) parts.push(`Frameworks: ${fingerprint.frameworks.join(", ")}`);
|
|
4997
|
+
if (fingerprint.description) parts.push(`Description: ${fingerprint.description}`);
|
|
4998
|
+
if (fingerprint.fileTree.length > 0) {
|
|
4999
|
+
parts.push(`
|
|
5000
|
+
File tree (${fingerprint.fileTree.length} files):
|
|
5001
|
+
${fingerprint.fileTree.slice(0, 50).join("\n")}`);
|
|
4883
5002
|
}
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
5003
|
+
if (fingerprint.existingConfigs.claudeMd) {
|
|
5004
|
+
parts.push(`
|
|
5005
|
+
Existing CLAUDE.md (first 500 chars):
|
|
5006
|
+
${fingerprint.existingConfigs.claudeMd.slice(0, 500)}`);
|
|
4887
5007
|
}
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
}
|
|
5008
|
+
const deps = extractTopDeps();
|
|
5009
|
+
if (deps.length > 0) {
|
|
5010
|
+
parts.push(`
|
|
5011
|
+
Dependencies: ${deps.slice(0, 30).join(", ")}`);
|
|
5012
|
+
}
|
|
5013
|
+
const installed = getInstalledSkills();
|
|
5014
|
+
if (installed.size > 0) {
|
|
5015
|
+
parts.push(`
|
|
5016
|
+
Already installed skills: ${Array.from(installed).join(", ")}`);
|
|
5017
|
+
}
|
|
5018
|
+
return parts.join("\n");
|
|
4893
5019
|
}
|
|
4894
|
-
|
|
4895
|
-
const
|
|
4896
|
-
|
|
5020
|
+
function extractTopDeps() {
|
|
5021
|
+
const pkgPath = join8(process.cwd(), "package.json");
|
|
5022
|
+
if (!existsSync9(pkgPath)) return [];
|
|
5023
|
+
try {
|
|
5024
|
+
const pkg3 = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
5025
|
+
const deps = Object.keys(pkg3.dependencies ?? {});
|
|
5026
|
+
const trivial = /* @__PURE__ */ new Set([
|
|
5027
|
+
"typescript",
|
|
5028
|
+
"tslib",
|
|
5029
|
+
"ts-node",
|
|
5030
|
+
"tsx",
|
|
5031
|
+
"prettier",
|
|
5032
|
+
"eslint",
|
|
5033
|
+
"@eslint/js",
|
|
5034
|
+
"rimraf",
|
|
5035
|
+
"cross-env",
|
|
5036
|
+
"dotenv",
|
|
5037
|
+
"nodemon",
|
|
5038
|
+
"husky",
|
|
5039
|
+
"lint-staged",
|
|
5040
|
+
"commitlint",
|
|
5041
|
+
"chalk",
|
|
5042
|
+
"ora",
|
|
5043
|
+
"commander",
|
|
5044
|
+
"yargs",
|
|
5045
|
+
"meow",
|
|
5046
|
+
"inquirer",
|
|
5047
|
+
"@inquirer/confirm",
|
|
5048
|
+
"@inquirer/select",
|
|
5049
|
+
"@inquirer/prompts",
|
|
5050
|
+
"glob",
|
|
5051
|
+
"minimatch",
|
|
5052
|
+
"micromatch",
|
|
5053
|
+
"diff",
|
|
5054
|
+
"semver",
|
|
5055
|
+
"uuid",
|
|
5056
|
+
"nanoid",
|
|
5057
|
+
"debug",
|
|
5058
|
+
"ms",
|
|
5059
|
+
"lodash",
|
|
5060
|
+
"underscore",
|
|
5061
|
+
"tsup",
|
|
5062
|
+
"esbuild",
|
|
5063
|
+
"rollup",
|
|
5064
|
+
"webpack",
|
|
5065
|
+
"vite",
|
|
5066
|
+
"vitest",
|
|
5067
|
+
"jest",
|
|
5068
|
+
"mocha",
|
|
5069
|
+
"chai",
|
|
5070
|
+
"ava",
|
|
5071
|
+
"fs-extra",
|
|
5072
|
+
"mkdirp",
|
|
5073
|
+
"del",
|
|
5074
|
+
"rimraf",
|
|
5075
|
+
"path-to-regexp",
|
|
5076
|
+
"strip-ansi",
|
|
5077
|
+
"ansi-colors"
|
|
5078
|
+
]);
|
|
5079
|
+
const trivialPatterns = [
|
|
5080
|
+
/^@types\//,
|
|
5081
|
+
/^@rely-ai\//,
|
|
5082
|
+
/^@caliber-ai\//,
|
|
5083
|
+
/^eslint-/,
|
|
5084
|
+
/^@eslint\//,
|
|
5085
|
+
/^prettier-/,
|
|
5086
|
+
/^@typescript-eslint\//,
|
|
5087
|
+
/^@commitlint\//
|
|
5088
|
+
];
|
|
5089
|
+
return deps.filter(
|
|
5090
|
+
(d) => !trivial.has(d) && !trivialPatterns.some((p) => p.test(d))
|
|
5091
|
+
);
|
|
5092
|
+
} catch {
|
|
5093
|
+
return [];
|
|
5094
|
+
}
|
|
5095
|
+
}
|
|
5096
|
+
async function recommendCommand() {
|
|
5097
|
+
const proceed = await select3({
|
|
5098
|
+
message: "Search public repos for relevant skills to add to this project?",
|
|
4897
5099
|
choices: [
|
|
4898
|
-
{ name: "Yes,
|
|
4899
|
-
{ name: "No,
|
|
5100
|
+
{ name: "Yes, find skills for my project", value: true },
|
|
5101
|
+
{ name: "No, cancel", value: false }
|
|
4900
5102
|
]
|
|
4901
5103
|
});
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
async function promptReviewMethod() {
|
|
4905
|
-
const available = detectAvailableEditors();
|
|
4906
|
-
if (available.length === 1) return "terminal";
|
|
4907
|
-
const choices = available.map((method) => {
|
|
4908
|
-
switch (method) {
|
|
4909
|
-
case "cursor":
|
|
4910
|
-
return { name: "Cursor (diff view)", value: "cursor" };
|
|
4911
|
-
case "vscode":
|
|
4912
|
-
return { name: "VS Code (diff view)", value: "vscode" };
|
|
4913
|
-
case "terminal":
|
|
4914
|
-
return { name: "Terminal", value: "terminal" };
|
|
4915
|
-
}
|
|
4916
|
-
});
|
|
4917
|
-
return select2({ message: "How would you like to review the changes?", choices });
|
|
4918
|
-
}
|
|
4919
|
-
async function openReview(method, stagedFiles) {
|
|
4920
|
-
if (method === "cursor" || method === "vscode") {
|
|
4921
|
-
openDiffsInEditor(method, stagedFiles.map((f) => ({
|
|
4922
|
-
originalPath: f.originalPath,
|
|
4923
|
-
proposedPath: f.proposedPath
|
|
4924
|
-
})));
|
|
4925
|
-
console.log(chalk5.dim(" Diffs opened in your editor.\n"));
|
|
5104
|
+
if (!proceed) {
|
|
5105
|
+
console.log(chalk6.dim(" Cancelled.\n"));
|
|
4926
5106
|
return;
|
|
4927
5107
|
}
|
|
4928
|
-
|
|
4929
|
-
const proposed = fs20.readFileSync(file.proposedPath, "utf-8");
|
|
4930
|
-
const current = file.currentPath ? fs20.readFileSync(file.currentPath, "utf-8") : "";
|
|
4931
|
-
const patch = createTwoFilesPatch(
|
|
4932
|
-
file.isNew ? "/dev/null" : file.relativePath,
|
|
4933
|
-
file.relativePath,
|
|
4934
|
-
current,
|
|
4935
|
-
proposed
|
|
4936
|
-
);
|
|
4937
|
-
let added = 0, removed = 0;
|
|
4938
|
-
for (const line of patch.split("\n")) {
|
|
4939
|
-
if (line.startsWith("+") && !line.startsWith("+++")) added++;
|
|
4940
|
-
if (line.startsWith("-") && !line.startsWith("---")) removed++;
|
|
4941
|
-
}
|
|
4942
|
-
return {
|
|
4943
|
-
relativePath: file.relativePath,
|
|
4944
|
-
isNew: file.isNew,
|
|
4945
|
-
added,
|
|
4946
|
-
removed,
|
|
4947
|
-
lines: proposed.split("\n").length,
|
|
4948
|
-
patch
|
|
4949
|
-
};
|
|
4950
|
-
});
|
|
4951
|
-
await interactiveDiffExplorer(fileInfos);
|
|
5108
|
+
await searchAndInstallSkills();
|
|
4952
5109
|
}
|
|
4953
|
-
async function
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
5110
|
+
async function searchAndInstallSkills() {
|
|
5111
|
+
const fingerprint = collectFingerprint(process.cwd());
|
|
5112
|
+
const platforms = detectLocalPlatforms();
|
|
5113
|
+
const installedSkills = getInstalledSkills();
|
|
5114
|
+
const technologies = [...new Set([
|
|
5115
|
+
...fingerprint.languages,
|
|
5116
|
+
...fingerprint.frameworks,
|
|
5117
|
+
...extractTopDeps()
|
|
5118
|
+
].filter(Boolean))];
|
|
5119
|
+
if (technologies.length === 0) {
|
|
5120
|
+
console.log(chalk6.yellow("Could not detect any languages or dependencies. Try running from a project root."));
|
|
5121
|
+
throw new Error("__exit__");
|
|
4962
5122
|
}
|
|
4963
|
-
const
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
return (stdout.rows || 24) - 4;
|
|
5123
|
+
const primaryPlatform = platforms.includes("claude") ? "claude" : platforms[0];
|
|
5124
|
+
const searchSpinner = ora2("Searching skill registries...").start();
|
|
5125
|
+
const allCandidates = await searchAllProviders(technologies, primaryPlatform);
|
|
5126
|
+
if (!allCandidates.length) {
|
|
5127
|
+
searchSpinner.succeed("No skills found matching your tech stack.");
|
|
5128
|
+
return;
|
|
4970
5129
|
}
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
const f = files[i];
|
|
4977
|
-
const ptr = i === cursor ? chalk5.cyan(">") : " ";
|
|
4978
|
-
const icon = f.isNew ? chalk5.green("+") : chalk5.yellow("~");
|
|
4979
|
-
const stats = f.isNew ? chalk5.dim(`${f.lines} lines`) : `${chalk5.green(`+${f.added}`)} ${chalk5.red(`-${f.removed}`)}`;
|
|
4980
|
-
lines.push(` ${ptr} ${icon} ${f.relativePath} ${stats}`);
|
|
4981
|
-
}
|
|
4982
|
-
lines.push("");
|
|
4983
|
-
lines.push(chalk5.dim(" \u2191\u2193 navigate \u23CE view diff q done"));
|
|
4984
|
-
return lines.join("\n");
|
|
5130
|
+
const newCandidates = allCandidates.filter((c) => !installedSkills.has(c.slug.toLowerCase()));
|
|
5131
|
+
const filteredCount = allCandidates.length - newCandidates.length;
|
|
5132
|
+
if (!newCandidates.length) {
|
|
5133
|
+
searchSpinner.succeed(`Found ${allCandidates.length} skills \u2014 all already installed.`);
|
|
5134
|
+
return;
|
|
4985
5135
|
}
|
|
4986
|
-
|
|
4987
|
-
|
|
5136
|
+
searchSpinner.succeed(
|
|
5137
|
+
`Found ${allCandidates.length} skills` + (filteredCount > 0 ? chalk6.dim(` (${filteredCount} already installed)`) : "")
|
|
5138
|
+
);
|
|
5139
|
+
let results;
|
|
5140
|
+
const config = loadConfig();
|
|
5141
|
+
if (config) {
|
|
5142
|
+
const scoreSpinner = ora2("Scoring relevance for your project...").start();
|
|
5143
|
+
try {
|
|
5144
|
+
const projectContext = buildProjectContext(process.cwd());
|
|
5145
|
+
results = await scoreWithLLM2(newCandidates, projectContext, technologies);
|
|
5146
|
+
if (results.length === 0) {
|
|
5147
|
+
scoreSpinner.succeed("No highly relevant skills found for your specific project.");
|
|
5148
|
+
return;
|
|
5149
|
+
}
|
|
5150
|
+
scoreSpinner.succeed(`${results.length} relevant skill${results.length > 1 ? "s" : ""} for your project`);
|
|
5151
|
+
} catch {
|
|
5152
|
+
scoreSpinner.warn("Could not score relevance \u2014 showing top results");
|
|
5153
|
+
results = newCandidates.slice(0, 20);
|
|
5154
|
+
}
|
|
5155
|
+
} else {
|
|
5156
|
+
results = newCandidates.slice(0, 20);
|
|
5157
|
+
}
|
|
5158
|
+
const fetchSpinner = ora2("Verifying skill availability...").start();
|
|
5159
|
+
const contentMap = /* @__PURE__ */ new Map();
|
|
5160
|
+
await Promise.all(results.map(async (rec) => {
|
|
5161
|
+
const content = await fetchSkillContent(rec);
|
|
5162
|
+
if (content) contentMap.set(rec.slug, content);
|
|
5163
|
+
}));
|
|
5164
|
+
const available = results.filter((r) => contentMap.has(r.slug));
|
|
5165
|
+
if (!available.length) {
|
|
5166
|
+
fetchSpinner.fail("No installable skills found \u2014 content could not be fetched.");
|
|
5167
|
+
return;
|
|
5168
|
+
}
|
|
5169
|
+
const unavailableCount = results.length - available.length;
|
|
5170
|
+
fetchSpinner.succeed(
|
|
5171
|
+
`${available.length} installable skill${available.length > 1 ? "s" : ""}` + (unavailableCount > 0 ? chalk6.dim(` (${unavailableCount} unavailable)`) : "")
|
|
5172
|
+
);
|
|
5173
|
+
const selected = await interactiveSelect2(available);
|
|
5174
|
+
if (selected?.length) {
|
|
5175
|
+
await installSkills(selected, platforms, contentMap);
|
|
5176
|
+
}
|
|
5177
|
+
}
|
|
5178
|
+
async function interactiveSelect2(recs) {
|
|
5179
|
+
if (!process.stdin.isTTY) {
|
|
5180
|
+
printSkills(recs);
|
|
5181
|
+
return null;
|
|
5182
|
+
}
|
|
5183
|
+
const selected = /* @__PURE__ */ new Set();
|
|
5184
|
+
let cursor = 0;
|
|
5185
|
+
const { stdin, stdout } = process;
|
|
5186
|
+
let lineCount = 0;
|
|
5187
|
+
const hasScores = recs.some((r) => r.score > 0);
|
|
5188
|
+
function render() {
|
|
4988
5189
|
const lines = [];
|
|
4989
|
-
|
|
4990
|
-
lines.push(
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5190
|
+
lines.push(chalk6.bold(" Skills"));
|
|
5191
|
+
lines.push("");
|
|
5192
|
+
if (hasScores) {
|
|
5193
|
+
lines.push(` ${chalk6.dim("Score".padEnd(7))} ${chalk6.dim("Name".padEnd(28))} ${chalk6.dim("Why")}`);
|
|
5194
|
+
} else {
|
|
5195
|
+
lines.push(` ${chalk6.dim("Name".padEnd(30))} ${chalk6.dim("Technology".padEnd(18))} ${chalk6.dim("Source")}`);
|
|
5196
|
+
}
|
|
5197
|
+
lines.push(chalk6.dim(" " + "\u2500".repeat(70)));
|
|
5198
|
+
for (let i = 0; i < recs.length; i++) {
|
|
5199
|
+
const rec = recs[i];
|
|
5200
|
+
const check = selected.has(i) ? chalk6.green("[x]") : "[ ]";
|
|
5201
|
+
const ptr = i === cursor ? chalk6.cyan(">") : " ";
|
|
5202
|
+
if (hasScores) {
|
|
5203
|
+
const scoreColor = rec.score >= 90 ? chalk6.green : rec.score >= 70 ? chalk6.yellow : chalk6.dim;
|
|
5204
|
+
lines.push(` ${ptr} ${check} ${scoreColor(String(rec.score).padStart(3))} ${rec.name.padEnd(26)} ${chalk6.dim(rec.reason.slice(0, 40))}`);
|
|
5003
5205
|
} else {
|
|
5004
|
-
lines.push(
|
|
5206
|
+
lines.push(` ${ptr} ${check} ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk6.dim(rec.source_url || "")}`);
|
|
5005
5207
|
}
|
|
5006
5208
|
}
|
|
5007
|
-
const totalBody = bodyLines.length;
|
|
5008
|
-
if (totalBody > maxVisible) {
|
|
5009
|
-
const pct = Math.round((scrollOffset + maxVisible) / totalBody * 100);
|
|
5010
|
-
lines.push(chalk5.dim(` \u2500\u2500 ${Math.min(pct, 100)}% \u2500\u2500`));
|
|
5011
|
-
}
|
|
5012
5209
|
lines.push("");
|
|
5013
|
-
lines.push(
|
|
5210
|
+
lines.push(chalk6.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q cancel"));
|
|
5014
5211
|
return lines.join("\n");
|
|
5015
5212
|
}
|
|
5016
5213
|
function draw(initial) {
|
|
@@ -5018,7 +5215,7 @@ async function interactiveDiffExplorer(files) {
|
|
|
5018
5215
|
stdout.write(`\x1B[${lineCount}A`);
|
|
5019
5216
|
}
|
|
5020
5217
|
stdout.write("\x1B[0J");
|
|
5021
|
-
const output =
|
|
5218
|
+
const output = render();
|
|
5022
5219
|
stdout.write(output + "\n");
|
|
5023
5220
|
lineCount = output.split("\n").length;
|
|
5024
5221
|
}
|
|
@@ -5034,292 +5231,261 @@ async function interactiveDiffExplorer(files) {
|
|
|
5034
5231
|
stdin.pause();
|
|
5035
5232
|
}
|
|
5036
5233
|
function onData(key) {
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
viewing = cursor;
|
|
5076
|
-
scrollOffset = 0;
|
|
5077
|
-
draw(false);
|
|
5078
|
-
break;
|
|
5079
|
-
case "q":
|
|
5080
|
-
case "":
|
|
5081
|
-
cleanup();
|
|
5082
|
-
console.log("");
|
|
5083
|
-
resolve2();
|
|
5084
|
-
break;
|
|
5085
|
-
}
|
|
5234
|
+
switch (key) {
|
|
5235
|
+
case "\x1B[A":
|
|
5236
|
+
cursor = (cursor - 1 + recs.length) % recs.length;
|
|
5237
|
+
draw(false);
|
|
5238
|
+
break;
|
|
5239
|
+
case "\x1B[B":
|
|
5240
|
+
cursor = (cursor + 1) % recs.length;
|
|
5241
|
+
draw(false);
|
|
5242
|
+
break;
|
|
5243
|
+
case " ":
|
|
5244
|
+
selected.has(cursor) ? selected.delete(cursor) : selected.add(cursor);
|
|
5245
|
+
draw(false);
|
|
5246
|
+
break;
|
|
5247
|
+
case "a":
|
|
5248
|
+
recs.forEach((_, i) => selected.add(i));
|
|
5249
|
+
draw(false);
|
|
5250
|
+
break;
|
|
5251
|
+
case "n":
|
|
5252
|
+
selected.clear();
|
|
5253
|
+
draw(false);
|
|
5254
|
+
break;
|
|
5255
|
+
case "\r":
|
|
5256
|
+
case "\n":
|
|
5257
|
+
cleanup();
|
|
5258
|
+
if (selected.size === 0) {
|
|
5259
|
+
console.log(chalk6.dim("\n No skills selected.\n"));
|
|
5260
|
+
resolve2(null);
|
|
5261
|
+
} else {
|
|
5262
|
+
resolve2(Array.from(selected).sort().map((i) => recs[i]));
|
|
5263
|
+
}
|
|
5264
|
+
break;
|
|
5265
|
+
case "q":
|
|
5266
|
+
case "\x1B":
|
|
5267
|
+
case "":
|
|
5268
|
+
cleanup();
|
|
5269
|
+
console.log(chalk6.dim("\n Cancelled.\n"));
|
|
5270
|
+
resolve2(null);
|
|
5271
|
+
break;
|
|
5086
5272
|
}
|
|
5087
5273
|
}
|
|
5088
5274
|
stdin.on("data", onData);
|
|
5089
5275
|
});
|
|
5090
5276
|
}
|
|
5091
|
-
async function
|
|
5092
|
-
return
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
console.log("");
|
|
5107
|
-
console.log(chalk5.bold(" Proposed changes:\n"));
|
|
5108
|
-
const getDescription = (filePath) => {
|
|
5109
|
-
return fileDescriptions?.[filePath];
|
|
5110
|
-
};
|
|
5111
|
-
if (claude) {
|
|
5112
|
-
if (claude.claudeMd) {
|
|
5113
|
-
const icon = fs20.existsSync("CLAUDE.md") ? chalk5.yellow("~") : chalk5.green("+");
|
|
5114
|
-
const desc = getDescription("CLAUDE.md");
|
|
5115
|
-
console.log(` ${icon} ${chalk5.bold("CLAUDE.md")}`);
|
|
5116
|
-
if (desc) console.log(chalk5.dim(` ${desc}`));
|
|
5117
|
-
console.log("");
|
|
5118
|
-
}
|
|
5119
|
-
const skills = claude.skills;
|
|
5120
|
-
if (Array.isArray(skills) && skills.length > 0) {
|
|
5121
|
-
for (const skill of skills) {
|
|
5122
|
-
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
5123
|
-
const icon = fs20.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
5124
|
-
const desc = getDescription(skillPath);
|
|
5125
|
-
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
5126
|
-
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
5127
|
-
console.log("");
|
|
5277
|
+
async function fetchSkillContent(rec) {
|
|
5278
|
+
if (!rec.source_url) return null;
|
|
5279
|
+
const repoPath = rec.source_url.replace("https://github.com/", "");
|
|
5280
|
+
const candidates = [
|
|
5281
|
+
`https://raw.githubusercontent.com/${repoPath}/HEAD/skills/${rec.slug}/SKILL.md`,
|
|
5282
|
+
`https://raw.githubusercontent.com/${repoPath}/HEAD/${rec.slug}/SKILL.md`,
|
|
5283
|
+
`https://raw.githubusercontent.com/${repoPath}/HEAD/.claude/skills/${rec.slug}/SKILL.md`,
|
|
5284
|
+
`https://raw.githubusercontent.com/${repoPath}/HEAD/.agents/skills/${rec.slug}/SKILL.md`
|
|
5285
|
+
];
|
|
5286
|
+
for (const url of candidates) {
|
|
5287
|
+
try {
|
|
5288
|
+
const resp = await fetch(url, { signal: AbortSignal.timeout(1e4) });
|
|
5289
|
+
if (resp.ok) {
|
|
5290
|
+
const text = await resp.text();
|
|
5291
|
+
if (text.length > 20) return text;
|
|
5128
5292
|
}
|
|
5293
|
+
} catch {
|
|
5129
5294
|
}
|
|
5130
5295
|
}
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
const icon = fs20.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
5145
|
-
const desc = getDescription(skillPath);
|
|
5146
|
-
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
5147
|
-
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
5148
|
-
console.log("");
|
|
5296
|
+
try {
|
|
5297
|
+
const resp = await fetch(
|
|
5298
|
+
`https://api.github.com/repos/${repoPath}/git/trees/HEAD?recursive=1`,
|
|
5299
|
+
{ signal: AbortSignal.timeout(1e4) }
|
|
5300
|
+
);
|
|
5301
|
+
if (resp.ok) {
|
|
5302
|
+
const tree = await resp.json();
|
|
5303
|
+
const needle = `${rec.slug}/SKILL.md`;
|
|
5304
|
+
const match = tree.tree?.find((f) => f.path.endsWith(needle));
|
|
5305
|
+
if (match) {
|
|
5306
|
+
const rawUrl = `https://raw.githubusercontent.com/${repoPath}/HEAD/${match.path}`;
|
|
5307
|
+
const contentResp = await fetch(rawUrl, { signal: AbortSignal.timeout(1e4) });
|
|
5308
|
+
if (contentResp.ok) return await contentResp.text();
|
|
5149
5309
|
}
|
|
5150
5310
|
}
|
|
5311
|
+
} catch {
|
|
5151
5312
|
}
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5313
|
+
return null;
|
|
5314
|
+
}
|
|
5315
|
+
async function installSkills(recs, platforms, contentMap) {
|
|
5316
|
+
const spinner = ora2(`Installing ${recs.length} skill${recs.length > 1 ? "s" : ""}...`).start();
|
|
5317
|
+
const installed = [];
|
|
5318
|
+
for (const rec of recs) {
|
|
5319
|
+
const content = contentMap.get(rec.slug);
|
|
5320
|
+
if (!content) continue;
|
|
5321
|
+
for (const platform of platforms) {
|
|
5322
|
+
const skillPath = getSkillPath(platform, rec.slug);
|
|
5323
|
+
const fullPath = join8(process.cwd(), skillPath);
|
|
5324
|
+
mkdirSync(dirname2(fullPath), { recursive: true });
|
|
5325
|
+
writeFileSync(fullPath, content, "utf-8");
|
|
5326
|
+
installed.push(`[${platform}] ${skillPath}`);
|
|
5159
5327
|
}
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
const desc = getDescription(skillPath);
|
|
5166
|
-
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
5167
|
-
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
5168
|
-
console.log("");
|
|
5169
|
-
}
|
|
5170
|
-
}
|
|
5171
|
-
const rules = cursor.rules;
|
|
5172
|
-
if (Array.isArray(rules) && rules.length > 0) {
|
|
5173
|
-
for (const rule of rules) {
|
|
5174
|
-
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
5175
|
-
const icon = fs20.existsSync(rulePath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
5176
|
-
const desc = getDescription(rulePath);
|
|
5177
|
-
console.log(` ${icon} ${chalk5.bold(rulePath)}`);
|
|
5178
|
-
if (desc) {
|
|
5179
|
-
console.log(chalk5.dim(` ${desc}`));
|
|
5180
|
-
} else {
|
|
5181
|
-
const firstLine = rule.content.split("\n").filter((l) => l.trim() && !l.trim().startsWith("#"))[0];
|
|
5182
|
-
if (firstLine) console.log(chalk5.dim(` ${firstLine.trim().slice(0, 80)}`));
|
|
5183
|
-
}
|
|
5184
|
-
console.log("");
|
|
5185
|
-
}
|
|
5186
|
-
}
|
|
5187
|
-
}
|
|
5188
|
-
if (!codex && !fs20.existsSync("AGENTS.md")) {
|
|
5189
|
-
console.log(` ${chalk5.green("+")} ${chalk5.bold("AGENTS.md")}`);
|
|
5190
|
-
console.log(chalk5.dim(" Cross-agent coordination file"));
|
|
5191
|
-
console.log("");
|
|
5192
|
-
}
|
|
5193
|
-
if (Array.isArray(deletions) && deletions.length > 0) {
|
|
5194
|
-
for (const del of deletions) {
|
|
5195
|
-
console.log(` ${chalk5.red("-")} ${chalk5.bold(del.filePath)}`);
|
|
5196
|
-
console.log(chalk5.dim(` ${del.reason}`));
|
|
5197
|
-
console.log("");
|
|
5328
|
+
}
|
|
5329
|
+
if (installed.length > 0) {
|
|
5330
|
+
spinner.succeed(`Installed ${installed.length} file${installed.length > 1 ? "s" : ""}`);
|
|
5331
|
+
for (const p of installed) {
|
|
5332
|
+
console.log(chalk6.green(` \u2713 ${p}`));
|
|
5198
5333
|
}
|
|
5334
|
+
} else {
|
|
5335
|
+
spinner.fail("No skills were installed");
|
|
5199
5336
|
}
|
|
5200
|
-
console.log(` ${chalk5.green("+")} ${chalk5.dim("new")} ${chalk5.yellow("~")} ${chalk5.dim("modified")} ${chalk5.red("-")} ${chalk5.dim("removed")}`);
|
|
5201
5337
|
console.log("");
|
|
5202
5338
|
}
|
|
5203
|
-
function
|
|
5204
|
-
const
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
settings = JSON.parse(fs20.readFileSync(settingsPath, "utf-8"));
|
|
5209
|
-
}
|
|
5210
|
-
} catch {
|
|
5211
|
-
}
|
|
5212
|
-
const permissions = settings.permissions ?? {};
|
|
5213
|
-
const allow = permissions.allow;
|
|
5214
|
-
if (Array.isArray(allow) && allow.length > 0) return;
|
|
5215
|
-
permissions.allow = [
|
|
5216
|
-
"Bash(npm run *)",
|
|
5217
|
-
"Bash(npx vitest *)",
|
|
5218
|
-
"Bash(npx tsc *)",
|
|
5219
|
-
"Bash(git *)"
|
|
5220
|
-
];
|
|
5221
|
-
settings.permissions = permissions;
|
|
5222
|
-
if (!fs20.existsSync(".claude")) fs20.mkdirSync(".claude", { recursive: true });
|
|
5223
|
-
fs20.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5224
|
-
}
|
|
5225
|
-
|
|
5226
|
-
// src/commands/undo.ts
|
|
5227
|
-
import chalk6 from "chalk";
|
|
5228
|
-
import ora3 from "ora";
|
|
5229
|
-
function undoCommand() {
|
|
5230
|
-
const spinner = ora3("Reverting setup...").start();
|
|
5231
|
-
try {
|
|
5232
|
-
const { restored, removed } = undoSetup();
|
|
5233
|
-
if (restored.length === 0 && removed.length === 0) {
|
|
5234
|
-
spinner.info("Nothing to undo.");
|
|
5235
|
-
return;
|
|
5236
|
-
}
|
|
5237
|
-
spinner.succeed("Setup reverted successfully.\n");
|
|
5238
|
-
if (restored.length > 0) {
|
|
5239
|
-
console.log(chalk6.cyan(" Restored from backup:"));
|
|
5240
|
-
for (const file of restored) {
|
|
5241
|
-
console.log(` ${chalk6.green("\u21A9")} ${file}`);
|
|
5242
|
-
}
|
|
5243
|
-
}
|
|
5244
|
-
if (removed.length > 0) {
|
|
5245
|
-
console.log(chalk6.cyan(" Removed:"));
|
|
5246
|
-
for (const file of removed) {
|
|
5247
|
-
console.log(` ${chalk6.red("\u2717")} ${file}`);
|
|
5248
|
-
}
|
|
5249
|
-
}
|
|
5250
|
-
console.log("");
|
|
5251
|
-
} catch (err) {
|
|
5252
|
-
spinner.fail(chalk6.red(err instanceof Error ? err.message : "Undo failed"));
|
|
5253
|
-
throw new Error("__exit__");
|
|
5254
|
-
}
|
|
5255
|
-
}
|
|
5256
|
-
|
|
5257
|
-
// src/commands/status.ts
|
|
5258
|
-
import chalk7 from "chalk";
|
|
5259
|
-
import fs21 from "fs";
|
|
5260
|
-
async function statusCommand(options) {
|
|
5261
|
-
const config = loadConfig();
|
|
5262
|
-
const manifest = readManifest();
|
|
5263
|
-
if (options.json) {
|
|
5264
|
-
console.log(JSON.stringify({
|
|
5265
|
-
configured: !!config,
|
|
5266
|
-
provider: config?.provider,
|
|
5267
|
-
model: config?.model,
|
|
5268
|
-
manifest
|
|
5269
|
-
}, null, 2));
|
|
5270
|
-
return;
|
|
5271
|
-
}
|
|
5272
|
-
console.log(chalk7.bold("\nCaliber Status\n"));
|
|
5273
|
-
if (config) {
|
|
5274
|
-
console.log(` LLM: ${chalk7.green(config.provider)} (${config.model})`);
|
|
5339
|
+
function printSkills(recs) {
|
|
5340
|
+
const hasScores = recs.some((r) => r.score > 0);
|
|
5341
|
+
console.log(chalk6.bold("\n Skills\n"));
|
|
5342
|
+
if (hasScores) {
|
|
5343
|
+
console.log(` ${chalk6.dim("Score".padEnd(7))} ${chalk6.dim("Name".padEnd(28))} ${chalk6.dim("Why")}`);
|
|
5275
5344
|
} else {
|
|
5276
|
-
console.log(`
|
|
5277
|
-
}
|
|
5278
|
-
if (!manifest) {
|
|
5279
|
-
console.log(` Setup: ${chalk7.dim("No setup applied")}`);
|
|
5280
|
-
console.log(chalk7.dim("\n Run ") + chalk7.hex("#83D1EB")("caliber onboard") + chalk7.dim(" to get started.\n"));
|
|
5281
|
-
return;
|
|
5345
|
+
console.log(` ${chalk6.dim("Name".padEnd(30))} ${chalk6.dim("Technology".padEnd(18))} ${chalk6.dim("Source")}`);
|
|
5282
5346
|
}
|
|
5283
|
-
console.log(
|
|
5284
|
-
for (const
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5347
|
+
console.log(chalk6.dim(" " + "\u2500".repeat(70)));
|
|
5348
|
+
for (const rec of recs) {
|
|
5349
|
+
if (hasScores) {
|
|
5350
|
+
console.log(` ${String(rec.score).padStart(3)} ${rec.name.padEnd(26)} ${chalk6.dim(rec.reason.slice(0, 50))}`);
|
|
5351
|
+
} else {
|
|
5352
|
+
console.log(` ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk6.dim(rec.source_url || "")}`);
|
|
5353
|
+
}
|
|
5288
5354
|
}
|
|
5289
5355
|
console.log("");
|
|
5290
5356
|
}
|
|
5291
5357
|
|
|
5292
|
-
// src/commands/
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5358
|
+
// src/commands/onboard.ts
|
|
5359
|
+
async function initCommand(options) {
|
|
5360
|
+
const brand = chalk7.hex("#EB9D83");
|
|
5361
|
+
const title = chalk7.hex("#83D1EB");
|
|
5362
|
+
console.log(brand.bold(`
|
|
5363
|
+
\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
|
|
5364
|
+
\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
|
|
5365
|
+
\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
5366
|
+
\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
5367
|
+
\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
|
|
5368
|
+
\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
|
|
5369
|
+
`));
|
|
5370
|
+
console.log(chalk7.dim(" Onboard your project for AI-assisted development\n"));
|
|
5371
|
+
console.log(title.bold(" Welcome to Caliber\n"));
|
|
5372
|
+
console.log(chalk7.dim(" Caliber analyzes your codebase and creates tailored config files"));
|
|
5373
|
+
console.log(chalk7.dim(" so your AI coding agents understand your project from day one.\n"));
|
|
5374
|
+
console.log(title.bold(" How onboarding works:\n"));
|
|
5375
|
+
console.log(chalk7.dim(" 1. Connect Set up your LLM provider"));
|
|
5376
|
+
console.log(chalk7.dim(" 2. Discover Analyze your code, dependencies, and structure"));
|
|
5377
|
+
console.log(chalk7.dim(" 3. Generate Create config files tailored to your project"));
|
|
5378
|
+
console.log(chalk7.dim(" 4. Review Preview, refine, and apply the changes"));
|
|
5379
|
+
console.log(chalk7.dim(" 5. Enhance Discover MCP servers for your tools"));
|
|
5380
|
+
console.log(chalk7.dim(" 6. Skills Browse community skills for your stack\n"));
|
|
5381
|
+
console.log(title.bold(" Step 1/6 \u2014 Connect your LLM\n"));
|
|
5382
|
+
let config = loadConfig();
|
|
5298
5383
|
if (!config) {
|
|
5299
|
-
console.log(
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5384
|
+
console.log(chalk7.dim(" No LLM provider set yet. Choose how to run Caliber:\n"));
|
|
5385
|
+
try {
|
|
5386
|
+
await runInteractiveProviderSetup({
|
|
5387
|
+
selectMessage: "How do you want to use Caliber? (choose LLM provider)"
|
|
5388
|
+
});
|
|
5389
|
+
} catch (err) {
|
|
5390
|
+
if (err.message === "__exit__") throw err;
|
|
5391
|
+
throw err;
|
|
5392
|
+
}
|
|
5393
|
+
config = loadConfig();
|
|
5394
|
+
if (!config) {
|
|
5395
|
+
console.log(chalk7.red(" Setup was cancelled or failed.\n"));
|
|
5396
|
+
throw new Error("__exit__");
|
|
5397
|
+
}
|
|
5398
|
+
console.log(chalk7.green(" \u2713 Provider saved. Let's continue.\n"));
|
|
5306
5399
|
}
|
|
5307
|
-
const
|
|
5308
|
-
const
|
|
5400
|
+
const displayModel = config.model === "default" && config.provider === "claude-cli" ? process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)" : config.model;
|
|
5401
|
+
const fastModel = getFastModel();
|
|
5402
|
+
const modelLine = fastModel ? ` Provider: ${config.provider} | Model: ${displayModel} | Scan: ${fastModel}` : ` Provider: ${config.provider} | Model: ${displayModel}`;
|
|
5403
|
+
console.log(chalk7.dim(modelLine + "\n"));
|
|
5404
|
+
console.log(title.bold(" Step 2/6 \u2014 Discover your project\n"));
|
|
5405
|
+
console.log(chalk7.dim(" Learning about your languages, dependencies, structure, and existing configs.\n"));
|
|
5406
|
+
const spinner = ora3("Analyzing project...").start();
|
|
5309
5407
|
const fingerprint = collectFingerprint(process.cwd());
|
|
5310
5408
|
await enrichFingerprintWithLLM(fingerprint, process.cwd());
|
|
5311
5409
|
spinner.succeed("Project analyzed");
|
|
5410
|
+
console.log(chalk7.dim(` Languages: ${fingerprint.languages.join(", ") || "none detected"}`));
|
|
5411
|
+
console.log(chalk7.dim(` Files: ${fingerprint.fileTree.length} found
|
|
5412
|
+
`));
|
|
5413
|
+
const targetAgent = options.agent || await promptAgent();
|
|
5414
|
+
const preScore = computeLocalScore(process.cwd(), targetAgent);
|
|
5415
|
+
const failingForDismissal = preScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
|
|
5416
|
+
if (failingForDismissal.length > 0) {
|
|
5417
|
+
const newDismissals = await evaluateDismissals(failingForDismissal, fingerprint);
|
|
5418
|
+
if (newDismissals.length > 0) {
|
|
5419
|
+
const existing = readDismissedChecks();
|
|
5420
|
+
const existingIds = new Set(existing.map((d) => d.id));
|
|
5421
|
+
const merged = [...existing, ...newDismissals.filter((d) => !existingIds.has(d.id))];
|
|
5422
|
+
writeDismissedChecks(merged);
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5312
5425
|
const baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
5313
5426
|
displayScoreSummary(baselineScore);
|
|
5314
|
-
const
|
|
5427
|
+
const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length || fingerprint.existingConfigs.agentsMd);
|
|
5428
|
+
const NON_LLM_CHECKS = /* @__PURE__ */ new Set(["hooks_configured", "agents_md_exists", "permissions_configured", "mcp_servers"]);
|
|
5429
|
+
if (hasExistingConfig && baselineScore.score === 100) {
|
|
5430
|
+
console.log(chalk7.bold.green(" Your setup is already optimal \u2014 nothing to change.\n"));
|
|
5431
|
+
console.log(chalk7.dim(" Run ") + chalk7.hex("#83D1EB")("caliber onboard --force") + chalk7.dim(" to regenerate anyway.\n"));
|
|
5432
|
+
if (!options.force) return;
|
|
5433
|
+
}
|
|
5434
|
+
const allFailingChecks = baselineScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
|
|
5435
|
+
const llmFixableChecks = allFailingChecks.filter((c) => !NON_LLM_CHECKS.has(c.id));
|
|
5436
|
+
if (hasExistingConfig && llmFixableChecks.length === 0 && allFailingChecks.length > 0 && !options.force) {
|
|
5437
|
+
console.log(chalk7.bold.green("\n Your config is fully optimized for LLM generation.\n"));
|
|
5438
|
+
console.log(chalk7.dim(" Remaining items need CLI actions:\n"));
|
|
5439
|
+
for (const check of allFailingChecks) {
|
|
5440
|
+
console.log(chalk7.dim(` \u2022 ${check.name}`));
|
|
5441
|
+
if (check.suggestion) {
|
|
5442
|
+
console.log(` ${chalk7.hex("#83D1EB")(check.suggestion)}`);
|
|
5443
|
+
}
|
|
5444
|
+
}
|
|
5445
|
+
console.log("");
|
|
5446
|
+
console.log(chalk7.dim(" Run ") + chalk7.hex("#83D1EB")("caliber onboard --force") + chalk7.dim(" to regenerate anyway.\n"));
|
|
5447
|
+
return;
|
|
5448
|
+
}
|
|
5449
|
+
const isEmpty = fingerprint.fileTree.length < 3;
|
|
5450
|
+
if (isEmpty) {
|
|
5451
|
+
fingerprint.description = await promptInput3("What will you build in this project?");
|
|
5452
|
+
}
|
|
5453
|
+
let failingChecks;
|
|
5454
|
+
let passingChecks;
|
|
5455
|
+
let currentScore;
|
|
5456
|
+
if (hasExistingConfig && baselineScore.score >= 95 && !options.force) {
|
|
5457
|
+
failingChecks = llmFixableChecks.map((c) => ({ name: c.name, suggestion: c.suggestion }));
|
|
5458
|
+
passingChecks = baselineScore.checks.filter((c) => c.passed).map((c) => ({ name: c.name }));
|
|
5459
|
+
currentScore = baselineScore.score;
|
|
5460
|
+
if (failingChecks.length > 0) {
|
|
5461
|
+
console.log(title.bold(" Step 3/6 \u2014 Fine-tuning\n"));
|
|
5462
|
+
console.log(chalk7.dim(` Your setup scores ${baselineScore.score}/100 \u2014 fixing ${failingChecks.length} remaining issue${failingChecks.length === 1 ? "" : "s"}:
|
|
5463
|
+
`));
|
|
5464
|
+
for (const check of failingChecks) {
|
|
5465
|
+
console.log(chalk7.dim(` \u2022 ${check.name}`));
|
|
5466
|
+
}
|
|
5467
|
+
console.log("");
|
|
5468
|
+
}
|
|
5469
|
+
} else if (hasExistingConfig) {
|
|
5470
|
+
console.log(title.bold(" Step 3/6 \u2014 Improve your setup\n"));
|
|
5471
|
+
console.log(chalk7.dim(" Reviewing your existing configs against your codebase"));
|
|
5472
|
+
console.log(chalk7.dim(" and preparing improvements.\n"));
|
|
5473
|
+
} else {
|
|
5474
|
+
console.log(title.bold(" Step 3/6 \u2014 Build your agent setup\n"));
|
|
5475
|
+
console.log(chalk7.dim(" Creating config files tailored to your project.\n"));
|
|
5476
|
+
}
|
|
5477
|
+
console.log(chalk7.dim(" This can take a couple of minutes depending on your model and provider.\n"));
|
|
5478
|
+
const genStartTime = Date.now();
|
|
5479
|
+
const genSpinner = ora3("Generating setup...").start();
|
|
5315
5480
|
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
|
|
5316
5481
|
genMessages.start();
|
|
5317
5482
|
let generatedSetup = null;
|
|
5483
|
+
let rawOutput;
|
|
5318
5484
|
try {
|
|
5319
5485
|
const result = await generateSetup(
|
|
5320
5486
|
fingerprint,
|
|
5321
5487
|
targetAgent,
|
|
5322
|
-
|
|
5488
|
+
fingerprint.description,
|
|
5323
5489
|
{
|
|
5324
5490
|
onStatus: (status) => {
|
|
5325
5491
|
genMessages.handleServerStatus(status);
|
|
@@ -5331,796 +5497,692 @@ async function regenerateCommand(options) {
|
|
|
5331
5497
|
genMessages.stop();
|
|
5332
5498
|
genSpinner.fail(`Generation error: ${error}`);
|
|
5333
5499
|
}
|
|
5334
|
-
}
|
|
5500
|
+
},
|
|
5501
|
+
failingChecks,
|
|
5502
|
+
currentScore,
|
|
5503
|
+
passingChecks
|
|
5335
5504
|
);
|
|
5336
|
-
if (!generatedSetup)
|
|
5505
|
+
if (!generatedSetup) {
|
|
5506
|
+
generatedSetup = result.setup;
|
|
5507
|
+
rawOutput = result.raw;
|
|
5508
|
+
}
|
|
5337
5509
|
} catch (err) {
|
|
5338
5510
|
genMessages.stop();
|
|
5339
5511
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
5340
|
-
genSpinner.fail(`
|
|
5512
|
+
genSpinner.fail(`Generation failed: ${msg}`);
|
|
5341
5513
|
throw new Error("__exit__");
|
|
5342
5514
|
}
|
|
5343
5515
|
genMessages.stop();
|
|
5344
5516
|
if (!generatedSetup) {
|
|
5345
|
-
genSpinner.fail("Failed to
|
|
5517
|
+
genSpinner.fail("Failed to generate setup.");
|
|
5518
|
+
if (rawOutput) {
|
|
5519
|
+
console.log(chalk7.dim("\nRaw LLM output (JSON parse failed):"));
|
|
5520
|
+
console.log(chalk7.dim(rawOutput.slice(0, 500)));
|
|
5521
|
+
}
|
|
5346
5522
|
throw new Error("__exit__");
|
|
5347
5523
|
}
|
|
5348
|
-
|
|
5524
|
+
const elapsedMs = Date.now() - genStartTime;
|
|
5525
|
+
const mins = Math.floor(elapsedMs / 6e4);
|
|
5526
|
+
const secs = Math.floor(elapsedMs % 6e4 / 1e3);
|
|
5527
|
+
const timeStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
|
|
5528
|
+
genSpinner.succeed(`Setup generated ${chalk7.dim(`in ${timeStr}`)}`);
|
|
5529
|
+
printSetupSummary(generatedSetup);
|
|
5530
|
+
const sessionHistory = [];
|
|
5531
|
+
sessionHistory.push({
|
|
5532
|
+
role: "assistant",
|
|
5533
|
+
content: summarizeSetup("Initial generation", generatedSetup)
|
|
5534
|
+
});
|
|
5535
|
+
console.log(title.bold(" Step 4/6 \u2014 Review and apply\n"));
|
|
5349
5536
|
const setupFiles = collectSetupFiles(generatedSetup);
|
|
5350
5537
|
const staged = stageFiles(setupFiles, process.cwd());
|
|
5351
5538
|
const totalChanges = staged.newFiles + staged.modifiedFiles;
|
|
5352
|
-
console.log(
|
|
5353
|
-
${chalk8.green(`${staged.newFiles} new`)} / ${chalk8.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}
|
|
5539
|
+
console.log(chalk7.dim(` ${chalk7.green(`${staged.newFiles} new`)} / ${chalk7.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}
|
|
5354
5540
|
`));
|
|
5541
|
+
let action;
|
|
5355
5542
|
if (totalChanges === 0) {
|
|
5356
|
-
console.log(
|
|
5543
|
+
console.log(chalk7.dim(" No changes needed \u2014 your configs are already up to date.\n"));
|
|
5357
5544
|
cleanupStaging();
|
|
5358
|
-
|
|
5545
|
+
action = "accept";
|
|
5546
|
+
} else {
|
|
5547
|
+
const wantsReview = await promptWantsReview();
|
|
5548
|
+
if (wantsReview) {
|
|
5549
|
+
const reviewMethod = await promptReviewMethod();
|
|
5550
|
+
await openReview(reviewMethod, staged.stagedFiles);
|
|
5551
|
+
}
|
|
5552
|
+
action = await promptReviewAction();
|
|
5359
5553
|
}
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5554
|
+
while (action === "refine") {
|
|
5555
|
+
generatedSetup = await refineLoop(generatedSetup, targetAgent, sessionHistory);
|
|
5556
|
+
if (!generatedSetup) {
|
|
5557
|
+
cleanupStaging();
|
|
5558
|
+
console.log(chalk7.dim("Refinement cancelled. No files were modified."));
|
|
5559
|
+
return;
|
|
5364
5560
|
}
|
|
5365
|
-
|
|
5366
|
-
|
|
5561
|
+
const updatedFiles = collectSetupFiles(generatedSetup);
|
|
5562
|
+
const restaged = stageFiles(updatedFiles, process.cwd());
|
|
5563
|
+
console.log(chalk7.dim(` ${chalk7.green(`${restaged.newFiles} new`)} / ${chalk7.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
|
|
5564
|
+
`));
|
|
5565
|
+
printSetupSummary(generatedSetup);
|
|
5566
|
+
await openReview("terminal", restaged.stagedFiles);
|
|
5567
|
+
action = await promptReviewAction();
|
|
5367
5568
|
}
|
|
5368
|
-
const action = await select3({
|
|
5369
|
-
message: "Apply regenerated setup?",
|
|
5370
|
-
choices: [
|
|
5371
|
-
{ name: "Accept and apply", value: "accept" },
|
|
5372
|
-
{ name: "Decline", value: "decline" }
|
|
5373
|
-
]
|
|
5374
|
-
});
|
|
5375
5569
|
cleanupStaging();
|
|
5376
5570
|
if (action === "decline") {
|
|
5377
|
-
console.log(
|
|
5571
|
+
console.log(chalk7.dim("Setup declined. No files were modified."));
|
|
5378
5572
|
return;
|
|
5379
5573
|
}
|
|
5380
|
-
|
|
5574
|
+
if (options.dryRun) {
|
|
5575
|
+
console.log(chalk7.yellow("\n[Dry run] Would write the following files:"));
|
|
5576
|
+
console.log(JSON.stringify(generatedSetup, null, 2));
|
|
5577
|
+
return;
|
|
5578
|
+
}
|
|
5579
|
+
const writeSpinner = ora3("Writing config files...").start();
|
|
5381
5580
|
try {
|
|
5382
5581
|
const result = writeSetup(generatedSetup);
|
|
5383
5582
|
writeSpinner.succeed("Config files written");
|
|
5583
|
+
console.log(chalk7.bold("\nFiles created/updated:"));
|
|
5384
5584
|
for (const file of result.written) {
|
|
5385
|
-
console.log(` ${
|
|
5585
|
+
console.log(` ${chalk7.green("\u2713")} ${file}`);
|
|
5386
5586
|
}
|
|
5387
5587
|
if (result.deleted.length > 0) {
|
|
5588
|
+
console.log(chalk7.bold("\nFiles removed:"));
|
|
5388
5589
|
for (const file of result.deleted) {
|
|
5389
|
-
console.log(` ${
|
|
5590
|
+
console.log(` ${chalk7.red("\u2717")} ${file}`);
|
|
5390
5591
|
}
|
|
5391
5592
|
}
|
|
5392
5593
|
if (result.backupDir) {
|
|
5393
|
-
console.log(
|
|
5594
|
+
console.log(chalk7.dim(`
|
|
5394
5595
|
Backups saved to ${result.backupDir}`));
|
|
5395
5596
|
}
|
|
5396
5597
|
} catch (err) {
|
|
5397
5598
|
writeSpinner.fail("Failed to write files");
|
|
5398
|
-
console.error(
|
|
5599
|
+
console.error(chalk7.red(err instanceof Error ? err.message : "Unknown error"));
|
|
5399
5600
|
throw new Error("__exit__");
|
|
5400
5601
|
}
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
targetAgent
|
|
5406
|
-
});
|
|
5407
|
-
const afterScore = computeLocalScore(process.cwd(), targetAgent);
|
|
5408
|
-
if (afterScore.score < baselineScore.score) {
|
|
5409
|
-
console.log("");
|
|
5410
|
-
console.log(chalk8.yellow(` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`));
|
|
5602
|
+
console.log(title.bold("\n Step 5/6 \u2014 Enhance with MCP servers\n"));
|
|
5603
|
+
console.log(chalk7.dim(" MCP servers connect your AI agents to external tools and services"));
|
|
5604
|
+
console.log(chalk7.dim(" like databases, APIs, and platforms your project depends on.\n"));
|
|
5605
|
+
if (fingerprint.tools.length > 0) {
|
|
5411
5606
|
try {
|
|
5412
|
-
const
|
|
5413
|
-
if (
|
|
5414
|
-
console.log(
|
|
5607
|
+
const mcpResult = await discoverAndInstallMcps(targetAgent, fingerprint, process.cwd());
|
|
5608
|
+
if (mcpResult.installed > 0) {
|
|
5609
|
+
console.log(chalk7.bold(`
|
|
5610
|
+
${mcpResult.installed} MCP server${mcpResult.installed > 1 ? "s" : ""} configured`));
|
|
5611
|
+
for (const name of mcpResult.names) {
|
|
5612
|
+
console.log(` ${chalk7.green("\u2713")} ${name}`);
|
|
5613
|
+
}
|
|
5415
5614
|
}
|
|
5416
|
-
} catch {
|
|
5615
|
+
} catch (err) {
|
|
5616
|
+
console.log(chalk7.dim(" MCP discovery skipped: " + (err instanceof Error ? err.message : "unknown error")));
|
|
5417
5617
|
}
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
}
|
|
5421
|
-
displayScoreDelta(baselineScore, afterScore);
|
|
5422
|
-
console.log(chalk8.bold.green(" Regeneration complete!"));
|
|
5423
|
-
console.log(chalk8.dim(" Run ") + chalk8.hex("#83D1EB")("caliber undo") + chalk8.dim(" to revert changes.\n"));
|
|
5424
|
-
}
|
|
5425
|
-
|
|
5426
|
-
// src/commands/recommend.ts
|
|
5427
|
-
import chalk9 from "chalk";
|
|
5428
|
-
import ora5 from "ora";
|
|
5429
|
-
import { mkdirSync, readFileSync as readFileSync7, readdirSync as readdirSync5, existsSync as existsSync9, writeFileSync } from "fs";
|
|
5430
|
-
import { join as join8, dirname as dirname2 } from "path";
|
|
5431
|
-
|
|
5432
|
-
// src/scanner/index.ts
|
|
5433
|
-
import fs22 from "fs";
|
|
5434
|
-
import path17 from "path";
|
|
5435
|
-
import crypto2 from "crypto";
|
|
5436
|
-
function scanLocalState(dir) {
|
|
5437
|
-
const items = [];
|
|
5438
|
-
const claudeMdPath = path17.join(dir, "CLAUDE.md");
|
|
5439
|
-
if (fs22.existsSync(claudeMdPath)) {
|
|
5440
|
-
items.push({
|
|
5441
|
-
type: "rule",
|
|
5442
|
-
platform: "claude",
|
|
5443
|
-
name: "CLAUDE.md",
|
|
5444
|
-
contentHash: hashFile(claudeMdPath),
|
|
5445
|
-
path: claudeMdPath
|
|
5446
|
-
});
|
|
5618
|
+
} else {
|
|
5619
|
+
console.log(chalk7.dim(" No external tools or services detected \u2014 skipping MCP discovery.\n"));
|
|
5447
5620
|
}
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5621
|
+
ensurePermissions();
|
|
5622
|
+
const sha = getCurrentHeadSha();
|
|
5623
|
+
writeState({
|
|
5624
|
+
lastRefreshSha: sha ?? "",
|
|
5625
|
+
lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5626
|
+
targetAgent
|
|
5627
|
+
});
|
|
5628
|
+
console.log("");
|
|
5629
|
+
console.log(title.bold(" Keep your configs fresh\n"));
|
|
5630
|
+
console.log(chalk7.dim(" Caliber can automatically update your agent configs when your code changes.\n"));
|
|
5631
|
+
const hookChoice = await promptHookType(targetAgent);
|
|
5632
|
+
if (hookChoice === "claude" || hookChoice === "both") {
|
|
5633
|
+
const hookResult = installHook();
|
|
5634
|
+
if (hookResult.installed) {
|
|
5635
|
+
console.log(` ${chalk7.green("\u2713")} Claude Code hook installed \u2014 docs update on session end`);
|
|
5636
|
+
console.log(chalk7.dim(" Run ") + chalk7.hex("#83D1EB")("caliber hooks --remove") + chalk7.dim(" to disable"));
|
|
5637
|
+
} else if (hookResult.alreadyInstalled) {
|
|
5638
|
+
console.log(chalk7.dim(" Claude Code hook already installed"));
|
|
5639
|
+
}
|
|
5640
|
+
const learnResult = installLearningHooks();
|
|
5641
|
+
if (learnResult.installed) {
|
|
5642
|
+
console.log(` ${chalk7.green("\u2713")} Learning hooks installed \u2014 session insights captured automatically`);
|
|
5643
|
+
console.log(chalk7.dim(" Run ") + chalk7.hex("#83D1EB")("caliber learn remove") + chalk7.dim(" to disable"));
|
|
5644
|
+
} else if (learnResult.alreadyInstalled) {
|
|
5645
|
+
console.log(chalk7.dim(" Learning hooks already installed"));
|
|
5459
5646
|
}
|
|
5460
5647
|
}
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
name,
|
|
5471
|
-
contentHash: hashJson(mcpJson.mcpServers[name]),
|
|
5472
|
-
path: mcpJsonPath
|
|
5473
|
-
});
|
|
5474
|
-
}
|
|
5475
|
-
}
|
|
5476
|
-
} catch {
|
|
5648
|
+
if (hookChoice === "precommit" || hookChoice === "both") {
|
|
5649
|
+
const precommitResult = installPreCommitHook();
|
|
5650
|
+
if (precommitResult.installed) {
|
|
5651
|
+
console.log(` ${chalk7.green("\u2713")} Pre-commit hook installed \u2014 docs refresh before each commit`);
|
|
5652
|
+
console.log(chalk7.dim(" Run ") + chalk7.hex("#83D1EB")("caliber hooks --remove") + chalk7.dim(" to disable"));
|
|
5653
|
+
} else if (precommitResult.alreadyInstalled) {
|
|
5654
|
+
console.log(chalk7.dim(" Pre-commit hook already installed"));
|
|
5655
|
+
} else {
|
|
5656
|
+
console.log(chalk7.yellow(" Could not install pre-commit hook (not a git repository?)"));
|
|
5477
5657
|
}
|
|
5478
5658
|
}
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
items.push({
|
|
5482
|
-
type: "rule",
|
|
5483
|
-
platform: "codex",
|
|
5484
|
-
name: "AGENTS.md",
|
|
5485
|
-
contentHash: hashFile(agentsMdPath),
|
|
5486
|
-
path: agentsMdPath
|
|
5487
|
-
});
|
|
5659
|
+
if (hookChoice === "skip") {
|
|
5660
|
+
console.log(chalk7.dim(" Skipped auto-refresh hooks. Run ") + chalk7.hex("#83D1EB")("caliber hooks --install") + chalk7.dim(" later to enable."));
|
|
5488
5661
|
}
|
|
5489
|
-
const
|
|
5490
|
-
if (
|
|
5662
|
+
const afterScore = computeLocalScore(process.cwd(), targetAgent);
|
|
5663
|
+
if (afterScore.score < baselineScore.score) {
|
|
5664
|
+
console.log("");
|
|
5665
|
+
console.log(chalk7.yellow(` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`));
|
|
5491
5666
|
try {
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
items.push({
|
|
5496
|
-
type: "skill",
|
|
5497
|
-
platform: "codex",
|
|
5498
|
-
name: `${name}/SKILL.md`,
|
|
5499
|
-
contentHash: hashFile(skillFile),
|
|
5500
|
-
path: skillFile
|
|
5501
|
-
});
|
|
5502
|
-
}
|
|
5667
|
+
const { restored, removed } = undoSetup();
|
|
5668
|
+
if (restored.length > 0 || removed.length > 0) {
|
|
5669
|
+
console.log(chalk7.dim(` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`));
|
|
5503
5670
|
}
|
|
5504
5671
|
} catch {
|
|
5505
5672
|
}
|
|
5673
|
+
console.log(chalk7.dim(" Run ") + chalk7.hex("#83D1EB")("caliber onboard --force") + chalk7.dim(" to override.\n"));
|
|
5674
|
+
return;
|
|
5506
5675
|
}
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
}
|
|
5517
|
-
|
|
5518
|
-
if (fs22.existsSync(cursorRulesDir)) {
|
|
5519
|
-
for (const file of fs22.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
5520
|
-
const filePath = path17.join(cursorRulesDir, file);
|
|
5521
|
-
items.push({
|
|
5522
|
-
type: "rule",
|
|
5523
|
-
platform: "cursor",
|
|
5524
|
-
name: file,
|
|
5525
|
-
contentHash: hashFile(filePath),
|
|
5526
|
-
path: filePath
|
|
5527
|
-
});
|
|
5528
|
-
}
|
|
5529
|
-
}
|
|
5530
|
-
const cursorSkillsDir = path17.join(dir, ".cursor", "skills");
|
|
5531
|
-
if (fs22.existsSync(cursorSkillsDir)) {
|
|
5676
|
+
displayScoreDelta(baselineScore, afterScore);
|
|
5677
|
+
console.log(title.bold("\n Step 6/6 \u2014 Community skills\n"));
|
|
5678
|
+
console.log(chalk7.dim(" Search public skill registries for skills that match your tech stack.\n"));
|
|
5679
|
+
const wantsSkills = await select4({
|
|
5680
|
+
message: "Search public repos for relevant skills to add to this project?",
|
|
5681
|
+
choices: [
|
|
5682
|
+
{ name: "Yes, find skills for my project", value: true },
|
|
5683
|
+
{ name: "Skip for now", value: false }
|
|
5684
|
+
]
|
|
5685
|
+
});
|
|
5686
|
+
if (wantsSkills) {
|
|
5532
5687
|
try {
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
type: "skill",
|
|
5538
|
-
platform: "cursor",
|
|
5539
|
-
name: `${name}/SKILL.md`,
|
|
5540
|
-
contentHash: hashFile(skillFile),
|
|
5541
|
-
path: skillFile
|
|
5542
|
-
});
|
|
5543
|
-
}
|
|
5688
|
+
await searchAndInstallSkills();
|
|
5689
|
+
} catch (err) {
|
|
5690
|
+
if (err.message !== "__exit__") {
|
|
5691
|
+
console.log(chalk7.dim(" Skills search failed: " + (err.message || "unknown error")));
|
|
5544
5692
|
}
|
|
5545
|
-
|
|
5693
|
+
console.log(chalk7.dim(" Run ") + chalk7.hex("#83D1EB")("caliber skills") + chalk7.dim(" later to try again.\n"));
|
|
5546
5694
|
}
|
|
5695
|
+
} else {
|
|
5696
|
+
console.log(chalk7.dim(" Skipped. Run ") + chalk7.hex("#83D1EB")("caliber skills") + chalk7.dim(" later to browse.\n"));
|
|
5547
5697
|
}
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5698
|
+
console.log(chalk7.bold.green(" Onboarding complete! Your project is ready for AI-assisted development."));
|
|
5699
|
+
console.log(chalk7.dim(" Run ") + chalk7.hex("#83D1EB")("caliber undo") + chalk7.dim(" to revert changes.\n"));
|
|
5700
|
+
console.log(chalk7.bold(" Next steps:\n"));
|
|
5701
|
+
console.log(` ${title("caliber score")} See your full config breakdown`);
|
|
5702
|
+
console.log(` ${title("caliber skills")} Discover community skills for your stack`);
|
|
5703
|
+
console.log(` ${title("caliber undo")} Revert all changes from this run`);
|
|
5704
|
+
console.log("");
|
|
5705
|
+
}
|
|
5706
|
+
async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
5707
|
+
while (true) {
|
|
5708
|
+
const message = await promptInput3("\nWhat would you like to change?");
|
|
5709
|
+
if (!message || message.toLowerCase() === "done" || message.toLowerCase() === "accept") {
|
|
5710
|
+
return currentSetup;
|
|
5711
|
+
}
|
|
5712
|
+
if (message.toLowerCase() === "cancel") {
|
|
5713
|
+
return null;
|
|
5714
|
+
}
|
|
5715
|
+
const isValid = await classifyRefineIntent(message);
|
|
5716
|
+
if (!isValid) {
|
|
5717
|
+
console.log(chalk7.dim(" This doesn't look like a config change request."));
|
|
5718
|
+
console.log(chalk7.dim(" Describe what to add, remove, or modify in your configs."));
|
|
5719
|
+
console.log(chalk7.dim(' Type "done" to accept the current setup.\n'));
|
|
5720
|
+
continue;
|
|
5721
|
+
}
|
|
5722
|
+
const refineSpinner = ora3("Refining setup...").start();
|
|
5723
|
+
const refineMessages = new SpinnerMessages(refineSpinner, REFINE_MESSAGES);
|
|
5724
|
+
refineMessages.start();
|
|
5725
|
+
const refined = await refineSetup(
|
|
5726
|
+
currentSetup,
|
|
5727
|
+
message,
|
|
5728
|
+
sessionHistory
|
|
5729
|
+
);
|
|
5730
|
+
refineMessages.stop();
|
|
5731
|
+
if (refined) {
|
|
5732
|
+
currentSetup = refined;
|
|
5733
|
+
sessionHistory.push({ role: "user", content: message });
|
|
5734
|
+
sessionHistory.push({
|
|
5735
|
+
role: "assistant",
|
|
5736
|
+
content: summarizeSetup("Applied changes", refined)
|
|
5737
|
+
});
|
|
5738
|
+
refineSpinner.succeed("Setup updated");
|
|
5739
|
+
printSetupSummary(refined);
|
|
5740
|
+
console.log(chalk7.dim('Type "done" to accept, or describe more changes.'));
|
|
5741
|
+
} else {
|
|
5742
|
+
refineSpinner.fail("Refinement failed \u2014 could not parse AI response.");
|
|
5743
|
+
console.log(chalk7.dim('Try rephrasing your request, or type "done" to keep the current setup.'));
|
|
5564
5744
|
}
|
|
5565
5745
|
}
|
|
5566
|
-
return items;
|
|
5567
5746
|
}
|
|
5568
|
-
function
|
|
5569
|
-
const
|
|
5570
|
-
|
|
5747
|
+
function summarizeSetup(action, setup) {
|
|
5748
|
+
const descriptions = setup.fileDescriptions;
|
|
5749
|
+
const files = descriptions ? Object.entries(descriptions).map(([path24, desc]) => ` ${path24}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
|
|
5750
|
+
return `${action}. Files:
|
|
5751
|
+
${files}`;
|
|
5571
5752
|
}
|
|
5572
|
-
function
|
|
5573
|
-
|
|
5753
|
+
async function classifyRefineIntent(message) {
|
|
5754
|
+
const fastModel = getFastModel();
|
|
5755
|
+
try {
|
|
5756
|
+
const result = await llmJsonCall({
|
|
5757
|
+
system: `You classify whether a user message is a valid request to modify AI agent config files (CLAUDE.md, .cursorrules, skills).
|
|
5758
|
+
Valid: requests to add, remove, change, or restructure config content. Examples: "add testing commands", "remove the terraform section", "make CLAUDE.md shorter".
|
|
5759
|
+
Invalid: questions, requests to show/display something, general chat, or anything that isn't a concrete config change.
|
|
5760
|
+
Return {"valid": true} or {"valid": false}. Nothing else.`,
|
|
5761
|
+
prompt: message,
|
|
5762
|
+
maxTokens: 20,
|
|
5763
|
+
...fastModel ? { model: fastModel } : {}
|
|
5764
|
+
});
|
|
5765
|
+
return result.valid === true;
|
|
5766
|
+
} catch {
|
|
5767
|
+
return true;
|
|
5768
|
+
}
|
|
5574
5769
|
}
|
|
5770
|
+
async function evaluateDismissals(failingChecks, fingerprint) {
|
|
5771
|
+
const fastModel = getFastModel();
|
|
5772
|
+
const checkList = failingChecks.map((c) => ({
|
|
5773
|
+
id: c.id,
|
|
5774
|
+
name: c.name,
|
|
5775
|
+
suggestion: c.suggestion
|
|
5776
|
+
}));
|
|
5777
|
+
try {
|
|
5778
|
+
const result = await llmJsonCall({
|
|
5779
|
+
system: `You evaluate whether scoring checks are applicable to a project.
|
|
5780
|
+
Given the project's languages/frameworks and a list of failing checks, return which checks are NOT applicable.
|
|
5575
5781
|
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5782
|
+
Only dismiss checks that truly don't apply \u2014 e.g. "Build/test/lint commands" for a pure Terraform/HCL repo with no build system.
|
|
5783
|
+
Do NOT dismiss checks that could reasonably apply even if the project doesn't use them yet.
|
|
5784
|
+
|
|
5785
|
+
Return {"dismissed": [{"id": "check_id", "reason": "brief reason"}]} or {"dismissed": []} if all apply.`,
|
|
5786
|
+
prompt: `Languages: ${fingerprint.languages.join(", ") || "none"}
|
|
5787
|
+
Frameworks: ${fingerprint.frameworks.join(", ") || "none"}
|
|
5788
|
+
|
|
5789
|
+
Failing checks:
|
|
5790
|
+
${JSON.stringify(checkList, null, 2)}`,
|
|
5791
|
+
maxTokens: 200,
|
|
5792
|
+
...fastModel ? { model: fastModel } : {}
|
|
5793
|
+
});
|
|
5794
|
+
if (!Array.isArray(result.dismissed)) return [];
|
|
5795
|
+
return result.dismissed.filter((d) => d.id && d.reason && failingChecks.some((c) => c.id === d.id)).map((d) => ({ id: d.id, reason: d.reason, dismissedAt: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
5796
|
+
} catch {
|
|
5797
|
+
return [];
|
|
5582
5798
|
}
|
|
5583
|
-
return platforms.size > 0 ? Array.from(platforms) : ["claude"];
|
|
5584
5799
|
}
|
|
5585
|
-
function
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5800
|
+
function promptInput3(question) {
|
|
5801
|
+
const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
|
|
5802
|
+
return new Promise((resolve2) => {
|
|
5803
|
+
rl.question(chalk7.cyan(`${question} `), (answer) => {
|
|
5804
|
+
rl.close();
|
|
5805
|
+
resolve2(answer.trim());
|
|
5806
|
+
});
|
|
5807
|
+
});
|
|
5808
|
+
}
|
|
5809
|
+
async function promptAgent() {
|
|
5810
|
+
const selected = await checkbox({
|
|
5811
|
+
message: "Which coding agents do you use? (toggle with space)",
|
|
5812
|
+
choices: [
|
|
5813
|
+
{ name: "Claude Code", value: "claude" },
|
|
5814
|
+
{ name: "Cursor", value: "cursor" },
|
|
5815
|
+
{ name: "Codex (OpenAI)", value: "codex" }
|
|
5816
|
+
],
|
|
5817
|
+
validate: (items) => {
|
|
5818
|
+
if (items.length === 0) return "At least one agent must be selected";
|
|
5819
|
+
return true;
|
|
5820
|
+
}
|
|
5821
|
+
});
|
|
5822
|
+
return selected;
|
|
5823
|
+
}
|
|
5824
|
+
async function promptHookType(targetAgent) {
|
|
5825
|
+
const choices = [];
|
|
5826
|
+
const hasClaude = targetAgent.includes("claude");
|
|
5827
|
+
if (hasClaude) {
|
|
5828
|
+
choices.push({ name: "Claude Code hook (auto-refresh on session end)", value: "claude" });
|
|
5591
5829
|
}
|
|
5592
|
-
|
|
5830
|
+
choices.push({ name: "Git pre-commit hook (refresh before each commit)", value: "precommit" });
|
|
5831
|
+
if (hasClaude) {
|
|
5832
|
+
choices.push({ name: "Both (Claude Code + pre-commit)", value: "both" });
|
|
5833
|
+
}
|
|
5834
|
+
choices.push({ name: "Skip for now", value: "skip" });
|
|
5835
|
+
return select4({
|
|
5836
|
+
message: "How would you like to auto-refresh your docs?",
|
|
5837
|
+
choices
|
|
5838
|
+
});
|
|
5593
5839
|
}
|
|
5594
|
-
function
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5840
|
+
async function promptReviewAction() {
|
|
5841
|
+
return select4({
|
|
5842
|
+
message: "What would you like to do?",
|
|
5843
|
+
choices: [
|
|
5844
|
+
{ name: "Accept and apply", value: "accept" },
|
|
5845
|
+
{ name: "Refine via chat", value: "refine" },
|
|
5846
|
+
{ name: "Decline", value: "decline" }
|
|
5847
|
+
]
|
|
5848
|
+
});
|
|
5849
|
+
}
|
|
5850
|
+
function printSetupSummary(setup) {
|
|
5851
|
+
const claude = setup.claude;
|
|
5852
|
+
const cursor = setup.cursor;
|
|
5853
|
+
const fileDescriptions = setup.fileDescriptions;
|
|
5854
|
+
const deletions = setup.deletions;
|
|
5855
|
+
console.log("");
|
|
5856
|
+
console.log(chalk7.bold(" Proposed changes:\n"));
|
|
5857
|
+
const getDescription = (filePath) => {
|
|
5858
|
+
return fileDescriptions?.[filePath];
|
|
5859
|
+
};
|
|
5860
|
+
if (claude) {
|
|
5861
|
+
if (claude.claudeMd) {
|
|
5862
|
+
const icon = fs22.existsSync("CLAUDE.md") ? chalk7.yellow("~") : chalk7.green("+");
|
|
5863
|
+
const desc = getDescription("CLAUDE.md");
|
|
5864
|
+
console.log(` ${icon} ${chalk7.bold("CLAUDE.md")}`);
|
|
5865
|
+
if (desc) console.log(chalk7.dim(` ${desc}`));
|
|
5866
|
+
console.log("");
|
|
5867
|
+
}
|
|
5868
|
+
const skills = claude.skills;
|
|
5869
|
+
if (Array.isArray(skills) && skills.length > 0) {
|
|
5870
|
+
for (const skill of skills) {
|
|
5871
|
+
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
5872
|
+
const icon = fs22.existsSync(skillPath) ? chalk7.yellow("~") : chalk7.green("+");
|
|
5873
|
+
const desc = getDescription(skillPath);
|
|
5874
|
+
console.log(` ${icon} ${chalk7.bold(skillPath)}`);
|
|
5875
|
+
console.log(chalk7.dim(` ${desc || skill.description || skill.name}`));
|
|
5876
|
+
console.log("");
|
|
5608
5877
|
}
|
|
5609
|
-
} catch {
|
|
5610
5878
|
}
|
|
5611
5879
|
}
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
const
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
source_url: skill.source ? `https://github.com/${skill.source}` : "",
|
|
5631
|
-
score: 0,
|
|
5632
|
-
reason: skill.description || "",
|
|
5633
|
-
detected_technology: tech,
|
|
5634
|
-
item_type: "skill",
|
|
5635
|
-
installs: skill.installs ?? 0
|
|
5636
|
-
});
|
|
5880
|
+
const codex = setup.codex;
|
|
5881
|
+
if (codex) {
|
|
5882
|
+
if (codex.agentsMd) {
|
|
5883
|
+
const icon = fs22.existsSync("AGENTS.md") ? chalk7.yellow("~") : chalk7.green("+");
|
|
5884
|
+
const desc = getDescription("AGENTS.md");
|
|
5885
|
+
console.log(` ${icon} ${chalk7.bold("AGENTS.md")}`);
|
|
5886
|
+
if (desc) console.log(chalk7.dim(` ${desc}`));
|
|
5887
|
+
console.log("");
|
|
5888
|
+
}
|
|
5889
|
+
const codexSkills = codex.skills;
|
|
5890
|
+
if (Array.isArray(codexSkills) && codexSkills.length > 0) {
|
|
5891
|
+
for (const skill of codexSkills) {
|
|
5892
|
+
const skillPath = `.agents/skills/${skill.name}/SKILL.md`;
|
|
5893
|
+
const icon = fs22.existsSync(skillPath) ? chalk7.yellow("~") : chalk7.green("+");
|
|
5894
|
+
const desc = getDescription(skillPath);
|
|
5895
|
+
console.log(` ${icon} ${chalk7.bold(skillPath)}`);
|
|
5896
|
+
console.log(chalk7.dim(` ${desc || skill.description || skill.name}`));
|
|
5897
|
+
console.log("");
|
|
5637
5898
|
}
|
|
5638
|
-
} catch {
|
|
5639
|
-
continue;
|
|
5640
5899
|
}
|
|
5641
5900
|
}
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5901
|
+
if (cursor) {
|
|
5902
|
+
if (cursor.cursorrules) {
|
|
5903
|
+
const icon = fs22.existsSync(".cursorrules") ? chalk7.yellow("~") : chalk7.green("+");
|
|
5904
|
+
const desc = getDescription(".cursorrules");
|
|
5905
|
+
console.log(` ${icon} ${chalk7.bold(".cursorrules")}`);
|
|
5906
|
+
if (desc) console.log(chalk7.dim(` ${desc}`));
|
|
5907
|
+
console.log("");
|
|
5908
|
+
}
|
|
5909
|
+
const cursorSkills = cursor.skills;
|
|
5910
|
+
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
5911
|
+
for (const skill of cursorSkills) {
|
|
5912
|
+
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
5913
|
+
const icon = fs22.existsSync(skillPath) ? chalk7.yellow("~") : chalk7.green("+");
|
|
5914
|
+
const desc = getDescription(skillPath);
|
|
5915
|
+
console.log(` ${icon} ${chalk7.bold(skillPath)}`);
|
|
5916
|
+
console.log(chalk7.dim(` ${desc || skill.description || skill.name}`));
|
|
5917
|
+
console.log("");
|
|
5918
|
+
}
|
|
5919
|
+
}
|
|
5920
|
+
const rules = cursor.rules;
|
|
5921
|
+
if (Array.isArray(rules) && rules.length > 0) {
|
|
5922
|
+
for (const rule of rules) {
|
|
5923
|
+
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
5924
|
+
const icon = fs22.existsSync(rulePath) ? chalk7.yellow("~") : chalk7.green("+");
|
|
5925
|
+
const desc = getDescription(rulePath);
|
|
5926
|
+
console.log(` ${icon} ${chalk7.bold(rulePath)}`);
|
|
5927
|
+
if (desc) {
|
|
5928
|
+
console.log(chalk7.dim(` ${desc}`));
|
|
5929
|
+
} else {
|
|
5930
|
+
const firstLine = rule.content.split("\n").filter((l) => l.trim() && !l.trim().startsWith("#"))[0];
|
|
5931
|
+
if (firstLine) console.log(chalk7.dim(` ${firstLine.trim().slice(0, 80)}`));
|
|
5932
|
+
}
|
|
5933
|
+
console.log("");
|
|
5669
5934
|
}
|
|
5670
|
-
} catch {
|
|
5671
|
-
continue;
|
|
5672
5935
|
}
|
|
5673
5936
|
}
|
|
5674
|
-
|
|
5937
|
+
if (!codex && !fs22.existsSync("AGENTS.md")) {
|
|
5938
|
+
console.log(` ${chalk7.green("+")} ${chalk7.bold("AGENTS.md")}`);
|
|
5939
|
+
console.log(chalk7.dim(" Cross-agent coordination file"));
|
|
5940
|
+
console.log("");
|
|
5941
|
+
}
|
|
5942
|
+
if (Array.isArray(deletions) && deletions.length > 0) {
|
|
5943
|
+
for (const del of deletions) {
|
|
5944
|
+
console.log(` ${chalk7.red("-")} ${chalk7.bold(del.filePath)}`);
|
|
5945
|
+
console.log(chalk7.dim(` ${del.reason}`));
|
|
5946
|
+
console.log("");
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
console.log(` ${chalk7.green("+")} ${chalk7.dim("new")} ${chalk7.yellow("~")} ${chalk7.dim("modified")} ${chalk7.red("-")} ${chalk7.dim("removed")}`);
|
|
5950
|
+
console.log("");
|
|
5675
5951
|
}
|
|
5676
|
-
|
|
5677
|
-
|
|
5952
|
+
function ensurePermissions() {
|
|
5953
|
+
const settingsPath = ".claude/settings.json";
|
|
5954
|
+
let settings = {};
|
|
5678
5955
|
try {
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
});
|
|
5682
|
-
if (!resp.ok) return [];
|
|
5683
|
-
const markdown = await resp.text();
|
|
5684
|
-
const items = [];
|
|
5685
|
-
const itemPattern = /^[-*]\s+\[([^\]]+)\]\(([^)]+)\)(?:\s+by\s+\[[^\]]*\]\([^)]*\))?\s*[-–—:]\s*(.*)/gm;
|
|
5686
|
-
let match;
|
|
5687
|
-
while ((match = itemPattern.exec(markdown)) !== null) {
|
|
5688
|
-
const [, name, url, description] = match;
|
|
5689
|
-
if (url.startsWith("#")) continue;
|
|
5690
|
-
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
5691
|
-
items.push({
|
|
5692
|
-
name: name.trim(),
|
|
5693
|
-
slug,
|
|
5694
|
-
source_url: url.trim(),
|
|
5695
|
-
score: 0,
|
|
5696
|
-
reason: description.trim().slice(0, 150),
|
|
5697
|
-
detected_technology: "claude-code",
|
|
5698
|
-
item_type: "skill"
|
|
5699
|
-
});
|
|
5956
|
+
if (fs22.existsSync(settingsPath)) {
|
|
5957
|
+
settings = JSON.parse(fs22.readFileSync(settingsPath, "utf-8"));
|
|
5700
5958
|
}
|
|
5701
|
-
const techLower = technologies.map((t) => t.toLowerCase());
|
|
5702
|
-
return items.filter((item) => {
|
|
5703
|
-
const text = `${item.name} ${item.reason}`.toLowerCase();
|
|
5704
|
-
return techLower.some((t) => text.includes(t));
|
|
5705
|
-
});
|
|
5706
5959
|
} catch {
|
|
5707
|
-
return [];
|
|
5708
5960
|
}
|
|
5709
|
-
}
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5961
|
+
const permissions = settings.permissions ?? {};
|
|
5962
|
+
const allow = permissions.allow;
|
|
5963
|
+
if (Array.isArray(allow) && allow.length > 0) return;
|
|
5964
|
+
permissions.allow = [
|
|
5965
|
+
"Bash(npm run *)",
|
|
5966
|
+
"Bash(npx vitest *)",
|
|
5967
|
+
"Bash(npx tsc *)",
|
|
5968
|
+
"Bash(git *)"
|
|
5714
5969
|
];
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5970
|
+
settings.permissions = permissions;
|
|
5971
|
+
if (!fs22.existsSync(".claude")) fs22.mkdirSync(".claude", { recursive: true });
|
|
5972
|
+
fs22.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5973
|
+
}
|
|
5974
|
+
|
|
5975
|
+
// src/commands/undo.ts
|
|
5976
|
+
import chalk8 from "chalk";
|
|
5977
|
+
import ora4 from "ora";
|
|
5978
|
+
function undoCommand() {
|
|
5979
|
+
const spinner = ora4("Reverting setup...").start();
|
|
5980
|
+
try {
|
|
5981
|
+
const { restored, removed } = undoSetup();
|
|
5982
|
+
if (restored.length === 0 && removed.length === 0) {
|
|
5983
|
+
spinner.info("Nothing to undo.");
|
|
5984
|
+
return;
|
|
5727
5985
|
}
|
|
5986
|
+
spinner.succeed("Setup reverted successfully.\n");
|
|
5987
|
+
if (restored.length > 0) {
|
|
5988
|
+
console.log(chalk8.cyan(" Restored from backup:"));
|
|
5989
|
+
for (const file of restored) {
|
|
5990
|
+
console.log(` ${chalk8.green("\u21A9")} ${file}`);
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5993
|
+
if (removed.length > 0) {
|
|
5994
|
+
console.log(chalk8.cyan(" Removed:"));
|
|
5995
|
+
for (const file of removed) {
|
|
5996
|
+
console.log(` ${chalk8.red("\u2717")} ${file}`);
|
|
5997
|
+
}
|
|
5998
|
+
}
|
|
5999
|
+
console.log("");
|
|
6000
|
+
} catch (err) {
|
|
6001
|
+
spinner.fail(chalk8.red(err instanceof Error ? err.message : "Undo failed"));
|
|
6002
|
+
throw new Error("__exit__");
|
|
5728
6003
|
}
|
|
5729
|
-
return combined;
|
|
5730
6004
|
}
|
|
5731
|
-
async function scoreWithLLM2(candidates, projectContext, technologies) {
|
|
5732
|
-
const candidateList = candidates.map((c, i) => `${i}. "${c.name}" \u2014 ${c.reason || "no description"}`).join("\n");
|
|
5733
|
-
const scored = await llmJsonCall({
|
|
5734
|
-
system: `You evaluate whether AI agent skills and tools are relevant to a specific software project.
|
|
5735
|
-
Given a project context and a list of candidates, score each one's relevance from 0-100 and provide a brief reason (max 80 chars).
|
|
5736
|
-
|
|
5737
|
-
Return a JSON array where each element has:
|
|
5738
|
-
- "index": the candidate's index number
|
|
5739
|
-
- "score": relevance score 0-100
|
|
5740
|
-
- "reason": one-liner explaining why it fits or doesn't
|
|
5741
6005
|
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
${technologies.join(", ")}
|
|
5757
|
-
|
|
5758
|
-
CANDIDATES:
|
|
5759
|
-
${candidateList}`,
|
|
5760
|
-
maxTokens: 8e3
|
|
5761
|
-
});
|
|
5762
|
-
if (!Array.isArray(scored)) return [];
|
|
5763
|
-
return scored.filter((s) => s.score >= 60 && s.index >= 0 && s.index < candidates.length).sort((a, b) => b.score - a.score).slice(0, 20).map((s) => ({
|
|
5764
|
-
...candidates[s.index],
|
|
5765
|
-
score: s.score,
|
|
5766
|
-
reason: s.reason || candidates[s.index].reason
|
|
5767
|
-
}));
|
|
5768
|
-
}
|
|
5769
|
-
function buildProjectContext(dir) {
|
|
5770
|
-
const parts = [];
|
|
5771
|
-
const fingerprint = collectFingerprint(dir);
|
|
5772
|
-
if (fingerprint.packageName) parts.push(`Package: ${fingerprint.packageName}`);
|
|
5773
|
-
if (fingerprint.languages.length > 0) parts.push(`Languages: ${fingerprint.languages.join(", ")}`);
|
|
5774
|
-
if (fingerprint.frameworks.length > 0) parts.push(`Frameworks: ${fingerprint.frameworks.join(", ")}`);
|
|
5775
|
-
if (fingerprint.description) parts.push(`Description: ${fingerprint.description}`);
|
|
5776
|
-
if (fingerprint.fileTree.length > 0) {
|
|
5777
|
-
parts.push(`
|
|
5778
|
-
File tree (${fingerprint.fileTree.length} files):
|
|
5779
|
-
${fingerprint.fileTree.slice(0, 50).join("\n")}`);
|
|
6006
|
+
// src/commands/status.ts
|
|
6007
|
+
import chalk9 from "chalk";
|
|
6008
|
+
import fs23 from "fs";
|
|
6009
|
+
async function statusCommand(options) {
|
|
6010
|
+
const config = loadConfig();
|
|
6011
|
+
const manifest = readManifest();
|
|
6012
|
+
if (options.json) {
|
|
6013
|
+
console.log(JSON.stringify({
|
|
6014
|
+
configured: !!config,
|
|
6015
|
+
provider: config?.provider,
|
|
6016
|
+
model: config?.model,
|
|
6017
|
+
manifest
|
|
6018
|
+
}, null, 2));
|
|
6019
|
+
return;
|
|
5780
6020
|
}
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
6021
|
+
console.log(chalk9.bold("\nCaliber Status\n"));
|
|
6022
|
+
if (config) {
|
|
6023
|
+
console.log(` LLM: ${chalk9.green(config.provider)} (${config.model})`);
|
|
6024
|
+
} else {
|
|
6025
|
+
console.log(` LLM: ${chalk9.yellow("Not configured")} \u2014 run ${chalk9.hex("#83D1EB")("caliber config")}`);
|
|
5785
6026
|
}
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
6027
|
+
if (!manifest) {
|
|
6028
|
+
console.log(` Setup: ${chalk9.dim("No setup applied")}`);
|
|
6029
|
+
console.log(chalk9.dim("\n Run ") + chalk9.hex("#83D1EB")("caliber onboard") + chalk9.dim(" to get started.\n"));
|
|
6030
|
+
return;
|
|
5790
6031
|
}
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
6032
|
+
console.log(` Files managed: ${chalk9.cyan(manifest.entries.length.toString())}`);
|
|
6033
|
+
for (const entry of manifest.entries) {
|
|
6034
|
+
const exists = fs23.existsSync(entry.path);
|
|
6035
|
+
const icon = exists ? chalk9.green("\u2713") : chalk9.red("\u2717");
|
|
6036
|
+
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
5795
6037
|
}
|
|
5796
|
-
|
|
6038
|
+
console.log("");
|
|
5797
6039
|
}
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
"tsx",
|
|
5809
|
-
"prettier",
|
|
5810
|
-
"eslint",
|
|
5811
|
-
"@eslint/js",
|
|
5812
|
-
"rimraf",
|
|
5813
|
-
"cross-env",
|
|
5814
|
-
"dotenv",
|
|
5815
|
-
"nodemon",
|
|
5816
|
-
"husky",
|
|
5817
|
-
"lint-staged",
|
|
5818
|
-
"commitlint",
|
|
5819
|
-
"chalk",
|
|
5820
|
-
"ora",
|
|
5821
|
-
"commander",
|
|
5822
|
-
"yargs",
|
|
5823
|
-
"meow",
|
|
5824
|
-
"inquirer",
|
|
5825
|
-
"@inquirer/confirm",
|
|
5826
|
-
"@inquirer/select",
|
|
5827
|
-
"@inquirer/prompts",
|
|
5828
|
-
"glob",
|
|
5829
|
-
"minimatch",
|
|
5830
|
-
"micromatch",
|
|
5831
|
-
"diff",
|
|
5832
|
-
"semver",
|
|
5833
|
-
"uuid",
|
|
5834
|
-
"nanoid",
|
|
5835
|
-
"debug",
|
|
5836
|
-
"ms",
|
|
5837
|
-
"lodash",
|
|
5838
|
-
"underscore",
|
|
5839
|
-
"tsup",
|
|
5840
|
-
"esbuild",
|
|
5841
|
-
"rollup",
|
|
5842
|
-
"webpack",
|
|
5843
|
-
"vite",
|
|
5844
|
-
"vitest",
|
|
5845
|
-
"jest",
|
|
5846
|
-
"mocha",
|
|
5847
|
-
"chai",
|
|
5848
|
-
"ava",
|
|
5849
|
-
"fs-extra",
|
|
5850
|
-
"mkdirp",
|
|
5851
|
-
"del",
|
|
5852
|
-
"rimraf",
|
|
5853
|
-
"path-to-regexp",
|
|
5854
|
-
"strip-ansi",
|
|
5855
|
-
"ansi-colors"
|
|
5856
|
-
]);
|
|
5857
|
-
const trivialPatterns = [
|
|
5858
|
-
/^@types\//,
|
|
5859
|
-
/^@rely-ai\//,
|
|
5860
|
-
/^@caliber-ai\//,
|
|
5861
|
-
/^eslint-/,
|
|
5862
|
-
/^@eslint\//,
|
|
5863
|
-
/^prettier-/,
|
|
5864
|
-
/^@typescript-eslint\//,
|
|
5865
|
-
/^@commitlint\//
|
|
5866
|
-
];
|
|
5867
|
-
return deps.filter(
|
|
5868
|
-
(d) => !trivial.has(d) && !trivialPatterns.some((p) => p.test(d))
|
|
5869
|
-
);
|
|
5870
|
-
} catch {
|
|
5871
|
-
return [];
|
|
6040
|
+
|
|
6041
|
+
// src/commands/regenerate.ts
|
|
6042
|
+
import chalk10 from "chalk";
|
|
6043
|
+
import ora5 from "ora";
|
|
6044
|
+
import select5 from "@inquirer/select";
|
|
6045
|
+
async function regenerateCommand(options) {
|
|
6046
|
+
const config = loadConfig();
|
|
6047
|
+
if (!config) {
|
|
6048
|
+
console.log(chalk10.red("No LLM provider configured. Run ") + chalk10.hex("#83D1EB")("caliber config") + chalk10.red(" first."));
|
|
6049
|
+
throw new Error("__exit__");
|
|
5872
6050
|
}
|
|
5873
|
-
|
|
5874
|
-
|
|
6051
|
+
const manifest = readManifest();
|
|
6052
|
+
if (!manifest) {
|
|
6053
|
+
console.log(chalk10.yellow("No existing setup found. Run ") + chalk10.hex("#83D1EB")("caliber onboard") + chalk10.yellow(" first."));
|
|
6054
|
+
throw new Error("__exit__");
|
|
6055
|
+
}
|
|
6056
|
+
const targetAgent = readState()?.targetAgent ?? ["claude", "cursor"];
|
|
6057
|
+
const spinner = ora5("Analyzing project...").start();
|
|
5875
6058
|
const fingerprint = collectFingerprint(process.cwd());
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
const
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
6059
|
+
await enrichFingerprintWithLLM(fingerprint, process.cwd());
|
|
6060
|
+
spinner.succeed("Project analyzed");
|
|
6061
|
+
const baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
6062
|
+
displayScoreSummary(baselineScore);
|
|
6063
|
+
if (baselineScore.score === 100) {
|
|
6064
|
+
console.log(chalk10.green(" Your setup is already at 100/100 \u2014 nothing to regenerate.\n"));
|
|
6065
|
+
return;
|
|
6066
|
+
}
|
|
6067
|
+
const genSpinner = ora5("Regenerating setup...").start();
|
|
6068
|
+
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
|
|
6069
|
+
genMessages.start();
|
|
6070
|
+
let generatedSetup = null;
|
|
6071
|
+
try {
|
|
6072
|
+
const result = await generateSetup(
|
|
6073
|
+
fingerprint,
|
|
6074
|
+
targetAgent,
|
|
6075
|
+
void 0,
|
|
6076
|
+
{
|
|
6077
|
+
onStatus: (status) => {
|
|
6078
|
+
genMessages.handleServerStatus(status);
|
|
6079
|
+
},
|
|
6080
|
+
onComplete: (setup) => {
|
|
6081
|
+
generatedSetup = setup;
|
|
6082
|
+
},
|
|
6083
|
+
onError: (error) => {
|
|
6084
|
+
genMessages.stop();
|
|
6085
|
+
genSpinner.fail(`Generation error: ${error}`);
|
|
6086
|
+
}
|
|
6087
|
+
}
|
|
6088
|
+
);
|
|
6089
|
+
if (!generatedSetup) generatedSetup = result.setup;
|
|
6090
|
+
} catch (err) {
|
|
6091
|
+
genMessages.stop();
|
|
6092
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
6093
|
+
genSpinner.fail(`Regeneration failed: ${msg}`);
|
|
5885
6094
|
throw new Error("__exit__");
|
|
5886
6095
|
}
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
searchSpinner.succeed("No skills found matching your tech stack.");
|
|
5892
|
-
return;
|
|
6096
|
+
genMessages.stop();
|
|
6097
|
+
if (!generatedSetup) {
|
|
6098
|
+
genSpinner.fail("Failed to regenerate setup.");
|
|
6099
|
+
throw new Error("__exit__");
|
|
5893
6100
|
}
|
|
5894
|
-
|
|
5895
|
-
const
|
|
5896
|
-
|
|
5897
|
-
|
|
6101
|
+
genSpinner.succeed("Setup regenerated");
|
|
6102
|
+
const setupFiles = collectSetupFiles(generatedSetup);
|
|
6103
|
+
const staged = stageFiles(setupFiles, process.cwd());
|
|
6104
|
+
const totalChanges = staged.newFiles + staged.modifiedFiles;
|
|
6105
|
+
console.log(chalk10.dim(`
|
|
6106
|
+
${chalk10.green(`${staged.newFiles} new`)} / ${chalk10.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}
|
|
6107
|
+
`));
|
|
6108
|
+
if (totalChanges === 0) {
|
|
6109
|
+
console.log(chalk10.dim(" No changes needed \u2014 your configs are already up to date.\n"));
|
|
6110
|
+
cleanupStaging();
|
|
5898
6111
|
return;
|
|
5899
6112
|
}
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
const config = loadConfig();
|
|
5905
|
-
if (config) {
|
|
5906
|
-
const scoreSpinner = ora5("Scoring relevance for your project...").start();
|
|
5907
|
-
try {
|
|
5908
|
-
const projectContext = buildProjectContext(process.cwd());
|
|
5909
|
-
results = await scoreWithLLM2(newCandidates, projectContext, technologies);
|
|
5910
|
-
if (results.length === 0) {
|
|
5911
|
-
scoreSpinner.succeed("No highly relevant skills found for your specific project.");
|
|
5912
|
-
return;
|
|
5913
|
-
}
|
|
5914
|
-
scoreSpinner.succeed(`${results.length} relevant skill${results.length > 1 ? "s" : ""} for your project`);
|
|
5915
|
-
} catch {
|
|
5916
|
-
scoreSpinner.warn("Could not score relevance \u2014 showing top results");
|
|
5917
|
-
results = newCandidates.slice(0, 20);
|
|
6113
|
+
if (options.dryRun) {
|
|
6114
|
+
console.log(chalk10.yellow("[Dry run] Would write:"));
|
|
6115
|
+
for (const f of staged.stagedFiles) {
|
|
6116
|
+
console.log(` ${f.isNew ? chalk10.green("+") : chalk10.yellow("~")} ${f.relativePath}`);
|
|
5918
6117
|
}
|
|
5919
|
-
|
|
5920
|
-
results = newCandidates.slice(0, 20);
|
|
5921
|
-
}
|
|
5922
|
-
const fetchSpinner = ora5("Verifying skill availability...").start();
|
|
5923
|
-
const contentMap = /* @__PURE__ */ new Map();
|
|
5924
|
-
await Promise.all(results.map(async (rec) => {
|
|
5925
|
-
const content = await fetchSkillContent(rec);
|
|
5926
|
-
if (content) contentMap.set(rec.slug, content);
|
|
5927
|
-
}));
|
|
5928
|
-
const available = results.filter((r) => contentMap.has(r.slug));
|
|
5929
|
-
if (!available.length) {
|
|
5930
|
-
fetchSpinner.fail("No installable skills found \u2014 content could not be fetched.");
|
|
6118
|
+
cleanupStaging();
|
|
5931
6119
|
return;
|
|
5932
6120
|
}
|
|
5933
|
-
const
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
const selected = await interactiveSelect2(available);
|
|
5938
|
-
if (selected?.length) {
|
|
5939
|
-
await installSkills(selected, platforms, contentMap);
|
|
6121
|
+
const wantsReview = await promptWantsReview();
|
|
6122
|
+
if (wantsReview) {
|
|
6123
|
+
const reviewMethod = await promptReviewMethod();
|
|
6124
|
+
await openReview(reviewMethod, staged.stagedFiles);
|
|
5940
6125
|
}
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
6126
|
+
const action = await select5({
|
|
6127
|
+
message: "Apply regenerated setup?",
|
|
6128
|
+
choices: [
|
|
6129
|
+
{ name: "Accept and apply", value: "accept" },
|
|
6130
|
+
{ name: "Decline", value: "decline" }
|
|
6131
|
+
]
|
|
6132
|
+
});
|
|
6133
|
+
cleanupStaging();
|
|
6134
|
+
if (action === "decline") {
|
|
6135
|
+
console.log(chalk10.dim("Regeneration cancelled. No files were modified."));
|
|
6136
|
+
return;
|
|
5946
6137
|
}
|
|
5947
|
-
const
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
const lines = [];
|
|
5954
|
-
lines.push(chalk9.bold(" Recommendations"));
|
|
5955
|
-
lines.push("");
|
|
5956
|
-
if (hasScores) {
|
|
5957
|
-
lines.push(` ${chalk9.dim("Score".padEnd(7))} ${chalk9.dim("Name".padEnd(28))} ${chalk9.dim("Why")}`);
|
|
5958
|
-
} else {
|
|
5959
|
-
lines.push(` ${chalk9.dim("Name".padEnd(30))} ${chalk9.dim("Technology".padEnd(18))} ${chalk9.dim("Source")}`);
|
|
6138
|
+
const writeSpinner = ora5("Writing config files...").start();
|
|
6139
|
+
try {
|
|
6140
|
+
const result = writeSetup(generatedSetup);
|
|
6141
|
+
writeSpinner.succeed("Config files written");
|
|
6142
|
+
for (const file of result.written) {
|
|
6143
|
+
console.log(` ${chalk10.green("\u2713")} ${file}`);
|
|
5960
6144
|
}
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
const check = selected.has(i) ? chalk9.green("[x]") : "[ ]";
|
|
5965
|
-
const ptr = i === cursor ? chalk9.cyan(">") : " ";
|
|
5966
|
-
if (hasScores) {
|
|
5967
|
-
const scoreColor = rec.score >= 90 ? chalk9.green : rec.score >= 70 ? chalk9.yellow : chalk9.dim;
|
|
5968
|
-
lines.push(` ${ptr} ${check} ${scoreColor(String(rec.score).padStart(3))} ${rec.name.padEnd(26)} ${chalk9.dim(rec.reason.slice(0, 40))}`);
|
|
5969
|
-
} else {
|
|
5970
|
-
lines.push(` ${ptr} ${check} ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk9.dim(rec.source_url || "")}`);
|
|
6145
|
+
if (result.deleted.length > 0) {
|
|
6146
|
+
for (const file of result.deleted) {
|
|
6147
|
+
console.log(` ${chalk10.red("\u2717")} ${file}`);
|
|
5971
6148
|
}
|
|
5972
6149
|
}
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
}
|
|
5977
|
-
function draw(initial) {
|
|
5978
|
-
if (!initial && lineCount > 0) {
|
|
5979
|
-
stdout.write(`\x1B[${lineCount}A`);
|
|
6150
|
+
if (result.backupDir) {
|
|
6151
|
+
console.log(chalk10.dim(`
|
|
6152
|
+
Backups saved to ${result.backupDir}`));
|
|
5980
6153
|
}
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
6154
|
+
} catch (err) {
|
|
6155
|
+
writeSpinner.fail("Failed to write files");
|
|
6156
|
+
console.error(chalk10.red(err instanceof Error ? err.message : "Unknown error"));
|
|
6157
|
+
throw new Error("__exit__");
|
|
5985
6158
|
}
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
stdin.setEncoding("utf8");
|
|
5992
|
-
function cleanup() {
|
|
5993
|
-
stdin.removeListener("data", onData);
|
|
5994
|
-
stdin.setRawMode(false);
|
|
5995
|
-
stdin.pause();
|
|
5996
|
-
}
|
|
5997
|
-
function onData(key) {
|
|
5998
|
-
switch (key) {
|
|
5999
|
-
case "\x1B[A":
|
|
6000
|
-
cursor = (cursor - 1 + recs.length) % recs.length;
|
|
6001
|
-
draw(false);
|
|
6002
|
-
break;
|
|
6003
|
-
case "\x1B[B":
|
|
6004
|
-
cursor = (cursor + 1) % recs.length;
|
|
6005
|
-
draw(false);
|
|
6006
|
-
break;
|
|
6007
|
-
case " ":
|
|
6008
|
-
selected.has(cursor) ? selected.delete(cursor) : selected.add(cursor);
|
|
6009
|
-
draw(false);
|
|
6010
|
-
break;
|
|
6011
|
-
case "a":
|
|
6012
|
-
recs.forEach((_, i) => selected.add(i));
|
|
6013
|
-
draw(false);
|
|
6014
|
-
break;
|
|
6015
|
-
case "n":
|
|
6016
|
-
selected.clear();
|
|
6017
|
-
draw(false);
|
|
6018
|
-
break;
|
|
6019
|
-
case "\r":
|
|
6020
|
-
case "\n":
|
|
6021
|
-
cleanup();
|
|
6022
|
-
if (selected.size === 0) {
|
|
6023
|
-
console.log(chalk9.dim("\n No skills selected.\n"));
|
|
6024
|
-
resolve2(null);
|
|
6025
|
-
} else {
|
|
6026
|
-
resolve2(Array.from(selected).sort().map((i) => recs[i]));
|
|
6027
|
-
}
|
|
6028
|
-
break;
|
|
6029
|
-
case "q":
|
|
6030
|
-
case "\x1B":
|
|
6031
|
-
case "":
|
|
6032
|
-
cleanup();
|
|
6033
|
-
console.log(chalk9.dim("\n Cancelled.\n"));
|
|
6034
|
-
resolve2(null);
|
|
6035
|
-
break;
|
|
6036
|
-
}
|
|
6037
|
-
}
|
|
6038
|
-
stdin.on("data", onData);
|
|
6159
|
+
const sha = getCurrentHeadSha();
|
|
6160
|
+
writeState({
|
|
6161
|
+
lastRefreshSha: sha ?? "",
|
|
6162
|
+
lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6163
|
+
targetAgent
|
|
6039
6164
|
});
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
const candidates = [
|
|
6045
|
-
`https://raw.githubusercontent.com/${repoPath}/HEAD/skills/${rec.slug}/SKILL.md`,
|
|
6046
|
-
`https://raw.githubusercontent.com/${repoPath}/HEAD/${rec.slug}/SKILL.md`,
|
|
6047
|
-
`https://raw.githubusercontent.com/${repoPath}/HEAD/.claude/skills/${rec.slug}/SKILL.md`,
|
|
6048
|
-
`https://raw.githubusercontent.com/${repoPath}/HEAD/.agents/skills/${rec.slug}/SKILL.md`
|
|
6049
|
-
];
|
|
6050
|
-
for (const url of candidates) {
|
|
6165
|
+
const afterScore = computeLocalScore(process.cwd(), targetAgent);
|
|
6166
|
+
if (afterScore.score < baselineScore.score) {
|
|
6167
|
+
console.log("");
|
|
6168
|
+
console.log(chalk10.yellow(` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`));
|
|
6051
6169
|
try {
|
|
6052
|
-
const
|
|
6053
|
-
if (
|
|
6054
|
-
|
|
6055
|
-
if (text.length > 20) return text;
|
|
6170
|
+
const { restored, removed } = undoSetup();
|
|
6171
|
+
if (restored.length > 0 || removed.length > 0) {
|
|
6172
|
+
console.log(chalk10.dim(` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`));
|
|
6056
6173
|
}
|
|
6057
6174
|
} catch {
|
|
6058
6175
|
}
|
|
6176
|
+
console.log(chalk10.dim(" Run ") + chalk10.hex("#83D1EB")("caliber onboard --force") + chalk10.dim(" to override.\n"));
|
|
6177
|
+
return;
|
|
6059
6178
|
}
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
{ signal: AbortSignal.timeout(1e4) }
|
|
6064
|
-
);
|
|
6065
|
-
if (resp.ok) {
|
|
6066
|
-
const tree = await resp.json();
|
|
6067
|
-
const needle = `${rec.slug}/SKILL.md`;
|
|
6068
|
-
const match = tree.tree?.find((f) => f.path.endsWith(needle));
|
|
6069
|
-
if (match) {
|
|
6070
|
-
const rawUrl = `https://raw.githubusercontent.com/${repoPath}/HEAD/${match.path}`;
|
|
6071
|
-
const contentResp = await fetch(rawUrl, { signal: AbortSignal.timeout(1e4) });
|
|
6072
|
-
if (contentResp.ok) return await contentResp.text();
|
|
6073
|
-
}
|
|
6074
|
-
}
|
|
6075
|
-
} catch {
|
|
6076
|
-
}
|
|
6077
|
-
return null;
|
|
6078
|
-
}
|
|
6079
|
-
async function installSkills(recs, platforms, contentMap) {
|
|
6080
|
-
const spinner = ora5(`Installing ${recs.length} skill${recs.length > 1 ? "s" : ""}...`).start();
|
|
6081
|
-
const installed = [];
|
|
6082
|
-
for (const rec of recs) {
|
|
6083
|
-
const content = contentMap.get(rec.slug);
|
|
6084
|
-
if (!content) continue;
|
|
6085
|
-
for (const platform of platforms) {
|
|
6086
|
-
const skillPath = getSkillPath(platform, rec.slug);
|
|
6087
|
-
const fullPath = join8(process.cwd(), skillPath);
|
|
6088
|
-
mkdirSync(dirname2(fullPath), { recursive: true });
|
|
6089
|
-
writeFileSync(fullPath, content, "utf-8");
|
|
6090
|
-
installed.push(`[${platform}] ${skillPath}`);
|
|
6091
|
-
}
|
|
6092
|
-
}
|
|
6093
|
-
if (installed.length > 0) {
|
|
6094
|
-
spinner.succeed(`Installed ${installed.length} file${installed.length > 1 ? "s" : ""}`);
|
|
6095
|
-
for (const p of installed) {
|
|
6096
|
-
console.log(chalk9.green(` \u2713 ${p}`));
|
|
6097
|
-
}
|
|
6098
|
-
} else {
|
|
6099
|
-
spinner.fail("No skills were installed");
|
|
6100
|
-
}
|
|
6101
|
-
console.log("");
|
|
6102
|
-
}
|
|
6103
|
-
function printRecommendations(recs) {
|
|
6104
|
-
const hasScores = recs.some((r) => r.score > 0);
|
|
6105
|
-
console.log(chalk9.bold("\n Recommendations\n"));
|
|
6106
|
-
if (hasScores) {
|
|
6107
|
-
console.log(` ${chalk9.dim("Score".padEnd(7))} ${chalk9.dim("Name".padEnd(28))} ${chalk9.dim("Why")}`);
|
|
6108
|
-
} else {
|
|
6109
|
-
console.log(` ${chalk9.dim("Name".padEnd(30))} ${chalk9.dim("Technology".padEnd(18))} ${chalk9.dim("Source")}`);
|
|
6110
|
-
}
|
|
6111
|
-
console.log(chalk9.dim(" " + "\u2500".repeat(70)));
|
|
6112
|
-
for (const rec of recs) {
|
|
6113
|
-
if (hasScores) {
|
|
6114
|
-
console.log(` ${String(rec.score).padStart(3)} ${rec.name.padEnd(26)} ${chalk9.dim(rec.reason.slice(0, 50))}`);
|
|
6115
|
-
} else {
|
|
6116
|
-
console.log(` ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk9.dim(rec.source_url || "")}`);
|
|
6117
|
-
}
|
|
6118
|
-
}
|
|
6119
|
-
console.log("");
|
|
6179
|
+
displayScoreDelta(baselineScore, afterScore);
|
|
6180
|
+
console.log(chalk10.bold.green(" Regeneration complete!"));
|
|
6181
|
+
console.log(chalk10.dim(" Run ") + chalk10.hex("#83D1EB")("caliber undo") + chalk10.dim(" to revert changes.\n"));
|
|
6120
6182
|
}
|
|
6121
6183
|
|
|
6122
6184
|
// src/commands/score.ts
|
|
6123
|
-
import
|
|
6185
|
+
import chalk11 from "chalk";
|
|
6124
6186
|
async function scoreCommand(options) {
|
|
6125
6187
|
const dir = process.cwd();
|
|
6126
6188
|
const target = options.agent ?? readState()?.targetAgent;
|
|
@@ -6134,22 +6196,22 @@ async function scoreCommand(options) {
|
|
|
6134
6196
|
return;
|
|
6135
6197
|
}
|
|
6136
6198
|
displayScore(result);
|
|
6137
|
-
const separator =
|
|
6199
|
+
const separator = chalk11.gray(" " + "\u2500".repeat(53));
|
|
6138
6200
|
console.log(separator);
|
|
6139
6201
|
if (result.score < 40) {
|
|
6140
|
-
console.log(
|
|
6202
|
+
console.log(chalk11.gray(" Run ") + chalk11.hex("#83D1EB")("caliber onboard") + chalk11.gray(" to generate a complete, optimized setup."));
|
|
6141
6203
|
} else if (result.score < 70) {
|
|
6142
|
-
console.log(
|
|
6204
|
+
console.log(chalk11.gray(" Run ") + chalk11.hex("#83D1EB")("caliber onboard") + chalk11.gray(" to improve your setup."));
|
|
6143
6205
|
} else {
|
|
6144
|
-
console.log(
|
|
6206
|
+
console.log(chalk11.green(" Looking good!") + chalk11.gray(" Run ") + chalk11.hex("#83D1EB")("caliber regenerate") + chalk11.gray(" to rebuild from scratch."));
|
|
6145
6207
|
}
|
|
6146
6208
|
console.log("");
|
|
6147
6209
|
}
|
|
6148
6210
|
|
|
6149
6211
|
// src/commands/refresh.ts
|
|
6150
|
-
import
|
|
6212
|
+
import fs25 from "fs";
|
|
6151
6213
|
import path19 from "path";
|
|
6152
|
-
import
|
|
6214
|
+
import chalk12 from "chalk";
|
|
6153
6215
|
import ora6 from "ora";
|
|
6154
6216
|
|
|
6155
6217
|
// src/lib/git-diff.ts
|
|
@@ -6225,37 +6287,37 @@ function collectDiff(lastSha) {
|
|
|
6225
6287
|
}
|
|
6226
6288
|
|
|
6227
6289
|
// src/writers/refresh.ts
|
|
6228
|
-
import
|
|
6290
|
+
import fs24 from "fs";
|
|
6229
6291
|
import path18 from "path";
|
|
6230
6292
|
function writeRefreshDocs(docs) {
|
|
6231
6293
|
const written = [];
|
|
6232
6294
|
if (docs.claudeMd) {
|
|
6233
|
-
|
|
6295
|
+
fs24.writeFileSync("CLAUDE.md", docs.claudeMd);
|
|
6234
6296
|
written.push("CLAUDE.md");
|
|
6235
6297
|
}
|
|
6236
6298
|
if (docs.readmeMd) {
|
|
6237
|
-
|
|
6299
|
+
fs24.writeFileSync("README.md", docs.readmeMd);
|
|
6238
6300
|
written.push("README.md");
|
|
6239
6301
|
}
|
|
6240
6302
|
if (docs.cursorrules) {
|
|
6241
|
-
|
|
6303
|
+
fs24.writeFileSync(".cursorrules", docs.cursorrules);
|
|
6242
6304
|
written.push(".cursorrules");
|
|
6243
6305
|
}
|
|
6244
6306
|
if (docs.cursorRules) {
|
|
6245
6307
|
const rulesDir = path18.join(".cursor", "rules");
|
|
6246
|
-
if (!
|
|
6308
|
+
if (!fs24.existsSync(rulesDir)) fs24.mkdirSync(rulesDir, { recursive: true });
|
|
6247
6309
|
for (const rule of docs.cursorRules) {
|
|
6248
6310
|
const filePath = path18.join(rulesDir, rule.filename);
|
|
6249
|
-
|
|
6311
|
+
fs24.writeFileSync(filePath, rule.content);
|
|
6250
6312
|
written.push(filePath);
|
|
6251
6313
|
}
|
|
6252
6314
|
}
|
|
6253
6315
|
if (docs.claudeSkills) {
|
|
6254
6316
|
const skillsDir = path18.join(".claude", "skills");
|
|
6255
|
-
if (!
|
|
6317
|
+
if (!fs24.existsSync(skillsDir)) fs24.mkdirSync(skillsDir, { recursive: true });
|
|
6256
6318
|
for (const skill of docs.claudeSkills) {
|
|
6257
6319
|
const filePath = path18.join(skillsDir, skill.filename);
|
|
6258
|
-
|
|
6320
|
+
fs24.writeFileSync(filePath, skill.content);
|
|
6259
6321
|
written.push(filePath);
|
|
6260
6322
|
}
|
|
6261
6323
|
}
|
|
@@ -6332,11 +6394,11 @@ function log(quiet, ...args) {
|
|
|
6332
6394
|
function discoverGitRepos(parentDir) {
|
|
6333
6395
|
const repos = [];
|
|
6334
6396
|
try {
|
|
6335
|
-
const entries =
|
|
6397
|
+
const entries = fs25.readdirSync(parentDir, { withFileTypes: true });
|
|
6336
6398
|
for (const entry of entries) {
|
|
6337
6399
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
6338
6400
|
const childPath = path19.join(parentDir, entry.name);
|
|
6339
|
-
if (
|
|
6401
|
+
if (fs25.existsSync(path19.join(childPath, ".git"))) {
|
|
6340
6402
|
repos.push(childPath);
|
|
6341
6403
|
}
|
|
6342
6404
|
}
|
|
@@ -6346,7 +6408,7 @@ function discoverGitRepos(parentDir) {
|
|
|
6346
6408
|
}
|
|
6347
6409
|
async function refreshSingleRepo(repoDir, options) {
|
|
6348
6410
|
const quiet = !!options.quiet;
|
|
6349
|
-
const prefix = options.label ? `${
|
|
6411
|
+
const prefix = options.label ? `${chalk12.bold(options.label)} ` : "";
|
|
6350
6412
|
const state = readState();
|
|
6351
6413
|
const lastSha = state?.lastRefreshSha ?? null;
|
|
6352
6414
|
const diff = collectDiff(lastSha);
|
|
@@ -6355,7 +6417,7 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
6355
6417
|
if (currentSha) {
|
|
6356
6418
|
writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
6357
6419
|
}
|
|
6358
|
-
log(quiet,
|
|
6420
|
+
log(quiet, chalk12.dim(`${prefix}No changes since last refresh.`));
|
|
6359
6421
|
return;
|
|
6360
6422
|
}
|
|
6361
6423
|
const spinner = quiet ? null : ora6(`${prefix}Analyzing changes...`).start();
|
|
@@ -6387,10 +6449,10 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
6387
6449
|
if (options.dryRun) {
|
|
6388
6450
|
spinner?.info(`${prefix}Dry run \u2014 would update:`);
|
|
6389
6451
|
for (const doc of response.docsUpdated) {
|
|
6390
|
-
console.log(` ${
|
|
6452
|
+
console.log(` ${chalk12.yellow("~")} ${doc}`);
|
|
6391
6453
|
}
|
|
6392
6454
|
if (response.changesSummary) {
|
|
6393
|
-
console.log(
|
|
6455
|
+
console.log(chalk12.dim(`
|
|
6394
6456
|
${response.changesSummary}`));
|
|
6395
6457
|
}
|
|
6396
6458
|
return;
|
|
@@ -6398,10 +6460,10 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
6398
6460
|
const written = writeRefreshDocs(response.updatedDocs);
|
|
6399
6461
|
spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
|
|
6400
6462
|
for (const file of written) {
|
|
6401
|
-
log(quiet, ` ${
|
|
6463
|
+
log(quiet, ` ${chalk12.green("\u2713")} ${file}`);
|
|
6402
6464
|
}
|
|
6403
6465
|
if (response.changesSummary) {
|
|
6404
|
-
log(quiet,
|
|
6466
|
+
log(quiet, chalk12.dim(`
|
|
6405
6467
|
${response.changesSummary}`));
|
|
6406
6468
|
}
|
|
6407
6469
|
if (currentSha) {
|
|
@@ -6414,7 +6476,7 @@ async function refreshCommand(options) {
|
|
|
6414
6476
|
const config = loadConfig();
|
|
6415
6477
|
if (!config) {
|
|
6416
6478
|
if (quiet) return;
|
|
6417
|
-
console.log(
|
|
6479
|
+
console.log(chalk12.red("No LLM provider configured. Run ") + chalk12.hex("#83D1EB")("caliber config") + chalk12.red(" (e.g. choose Cursor) or set an API key."));
|
|
6418
6480
|
throw new Error("__exit__");
|
|
6419
6481
|
}
|
|
6420
6482
|
if (isGitRepo()) {
|
|
@@ -6424,10 +6486,10 @@ async function refreshCommand(options) {
|
|
|
6424
6486
|
const repos = discoverGitRepos(process.cwd());
|
|
6425
6487
|
if (repos.length === 0) {
|
|
6426
6488
|
if (quiet) return;
|
|
6427
|
-
console.log(
|
|
6489
|
+
console.log(chalk12.red("Not inside a git repository and no git repos found in child directories."));
|
|
6428
6490
|
throw new Error("__exit__");
|
|
6429
6491
|
}
|
|
6430
|
-
log(quiet,
|
|
6492
|
+
log(quiet, chalk12.dim(`Found ${repos.length} git repo${repos.length === 1 ? "" : "s"}
|
|
6431
6493
|
`));
|
|
6432
6494
|
const originalDir = process.cwd();
|
|
6433
6495
|
for (const repo of repos) {
|
|
@@ -6437,7 +6499,7 @@ async function refreshCommand(options) {
|
|
|
6437
6499
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
6438
6500
|
} catch (err) {
|
|
6439
6501
|
if (err instanceof Error && err.message === "__exit__") continue;
|
|
6440
|
-
log(quiet,
|
|
6502
|
+
log(quiet, chalk12.yellow(`${repoName}: refresh failed \u2014 ${err instanceof Error ? err.message : "unknown error"}`));
|
|
6441
6503
|
}
|
|
6442
6504
|
}
|
|
6443
6505
|
process.chdir(originalDir);
|
|
@@ -6445,13 +6507,13 @@ async function refreshCommand(options) {
|
|
|
6445
6507
|
if (err instanceof Error && err.message === "__exit__") throw err;
|
|
6446
6508
|
if (quiet) return;
|
|
6447
6509
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
6448
|
-
console.log(
|
|
6510
|
+
console.log(chalk12.red(`Refresh failed: ${msg}`));
|
|
6449
6511
|
throw new Error("__exit__");
|
|
6450
6512
|
}
|
|
6451
6513
|
}
|
|
6452
6514
|
|
|
6453
6515
|
// src/commands/hooks.ts
|
|
6454
|
-
import
|
|
6516
|
+
import chalk13 from "chalk";
|
|
6455
6517
|
var HOOKS = [
|
|
6456
6518
|
{
|
|
6457
6519
|
id: "session-end",
|
|
@@ -6471,13 +6533,13 @@ var HOOKS = [
|
|
|
6471
6533
|
}
|
|
6472
6534
|
];
|
|
6473
6535
|
function printStatus() {
|
|
6474
|
-
console.log(
|
|
6536
|
+
console.log(chalk13.bold("\n Hooks\n"));
|
|
6475
6537
|
for (const hook of HOOKS) {
|
|
6476
6538
|
const installed = hook.isInstalled();
|
|
6477
|
-
const icon = installed ?
|
|
6478
|
-
const state = installed ?
|
|
6539
|
+
const icon = installed ? chalk13.green("\u2713") : chalk13.dim("\u2717");
|
|
6540
|
+
const state = installed ? chalk13.green("enabled") : chalk13.dim("disabled");
|
|
6479
6541
|
console.log(` ${icon} ${hook.label.padEnd(26)} ${state}`);
|
|
6480
|
-
console.log(
|
|
6542
|
+
console.log(chalk13.dim(` ${hook.description}`));
|
|
6481
6543
|
}
|
|
6482
6544
|
console.log("");
|
|
6483
6545
|
}
|
|
@@ -6486,9 +6548,9 @@ async function hooksCommand(options) {
|
|
|
6486
6548
|
for (const hook of HOOKS) {
|
|
6487
6549
|
const result = hook.install();
|
|
6488
6550
|
if (result.alreadyInstalled) {
|
|
6489
|
-
console.log(
|
|
6551
|
+
console.log(chalk13.dim(` ${hook.label} already enabled.`));
|
|
6490
6552
|
} else {
|
|
6491
|
-
console.log(
|
|
6553
|
+
console.log(chalk13.green(" \u2713") + ` ${hook.label} enabled`);
|
|
6492
6554
|
}
|
|
6493
6555
|
}
|
|
6494
6556
|
return;
|
|
@@ -6497,9 +6559,9 @@ async function hooksCommand(options) {
|
|
|
6497
6559
|
for (const hook of HOOKS) {
|
|
6498
6560
|
const result = hook.remove();
|
|
6499
6561
|
if (result.notFound) {
|
|
6500
|
-
console.log(
|
|
6562
|
+
console.log(chalk13.dim(` ${hook.label} already disabled.`));
|
|
6501
6563
|
} else {
|
|
6502
|
-
console.log(
|
|
6564
|
+
console.log(chalk13.green(" \u2713") + ` ${hook.label} removed`);
|
|
6503
6565
|
}
|
|
6504
6566
|
}
|
|
6505
6567
|
return;
|
|
@@ -6514,18 +6576,18 @@ async function hooksCommand(options) {
|
|
|
6514
6576
|
const states = HOOKS.map((h) => h.isInstalled());
|
|
6515
6577
|
function render() {
|
|
6516
6578
|
const lines = [];
|
|
6517
|
-
lines.push(
|
|
6579
|
+
lines.push(chalk13.bold(" Hooks"));
|
|
6518
6580
|
lines.push("");
|
|
6519
6581
|
for (let i = 0; i < HOOKS.length; i++) {
|
|
6520
6582
|
const hook = HOOKS[i];
|
|
6521
6583
|
const enabled = states[i];
|
|
6522
|
-
const toggle = enabled ?
|
|
6523
|
-
const ptr = i === cursor ?
|
|
6584
|
+
const toggle = enabled ? chalk13.green("[on] ") : chalk13.dim("[off]");
|
|
6585
|
+
const ptr = i === cursor ? chalk13.cyan(">") : " ";
|
|
6524
6586
|
lines.push(` ${ptr} ${toggle} ${hook.label}`);
|
|
6525
|
-
lines.push(
|
|
6587
|
+
lines.push(chalk13.dim(` ${hook.description}`));
|
|
6526
6588
|
}
|
|
6527
6589
|
lines.push("");
|
|
6528
|
-
lines.push(
|
|
6590
|
+
lines.push(chalk13.dim(" \u2191\u2193 navigate \u23B5 toggle a all on n all off \u23CE apply q cancel"));
|
|
6529
6591
|
return lines.join("\n");
|
|
6530
6592
|
}
|
|
6531
6593
|
function draw(initial) {
|
|
@@ -6556,16 +6618,16 @@ async function hooksCommand(options) {
|
|
|
6556
6618
|
const wantEnabled = states[i];
|
|
6557
6619
|
if (wantEnabled && !wasInstalled) {
|
|
6558
6620
|
hook.install();
|
|
6559
|
-
console.log(
|
|
6621
|
+
console.log(chalk13.green(" \u2713") + ` ${hook.label} enabled`);
|
|
6560
6622
|
changed++;
|
|
6561
6623
|
} else if (!wantEnabled && wasInstalled) {
|
|
6562
6624
|
hook.remove();
|
|
6563
|
-
console.log(
|
|
6625
|
+
console.log(chalk13.green(" \u2713") + ` ${hook.label} disabled`);
|
|
6564
6626
|
changed++;
|
|
6565
6627
|
}
|
|
6566
6628
|
}
|
|
6567
6629
|
if (changed === 0) {
|
|
6568
|
-
console.log(
|
|
6630
|
+
console.log(chalk13.dim(" No changes."));
|
|
6569
6631
|
}
|
|
6570
6632
|
console.log("");
|
|
6571
6633
|
}
|
|
@@ -6601,7 +6663,7 @@ async function hooksCommand(options) {
|
|
|
6601
6663
|
case "\x1B":
|
|
6602
6664
|
case "":
|
|
6603
6665
|
cleanup();
|
|
6604
|
-
console.log(
|
|
6666
|
+
console.log(chalk13.dim("\n Cancelled.\n"));
|
|
6605
6667
|
resolve2();
|
|
6606
6668
|
break;
|
|
6607
6669
|
}
|
|
@@ -6611,48 +6673,48 @@ async function hooksCommand(options) {
|
|
|
6611
6673
|
}
|
|
6612
6674
|
|
|
6613
6675
|
// src/commands/config.ts
|
|
6614
|
-
import
|
|
6676
|
+
import chalk14 from "chalk";
|
|
6615
6677
|
async function configCommand() {
|
|
6616
6678
|
const existing = loadConfig();
|
|
6617
6679
|
if (existing) {
|
|
6618
6680
|
const displayModel = existing.model === "default" && existing.provider === "claude-cli" ? process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)" : existing.model;
|
|
6619
6681
|
const fastModel = getFastModel();
|
|
6620
|
-
console.log(
|
|
6621
|
-
console.log(` Provider: ${
|
|
6622
|
-
console.log(` Model: ${
|
|
6682
|
+
console.log(chalk14.bold("\nCurrent Configuration\n"));
|
|
6683
|
+
console.log(` Provider: ${chalk14.cyan(existing.provider)}`);
|
|
6684
|
+
console.log(` Model: ${chalk14.cyan(displayModel)}`);
|
|
6623
6685
|
if (fastModel) {
|
|
6624
|
-
console.log(` Scan: ${
|
|
6686
|
+
console.log(` Scan: ${chalk14.cyan(fastModel)}`);
|
|
6625
6687
|
}
|
|
6626
6688
|
if (existing.apiKey) {
|
|
6627
6689
|
const masked = existing.apiKey.slice(0, 8) + "..." + existing.apiKey.slice(-4);
|
|
6628
|
-
console.log(` API Key: ${
|
|
6690
|
+
console.log(` API Key: ${chalk14.dim(masked)}`);
|
|
6629
6691
|
}
|
|
6630
6692
|
if (existing.provider === "cursor") {
|
|
6631
|
-
console.log(` Seat: ${
|
|
6693
|
+
console.log(` Seat: ${chalk14.dim("Cursor (agent acp)")}`);
|
|
6632
6694
|
}
|
|
6633
6695
|
if (existing.provider === "claude-cli") {
|
|
6634
|
-
console.log(` Seat: ${
|
|
6696
|
+
console.log(` Seat: ${chalk14.dim("Claude Code (claude -p)")}`);
|
|
6635
6697
|
}
|
|
6636
6698
|
if (existing.baseUrl) {
|
|
6637
|
-
console.log(` Base URL: ${
|
|
6699
|
+
console.log(` Base URL: ${chalk14.dim(existing.baseUrl)}`);
|
|
6638
6700
|
}
|
|
6639
6701
|
if (existing.vertexProjectId) {
|
|
6640
|
-
console.log(` Vertex Project: ${
|
|
6641
|
-
console.log(` Vertex Region: ${
|
|
6702
|
+
console.log(` Vertex Project: ${chalk14.dim(existing.vertexProjectId)}`);
|
|
6703
|
+
console.log(` Vertex Region: ${chalk14.dim(existing.vertexRegion || "us-east5")}`);
|
|
6642
6704
|
}
|
|
6643
|
-
console.log(` Source: ${
|
|
6705
|
+
console.log(` Source: ${chalk14.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())}`);
|
|
6644
6706
|
console.log("");
|
|
6645
6707
|
}
|
|
6646
6708
|
await runInteractiveProviderSetup();
|
|
6647
|
-
console.log(
|
|
6648
|
-
console.log(
|
|
6709
|
+
console.log(chalk14.green("\n\u2713 Configuration saved"));
|
|
6710
|
+
console.log(chalk14.dim(` ${getConfigFilePath()}
|
|
6649
6711
|
`));
|
|
6650
|
-
console.log(
|
|
6651
|
-
console.log(
|
|
6712
|
+
console.log(chalk14.dim(" You can also set environment variables instead:"));
|
|
6713
|
+
console.log(chalk14.dim(" ANTHROPIC_API_KEY, OPENAI_API_KEY, VERTEX_PROJECT_ID, CALIBER_USE_CURSOR_SEAT=1, or CALIBER_USE_CLAUDE_CLI=1\n"));
|
|
6652
6714
|
}
|
|
6653
6715
|
|
|
6654
6716
|
// src/commands/learn.ts
|
|
6655
|
-
import
|
|
6717
|
+
import chalk15 from "chalk";
|
|
6656
6718
|
|
|
6657
6719
|
// src/learner/stdin.ts
|
|
6658
6720
|
var STDIN_TIMEOUT_MS = 5e3;
|
|
@@ -6683,7 +6745,7 @@ function readStdin() {
|
|
|
6683
6745
|
|
|
6684
6746
|
// src/learner/storage.ts
|
|
6685
6747
|
init_constants();
|
|
6686
|
-
import
|
|
6748
|
+
import fs26 from "fs";
|
|
6687
6749
|
import path20 from "path";
|
|
6688
6750
|
var MAX_RESPONSE_LENGTH = 2e3;
|
|
6689
6751
|
var DEFAULT_STATE = {
|
|
@@ -6692,8 +6754,8 @@ var DEFAULT_STATE = {
|
|
|
6692
6754
|
lastAnalysisTimestamp: null
|
|
6693
6755
|
};
|
|
6694
6756
|
function ensureLearningDir() {
|
|
6695
|
-
if (!
|
|
6696
|
-
|
|
6757
|
+
if (!fs26.existsSync(LEARNING_DIR)) {
|
|
6758
|
+
fs26.mkdirSync(LEARNING_DIR, { recursive: true });
|
|
6697
6759
|
}
|
|
6698
6760
|
}
|
|
6699
6761
|
function sessionFilePath() {
|
|
@@ -6711,49 +6773,49 @@ function appendEvent(event) {
|
|
|
6711
6773
|
ensureLearningDir();
|
|
6712
6774
|
const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
|
|
6713
6775
|
const filePath = sessionFilePath();
|
|
6714
|
-
|
|
6776
|
+
fs26.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
6715
6777
|
const count = getEventCount();
|
|
6716
6778
|
if (count > LEARNING_MAX_EVENTS) {
|
|
6717
|
-
const lines =
|
|
6779
|
+
const lines = fs26.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
6718
6780
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
6719
|
-
|
|
6781
|
+
fs26.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
6720
6782
|
}
|
|
6721
6783
|
}
|
|
6722
6784
|
function readAllEvents() {
|
|
6723
6785
|
const filePath = sessionFilePath();
|
|
6724
|
-
if (!
|
|
6725
|
-
const lines =
|
|
6786
|
+
if (!fs26.existsSync(filePath)) return [];
|
|
6787
|
+
const lines = fs26.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
6726
6788
|
return lines.map((line) => JSON.parse(line));
|
|
6727
6789
|
}
|
|
6728
6790
|
function getEventCount() {
|
|
6729
6791
|
const filePath = sessionFilePath();
|
|
6730
|
-
if (!
|
|
6731
|
-
const content =
|
|
6792
|
+
if (!fs26.existsSync(filePath)) return 0;
|
|
6793
|
+
const content = fs26.readFileSync(filePath, "utf-8");
|
|
6732
6794
|
return content.split("\n").filter(Boolean).length;
|
|
6733
6795
|
}
|
|
6734
6796
|
function clearSession() {
|
|
6735
6797
|
const filePath = sessionFilePath();
|
|
6736
|
-
if (
|
|
6798
|
+
if (fs26.existsSync(filePath)) fs26.unlinkSync(filePath);
|
|
6737
6799
|
}
|
|
6738
6800
|
function readState2() {
|
|
6739
6801
|
const filePath = stateFilePath();
|
|
6740
|
-
if (!
|
|
6802
|
+
if (!fs26.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
6741
6803
|
try {
|
|
6742
|
-
return JSON.parse(
|
|
6804
|
+
return JSON.parse(fs26.readFileSync(filePath, "utf-8"));
|
|
6743
6805
|
} catch {
|
|
6744
6806
|
return { ...DEFAULT_STATE };
|
|
6745
6807
|
}
|
|
6746
6808
|
}
|
|
6747
6809
|
function writeState2(state) {
|
|
6748
6810
|
ensureLearningDir();
|
|
6749
|
-
|
|
6811
|
+
fs26.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
|
|
6750
6812
|
}
|
|
6751
6813
|
function resetState() {
|
|
6752
6814
|
writeState2({ ...DEFAULT_STATE });
|
|
6753
6815
|
}
|
|
6754
6816
|
|
|
6755
6817
|
// src/learner/writer.ts
|
|
6756
|
-
import
|
|
6818
|
+
import fs27 from "fs";
|
|
6757
6819
|
import path21 from "path";
|
|
6758
6820
|
var LEARNED_START = "<!-- caliber:learned -->";
|
|
6759
6821
|
var LEARNED_END = "<!-- /caliber:learned -->";
|
|
@@ -6774,8 +6836,8 @@ function writeLearnedContent(update) {
|
|
|
6774
6836
|
function writeLearnedSection(content) {
|
|
6775
6837
|
const claudeMdPath = "CLAUDE.md";
|
|
6776
6838
|
let existing = "";
|
|
6777
|
-
if (
|
|
6778
|
-
existing =
|
|
6839
|
+
if (fs27.existsSync(claudeMdPath)) {
|
|
6840
|
+
existing = fs27.readFileSync(claudeMdPath, "utf-8");
|
|
6779
6841
|
}
|
|
6780
6842
|
const section = `${LEARNED_START}
|
|
6781
6843
|
${content}
|
|
@@ -6789,15 +6851,15 @@ ${LEARNED_END}`;
|
|
|
6789
6851
|
const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
6790
6852
|
updated = existing + separator + "\n" + section + "\n";
|
|
6791
6853
|
}
|
|
6792
|
-
|
|
6854
|
+
fs27.writeFileSync(claudeMdPath, updated);
|
|
6793
6855
|
}
|
|
6794
6856
|
function writeLearnedSkill(skill) {
|
|
6795
6857
|
const skillDir = path21.join(".claude", "skills", skill.name);
|
|
6796
|
-
if (!
|
|
6858
|
+
if (!fs27.existsSync(skillDir)) fs27.mkdirSync(skillDir, { recursive: true });
|
|
6797
6859
|
const skillPath = path21.join(skillDir, "SKILL.md");
|
|
6798
|
-
if (!skill.isNew &&
|
|
6799
|
-
const existing =
|
|
6800
|
-
|
|
6860
|
+
if (!skill.isNew && fs27.existsSync(skillPath)) {
|
|
6861
|
+
const existing = fs27.readFileSync(skillPath, "utf-8");
|
|
6862
|
+
fs27.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
6801
6863
|
} else {
|
|
6802
6864
|
const frontmatter = [
|
|
6803
6865
|
"---",
|
|
@@ -6806,14 +6868,14 @@ function writeLearnedSkill(skill) {
|
|
|
6806
6868
|
"---",
|
|
6807
6869
|
""
|
|
6808
6870
|
].join("\n");
|
|
6809
|
-
|
|
6871
|
+
fs27.writeFileSync(skillPath, frontmatter + skill.content);
|
|
6810
6872
|
}
|
|
6811
6873
|
return skillPath;
|
|
6812
6874
|
}
|
|
6813
6875
|
function readLearnedSection() {
|
|
6814
6876
|
const claudeMdPath = "CLAUDE.md";
|
|
6815
|
-
if (!
|
|
6816
|
-
const content =
|
|
6877
|
+
if (!fs27.existsSync(claudeMdPath)) return null;
|
|
6878
|
+
const content = fs27.readFileSync(claudeMdPath, "utf-8");
|
|
6817
6879
|
const startIdx = content.indexOf(LEARNED_START);
|
|
6818
6880
|
const endIdx = content.indexOf(LEARNED_END);
|
|
6819
6881
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
@@ -6953,53 +7015,53 @@ async function learnFinalizeCommand() {
|
|
|
6953
7015
|
async function learnInstallCommand() {
|
|
6954
7016
|
const result = installLearningHooks();
|
|
6955
7017
|
if (result.alreadyInstalled) {
|
|
6956
|
-
console.log(
|
|
7018
|
+
console.log(chalk15.dim("Learning hooks already installed."));
|
|
6957
7019
|
return;
|
|
6958
7020
|
}
|
|
6959
|
-
console.log(
|
|
6960
|
-
console.log(
|
|
6961
|
-
console.log(
|
|
7021
|
+
console.log(chalk15.green("\u2713") + " Learning hooks installed in .claude/settings.json");
|
|
7022
|
+
console.log(chalk15.dim(" PostToolUse, PostToolUseFailure, and SessionEnd hooks active."));
|
|
7023
|
+
console.log(chalk15.dim(" Session learnings will be written to CLAUDE.md and skills."));
|
|
6962
7024
|
}
|
|
6963
7025
|
async function learnRemoveCommand() {
|
|
6964
7026
|
const result = removeLearningHooks();
|
|
6965
7027
|
if (result.notFound) {
|
|
6966
|
-
console.log(
|
|
7028
|
+
console.log(chalk15.dim("Learning hooks not found."));
|
|
6967
7029
|
return;
|
|
6968
7030
|
}
|
|
6969
|
-
console.log(
|
|
7031
|
+
console.log(chalk15.green("\u2713") + " Learning hooks removed from .claude/settings.json");
|
|
6970
7032
|
}
|
|
6971
7033
|
async function learnStatusCommand() {
|
|
6972
7034
|
const installed = areLearningHooksInstalled();
|
|
6973
7035
|
const state = readState2();
|
|
6974
7036
|
const eventCount = getEventCount();
|
|
6975
|
-
console.log(
|
|
7037
|
+
console.log(chalk15.bold("Session Learning Status"));
|
|
6976
7038
|
console.log();
|
|
6977
7039
|
if (installed) {
|
|
6978
|
-
console.log(
|
|
7040
|
+
console.log(chalk15.green("\u2713") + " Learning hooks are " + chalk15.green("installed"));
|
|
6979
7041
|
} else {
|
|
6980
|
-
console.log(
|
|
6981
|
-
console.log(
|
|
7042
|
+
console.log(chalk15.dim("\u2717") + " Learning hooks are " + chalk15.yellow("not installed"));
|
|
7043
|
+
console.log(chalk15.dim(" Run `caliber learn install` to enable session learning."));
|
|
6982
7044
|
}
|
|
6983
7045
|
console.log();
|
|
6984
|
-
console.log(`Events recorded: ${
|
|
6985
|
-
console.log(`Total this session: ${
|
|
7046
|
+
console.log(`Events recorded: ${chalk15.cyan(String(eventCount))}`);
|
|
7047
|
+
console.log(`Total this session: ${chalk15.cyan(String(state.eventCount))}`);
|
|
6986
7048
|
if (state.lastAnalysisTimestamp) {
|
|
6987
|
-
console.log(`Last analysis: ${
|
|
7049
|
+
console.log(`Last analysis: ${chalk15.cyan(state.lastAnalysisTimestamp)}`);
|
|
6988
7050
|
} else {
|
|
6989
|
-
console.log(`Last analysis: ${
|
|
7051
|
+
console.log(`Last analysis: ${chalk15.dim("none")}`);
|
|
6990
7052
|
}
|
|
6991
7053
|
const learnedSection = readLearnedSection();
|
|
6992
7054
|
if (learnedSection) {
|
|
6993
7055
|
const lineCount = learnedSection.split("\n").filter(Boolean).length;
|
|
6994
7056
|
console.log(`
|
|
6995
|
-
Learned items in CLAUDE.md: ${
|
|
7057
|
+
Learned items in CLAUDE.md: ${chalk15.cyan(String(lineCount))}`);
|
|
6996
7058
|
}
|
|
6997
7059
|
}
|
|
6998
7060
|
|
|
6999
7061
|
// src/cli.ts
|
|
7000
7062
|
var __dirname = path22.dirname(fileURLToPath(import.meta.url));
|
|
7001
7063
|
var pkg = JSON.parse(
|
|
7002
|
-
|
|
7064
|
+
fs28.readFileSync(path22.resolve(__dirname, "..", "package.json"), "utf-8")
|
|
7003
7065
|
);
|
|
7004
7066
|
var program = new Command();
|
|
7005
7067
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
|
|
@@ -7020,7 +7082,7 @@ program.command("undo").description("Revert all config changes made by Caliber")
|
|
|
7020
7082
|
program.command("status").description("Show current Caliber setup status").option("--json", "Output as JSON").action(statusCommand);
|
|
7021
7083
|
program.command("regenerate").alias("regen").alias("re").description("Re-analyze project and regenerate setup").option("--dry-run", "Preview changes without writing files").action(regenerateCommand);
|
|
7022
7084
|
program.command("config").description("Configure LLM provider, API key, and model").action(configCommand);
|
|
7023
|
-
program.command("
|
|
7085
|
+
program.command("skills").description("Discover and install community skills for your project").action(recommendCommand);
|
|
7024
7086
|
program.command("score").description("Score your current agent config setup (deterministic, no network)").option("--json", "Output as JSON").option("--quiet", "One-line output for scripts/hooks").option("--agent <type>", "Target agents (comma-separated): claude, cursor, codex", parseAgentOption).action(scoreCommand);
|
|
7025
7087
|
program.command("refresh").description("Update docs based on recent code changes").option("--quiet", "Suppress output (for use in hooks)").option("--dry-run", "Preview changes without writing files").action(refreshCommand);
|
|
7026
7088
|
program.command("hooks").description("Manage auto-refresh hooks (toggle interactively)").option("--install", "Enable all hooks non-interactively").option("--remove", "Disable all hooks non-interactively").action(hooksCommand);
|
|
@@ -7032,22 +7094,22 @@ learn.command("remove").description("Remove learning hooks from .claude/settings
|
|
|
7032
7094
|
learn.command("status").description("Show learning system status").action(learnStatusCommand);
|
|
7033
7095
|
|
|
7034
7096
|
// src/utils/version-check.ts
|
|
7035
|
-
import
|
|
7097
|
+
import fs29 from "fs";
|
|
7036
7098
|
import path23 from "path";
|
|
7037
7099
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7038
7100
|
import { execSync as execSync9 } from "child_process";
|
|
7039
|
-
import
|
|
7101
|
+
import chalk16 from "chalk";
|
|
7040
7102
|
import ora7 from "ora";
|
|
7041
7103
|
import confirm from "@inquirer/confirm";
|
|
7042
7104
|
var __dirname_vc = path23.dirname(fileURLToPath2(import.meta.url));
|
|
7043
7105
|
var pkg2 = JSON.parse(
|
|
7044
|
-
|
|
7106
|
+
fs29.readFileSync(path23.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
7045
7107
|
);
|
|
7046
7108
|
function getInstalledVersion() {
|
|
7047
7109
|
try {
|
|
7048
7110
|
const globalRoot = execSync9("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
7049
7111
|
const pkgPath = path23.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
7050
|
-
return JSON.parse(
|
|
7112
|
+
return JSON.parse(fs29.readFileSync(pkgPath, "utf-8")).version;
|
|
7051
7113
|
} catch {
|
|
7052
7114
|
return null;
|
|
7053
7115
|
}
|
|
@@ -7070,17 +7132,17 @@ async function checkForUpdates() {
|
|
|
7070
7132
|
const isInteractive = process.stdin.isTTY === true;
|
|
7071
7133
|
if (!isInteractive) {
|
|
7072
7134
|
console.log(
|
|
7073
|
-
|
|
7135
|
+
chalk16.yellow(
|
|
7074
7136
|
`
|
|
7075
7137
|
Update available: ${current} -> ${latest}
|
|
7076
|
-
Run ${
|
|
7138
|
+
Run ${chalk16.bold("npm install -g @rely-ai/caliber")} to upgrade.
|
|
7077
7139
|
`
|
|
7078
7140
|
)
|
|
7079
7141
|
);
|
|
7080
7142
|
return;
|
|
7081
7143
|
}
|
|
7082
7144
|
console.log(
|
|
7083
|
-
|
|
7145
|
+
chalk16.yellow(`
|
|
7084
7146
|
Update available: ${current} -> ${latest}`)
|
|
7085
7147
|
);
|
|
7086
7148
|
const shouldUpdate = await confirm({ message: "Would you like to update now? (Y/n)", default: true });
|
|
@@ -7098,13 +7160,13 @@ Update available: ${current} -> ${latest}`)
|
|
|
7098
7160
|
const installed = getInstalledVersion();
|
|
7099
7161
|
if (installed !== latest) {
|
|
7100
7162
|
spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
|
|
7101
|
-
console.log(
|
|
7163
|
+
console.log(chalk16.yellow(`Run ${chalk16.bold(`npm install -g @rely-ai/caliber@${latest}`)} manually.
|
|
7102
7164
|
`));
|
|
7103
7165
|
return;
|
|
7104
7166
|
}
|
|
7105
|
-
spinner.succeed(
|
|
7167
|
+
spinner.succeed(chalk16.green(`Updated to ${latest}`));
|
|
7106
7168
|
const args = process.argv.slice(2);
|
|
7107
|
-
console.log(
|
|
7169
|
+
console.log(chalk16.dim(`
|
|
7108
7170
|
Restarting: caliber ${args.join(" ")}
|
|
7109
7171
|
`));
|
|
7110
7172
|
execSync9(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
@@ -7117,11 +7179,11 @@ Restarting: caliber ${args.join(" ")}
|
|
|
7117
7179
|
if (err instanceof Error) {
|
|
7118
7180
|
const stderr = err.stderr;
|
|
7119
7181
|
const errMsg = stderr ? String(stderr).trim().split("\n").pop() : err.message.split("\n")[0];
|
|
7120
|
-
if (errMsg && !errMsg.includes("SIGTERM")) console.log(
|
|
7182
|
+
if (errMsg && !errMsg.includes("SIGTERM")) console.log(chalk16.dim(` ${errMsg}`));
|
|
7121
7183
|
}
|
|
7122
7184
|
console.log(
|
|
7123
|
-
|
|
7124
|
-
`Run ${
|
|
7185
|
+
chalk16.yellow(
|
|
7186
|
+
`Run ${chalk16.bold(`npm install -g @rely-ai/caliber@${latest}`)} manually to upgrade.
|
|
7125
7187
|
`
|
|
7126
7188
|
)
|
|
7127
7189
|
);
|