@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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/bin.js +1743 -1681
  3. 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 fs27 from "fs";
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 chalk5 from "chalk";
60
- import ora2 from "ora";
59
+ import chalk7 from "chalk";
60
+ import ora3 from "ora";
61
61
  import readline4 from "readline";
62
- import select2 from "@inquirer/select";
62
+ import select4 from "@inquirer/select";
63
63
  import checkbox from "@inquirer/checkbox";
64
- import fs20 from "fs";
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
- - The "name" field must be kebab-case (lowercase letters, numbers, hyphens only). It becomes the directory name.
1247
- - The "description" field should describe what the skill does AND when to use it \u2014 this drives automatic skill discovery by agents.
1248
- - The "content" field is the markdown body only \u2014 do NOT include YAML frontmatter in the content, it will be generated from the name and description fields.
1249
- - Keep skill content under 500 lines. Move detailed references to separate files if needed.
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/commands/onboard.ts
2205
- import { createTwoFilesPatch } from "diff";
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 fs14 from "fs";
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 (!fs14.existsSync("AGENTS.md") && !(codex && codex.agentsMd)) {
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 fs15 from "fs";
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 (!fs15.existsSync(SETTINGS_PATH)) return {};
2495
+ if (!fs16.existsSync(SETTINGS_PATH)) return {};
2283
2496
  try {
2284
- return JSON.parse(fs15.readFileSync(SETTINGS_PATH, "utf-8"));
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 (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
2292
- fs15.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
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 || !fs15.existsSync(hookPath)) return false;
2362
- const content = fs15.readFileSync(hookPath, "utf-8");
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 (!fs15.existsSync(hooksDir)) fs15.mkdirSync(hooksDir, { recursive: true });
2585
+ if (!fs16.existsSync(hooksDir)) fs16.mkdirSync(hooksDir, { recursive: true });
2373
2586
  let content = "";
2374
- if (fs15.existsSync(hookPath)) {
2375
- content = fs15.readFileSync(hookPath, "utf-8");
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
- fs15.writeFileSync(hookPath, content);
2382
- fs15.chmodSync(hookPath, 493);
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 || !fs15.existsSync(hookPath)) {
2600
+ if (!hookPath || !fs16.existsSync(hookPath)) {
2388
2601
  return { removed: false, notFound: true };
2389
2602
  }
2390
- let content = fs15.readFileSync(hookPath, "utf-8");
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
- fs15.unlinkSync(hookPath);
2610
+ fs16.unlinkSync(hookPath);
2398
2611
  } else {
2399
- fs15.writeFileSync(hookPath, content);
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 fs16 from "fs";
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 (!fs16.existsSync(SETTINGS_PATH2)) return {};
2639
+ if (!fs17.existsSync(SETTINGS_PATH2)) return {};
2427
2640
  try {
2428
- return JSON.parse(fs16.readFileSync(SETTINGS_PATH2, "utf-8"));
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 (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
2436
- fs16.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
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 fs17 from "fs";
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 (!fs17.existsSync(STATE_FILE)) return null;
2508
- const raw = JSON.parse(fs17.readFileSync(STATE_FILE, "utf-8"));
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 (!fs17.existsSync(CALIBER_DIR)) {
2517
- fs17.mkdirSync(CALIBER_DIR, { recursive: true });
2729
+ if (!fs18.existsSync(CALIBER_DIR)) {
2730
+ fs18.mkdirSync(CALIBER_DIR, { recursive: true });
2518
2731
  }
2519
- fs17.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
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 chalk from "chalk";
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 = chalk.dim(`(${this.formatElapsed()})`);
2796
+ this.spinner.suffixText = chalk2.dim(`(${this.formatElapsed()})`);
2584
2797
  this.elapsedTimer = setInterval(() => {
2585
- this.spinner.suffixText = chalk.dim(`(${this.formatElapsed()})`);
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 chalk2 from "chalk";
2833
+ import chalk3 from "chalk";
2621
2834
  import readline2 from "readline";
2622
- import select from "@inquirer/select";
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(chalk2.cyan(`${question} `), (answer) => {
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 select({
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(chalk2.dim(" Run `claude` once and log in with your Pro/Max/Team account if you haven't."));
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(chalk2.dim(" Run `agent login` if you haven't, or set CURSOR_API_KEY."));
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(chalk2.dim(" Get a key at https://console.anthropic.com (same account as Claude Pro/Team/Max)."));
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(chalk2.red("API key is required."));
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(chalk2.red("Project ID is required."));
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(chalk2.red("API key is required."));
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 fs18 from "fs";
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 (!fs18.existsSync(DISMISSED_FILE)) return [];
3746
- return JSON.parse(fs18.readFileSync(DISMISSED_FILE, "utf-8"));
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 (!fs18.existsSync(CALIBER_DIR)) {
3753
- fs18.mkdirSync(CALIBER_DIR, { recursive: true });
3965
+ if (!fs19.existsSync(CALIBER_DIR)) {
3966
+ fs19.mkdirSync(CALIBER_DIR, { recursive: true });
3754
3967
  }
3755
- fs18.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
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 chalk3 from "chalk";
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 chalk3.green;
4050
+ return chalk4.green;
3838
4051
  case "B":
3839
- return chalk3.greenBright;
4052
+ return chalk4.greenBright;
3840
4053
  case "C":
3841
- return chalk3.yellow;
4054
+ return chalk4.yellow;
3842
4055
  case "D":
3843
- return chalk3.hex("#f97316");
4056
+ return chalk4.hex("#f97316");
3844
4057
  case "F":
3845
- return chalk3.red;
4058
+ return chalk4.red;
3846
4059
  default:
3847
- return chalk3.white;
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 = chalk3.hex("#f97316")("\u2593".repeat(filled)) + chalk3.gray("\u2591".repeat(empty));
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 ? chalk3.green("\u2713") : check.earnedPoints < 0 ? chalk3.red("\u2717") : chalk3.gray("\u2717");
3858
- const points = check.passed ? chalk3.green(`+${check.earnedPoints}`.padStart(4)) : check.earnedPoints < 0 ? chalk3.red(`${check.earnedPoints}`.padStart(4)) : chalk3.gray(" \u2014");
3859
- const name = check.passed ? chalk3.white(check.name) : chalk3.gray(check.name);
3860
- const detail = check.detail ? chalk3.gray(` (${check.detail})`) : "";
3861
- const suggestion = !check.passed && check.suggestion ? chalk3.gray(`
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(chalk3.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"));
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(` ${chalk3.bold("Agent Config Score")} ${gc(chalk3.bold(`${result.score} / ${result.maxScore}`))} Grade ${gc(chalk3.bold(result.grade))}`);
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(chalk3.dim(` Target: ${agentLabel}`));
4086
+ console.log(chalk4.dim(` Target: ${agentLabel}`));
3874
4087
  console.log("");
3875
- console.log(chalk3.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"));
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
- chalk3.gray(` ${CATEGORY_LABELS[category]}`) + chalk3.gray(" ".repeat(Math.max(1, 45 - CATEGORY_LABELS[category].length))) + chalk3.white(`${summary.earned}`) + chalk3.gray(` / ${summary.max}`)
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
- chalk3.gray(" ") + gc(`${result.score}/${result.maxScore}`) + chalk3.gray(` (Grade ${result.grade})`) + chalk3.gray(` \xB7 ${agentLabel}`) + chalk3.gray(` \xB7 ${progressBar(result.score, result.maxScore, 20)}`)
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(chalk3.gray(` \u2717 ${check.name}`));
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(chalk3.dim(`
3905
- Run ${chalk3.hex("#83D1EB")("caliber score")} for details.${moreText}`));
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 ? chalk3.green : chalk3.red;
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(chalk3.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"));
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}`)} ${chalk3.gray("\u2192")} ${afterGc(`${after.score}`)} ${deltaColor(deltaStr + " pts")} ${beforeGc(before.grade)} ${chalk3.gray("\u2192")} ${afterGc(after.grade)}`
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)} ${chalk3.gray("\u2192")} ${progressBar(after.score, after.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(chalk3.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"));
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(chalk3.gray(" What improved:"));
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
- chalk3.green(" +") + chalk3.white(` ${check.name.padEnd(50)}`) + chalk3.green(`+${gain}`)
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 chalk4 from "chalk";
4156
+ import chalk5 from "chalk";
3944
4157
  import ora from "ora";
3945
4158
  import readline3 from "readline";
3946
- import fs19 from "fs";
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(chalk4.hex("#6366f1").bold("\n MCP Server Discovery\n"));
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(chalk4.dim(" No external tools or services detected \u2014 skipping MCP discovery"));
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(chalk4.dim(` Detected: ${toolDeps.join(", ")}`));
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(chalk4.dim("All detected tools already have MCP servers configured"));
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(chalk4.dim("No MCP servers found for detected tools"));
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(chalk4.dim("No quality MCP servers passed validation"));
4248
- console.log(chalk4.dim(` Candidates checked: ${candidates.map((c) => c.name).join(", ")}`));
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(chalk4.dim(` Scored: ${scored.map((c) => `${c.name} (${c.score})`).join(", ")}`));
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(chalk4.bold(`
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(chalk4.yellow(` Could not fetch README for ${mcp.repoFullName} \u2014 skipping`));
4265
- console.log(chalk4.dim(` Manual setup: ${mcp.url}`));
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(chalk4.yellow(` Could not extract config for ${mcp.name} \u2014 skipping`));
4271
- console.log(chalk4.dim(` Manual setup: ${mcp.url}`));
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(` ${chalk4.green("\u2713")} ${mcp.name} configured`);
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 (!fs19.existsSync(cursorDir)) fs19.mkdirSync(cursorDir, { recursive: true });
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 (fs19.existsSync(filePath)) {
4308
- const parsed = JSON.parse(fs19.readFileSync(filePath, "utf-8"));
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
- fs19.writeFileSync(filePath, JSON.stringify({ mcpServers: merged }, null, 2) + "\n");
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(chalk4.cyan(`${question}: `), (answer) => {
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(chalk4.bold("\n Available MCP servers:\n"));
4554
+ console.log(chalk5.bold("\n Available MCP servers:\n"));
4342
4555
  for (const c of candidates) {
4343
- const vendorTag = c.vendor ? chalk4.blue(" (vendor)") : "";
4344
- console.log(` ${String(c.score).padStart(3)} ${c.name}${vendorTag} ${chalk4.dim(c.reason)}`);
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(chalk4.bold(" Select MCP servers to install:"));
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) ? chalk4.green("[x]") : "[ ]";
4360
- const ptr = i === cursor ? chalk4.cyan(">") : " ";
4361
- const scoreColor = c.score >= 90 ? chalk4.green : c.score >= 70 ? chalk4.yellow : chalk4.dim;
4362
- const vendorTag = c.vendor ? chalk4.blue(" (vendor)") : "";
4363
- lines.push(` ${ptr} ${check} ${scoreColor(String(c.score).padStart(3))} ${c.name}${vendorTag} ${chalk4.dim(c.reason.slice(0, 40))}`);
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(chalk4.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q skip"));
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(chalk4.dim("\n No MCP servers selected.\n"));
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(chalk4.dim("\n Skipped MCP server installation.\n"));
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/onboard.ts
4435
- async function initCommand(options) {
4436
- const brand = chalk5.hex("#EB9D83");
4437
- const title = chalk5.hex("#83D1EB");
4438
- console.log(brand.bold(`
4439
- \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
4440
- \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
4441
- \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
4442
- \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
4443
- \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
4444
- \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
4445
- `));
4446
- console.log(chalk5.dim(" Onboard your project for AI-assisted development\n"));
4447
- console.log(title.bold(" Welcome to Caliber\n"));
4448
- console.log(chalk5.dim(" Caliber analyzes your codebase and creates tailored config files"));
4449
- console.log(chalk5.dim(" so your AI coding agents understand your project from day one.\n"));
4450
- console.log(title.bold(" How onboarding works:\n"));
4451
- console.log(chalk5.dim(" 1. Connect Set up your LLM provider"));
4452
- console.log(chalk5.dim(" 2. Discover Analyze your code, dependencies, and structure"));
4453
- console.log(chalk5.dim(" 3. Generate Create config files tailored to your project"));
4454
- console.log(chalk5.dim(" 4. Review Preview, refine, and apply the changes"));
4455
- console.log(chalk5.dim(" 5. Enhance Discover MCP servers for your tools\n"));
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 allFailingChecks = baselineScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
4510
- const llmFixableChecks = allFailingChecks.filter((c) => !NON_LLM_CHECKS.has(c.id));
4511
- if (hasExistingConfig && llmFixableChecks.length === 0 && allFailingChecks.length > 0 && !options.force) {
4512
- console.log(chalk5.bold.green("\n Your config is fully optimized for LLM generation.\n"));
4513
- console.log(chalk5.dim(" Remaining items need CLI actions:\n"));
4514
- for (const check of allFailingChecks) {
4515
- console.log(chalk5.dim(` \u2022 ${check.name}`));
4516
- if (check.suggestion) {
4517
- console.log(` ${chalk5.hex("#83D1EB")(check.suggestion)}`);
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
- let failingChecks;
4529
- let passingChecks;
4530
- let currentScore;
4531
- if (hasExistingConfig && baselineScore.score >= 95 && !options.force) {
4532
- failingChecks = llmFixableChecks.map((c) => ({ name: c.name, suggestion: c.suggestion }));
4533
- passingChecks = baselineScore.checks.filter((c) => c.passed).map((c) => ({ name: c.name }));
4534
- currentScore = baselineScore.score;
4535
- if (failingChecks.length > 0) {
4536
- console.log(title.bold(" Step 3/5 \u2014 Fine-tuning\n"));
4537
- console.log(chalk5.dim(` Your setup scores ${baselineScore.score}/100 \u2014 fixing ${failingChecks.length} remaining issue${failingChecks.length === 1 ? "" : "s"}:
4538
- `));
4539
- for (const check of failingChecks) {
4540
- console.log(chalk5.dim(` \u2022 ${check.name}`));
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
- console.log("");
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
- console.log(chalk5.dim(" This can take a couple of minutes depending on your model and provider.\n"));
4553
- const genStartTime = Date.now();
4554
- const genSpinner = ora2("Generating setup...").start();
4555
- const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
4556
- genMessages.start();
4557
- let generatedSetup = null;
4558
- let rawOutput;
4559
- try {
4560
- const result = await generateSetup(
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
- genMessages.stop();
4591
- if (!generatedSetup) {
4592
- genSpinner.fail("Failed to generate setup.");
4593
- if (rawOutput) {
4594
- console.log(chalk5.dim("\nRaw LLM output (JSON parse failed):"));
4595
- console.log(chalk5.dim(rawOutput.slice(0, 500)));
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 elapsedMs = Date.now() - genStartTime;
4600
- const mins = Math.floor(elapsedMs / 6e4);
4601
- const secs = Math.floor(elapsedMs % 6e4 / 1e3);
4602
- const timeStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
4603
- genSpinner.succeed(`Setup generated ${chalk5.dim(`in ${timeStr}`)}`);
4604
- printSetupSummary(generatedSetup);
4605
- const sessionHistory = [];
4606
- sessionHistory.push({
4607
- role: "assistant",
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
- while (action === "refine") {
4630
- generatedSetup = await refineLoop(generatedSetup, targetAgent, sessionHistory);
4631
- if (!generatedSetup) {
4632
- cleanupStaging();
4633
- console.log(chalk5.dim("Refinement cancelled. No files were modified."));
4634
- return;
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 writeSpinner = ora2("Writing config files...").start();
4655
- try {
4656
- const result = writeSetup(generatedSetup);
4657
- writeSpinner.succeed("Config files written");
4658
- console.log(chalk5.bold("\nFiles created/updated:"));
4659
- for (const file of result.written) {
4660
- console.log(` ${chalk5.green("\u2713")} ${file}`);
4661
- }
4662
- if (result.deleted.length > 0) {
4663
- console.log(chalk5.bold("\nFiles removed:"));
4664
- for (const file of result.deleted) {
4665
- console.log(` ${chalk5.red("\u2717")} ${file}`);
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
- console.log(title.bold("\n Step 5/5 \u2014 Enhance with MCP servers\n"));
4678
- console.log(chalk5.dim(" MCP servers connect your AI agents to external tools and services"));
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 mcpResult = await discoverAndInstallMcps(targetAgent, fingerprint, process.cwd());
4683
- if (mcpResult.installed > 0) {
4684
- console.log(chalk5.bold(`
4685
- ${mcpResult.installed} MCP server${mcpResult.installed > 1 ? "s" : ""} configured`));
4686
- for (const name of mcpResult.names) {
4687
- console.log(` ${chalk5.green("\u2713")} ${name}`);
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 (err) {
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
- ensurePermissions();
4697
- const sha = getCurrentHeadSha();
4698
- writeState({
4699
- lastRefreshSha: sha ?? "",
4700
- lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
4701
- targetAgent
4702
- });
4703
- console.log("");
4704
- console.log(title.bold(" Keep your configs fresh\n"));
4705
- console.log(chalk5.dim(" Caliber can automatically update your agent configs when your code changes.\n"));
4706
- const hookChoice = await promptHookType(targetAgent);
4707
- if (hookChoice === "claude" || hookChoice === "both") {
4708
- const hookResult = installHook();
4709
- if (hookResult.installed) {
4710
- console.log(` ${chalk5.green("\u2713")} Claude Code hook installed \u2014 docs update on session end`);
4711
- console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber hooks --remove") + chalk5.dim(" to disable"));
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
- if (hookChoice === "precommit" || hookChoice === "both") {
4724
- const precommitResult = installPreCommitHook();
4725
- if (precommitResult.installed) {
4726
- console.log(` ${chalk5.green("\u2713")} Pre-commit hook installed \u2014 docs refresh before each commit`);
4727
- console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber hooks --remove") + chalk5.dim(" to disable"));
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 (hookChoice === "skip") {
4735
- console.log(chalk5.dim(" Skipped auto-refresh hooks. Run ") + chalk5.hex("#83D1EB")("caliber hooks --install") + chalk5.dim(" later to enable."));
4811
+ if (platform === "codex") {
4812
+ return join8(".agents", "skills", slug, "SKILL.md");
4736
4813
  }
4737
- const afterScore = computeLocalScore(process.cwd(), targetAgent);
4738
- if (afterScore.score < baselineScore.score) {
4739
- console.log("");
4740
- console.log(chalk5.yellow(` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`));
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 { restored, removed } = undoSetup();
4743
- if (restored.length > 0 || removed.length > 0) {
4744
- console.log(chalk5.dim(` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`));
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
- displayScoreDelta(baselineScore, afterScore);
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 refineLoop(currentSetup, _targetAgent, sessionHistory) {
4761
- while (true) {
4762
- const message = await promptInput3("\nWhat would you like to change?");
4763
- if (!message || message.toLowerCase() === "done" || message.toLowerCase() === "accept") {
4764
- return currentSetup;
4765
- }
4766
- if (message.toLowerCase() === "cancel") {
4767
- return null;
4768
- }
4769
- const isValid = await classifyRefineIntent(message);
4770
- if (!isValid) {
4771
- console.log(chalk5.dim(" This doesn't look like a config change request."));
4772
- console.log(chalk5.dim(" Describe what to add, remove, or modify in your configs."));
4773
- console.log(chalk5.dim(' Type "done" to accept the current setup.\n'));
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
- const refineSpinner = ora2("Refining setup...").start();
4777
- const refineMessages = new SpinnerMessages(refineSpinner, REFINE_MESSAGES);
4778
- refineMessages.start();
4779
- const refined = await refineSetup(
4780
- currentSetup,
4781
- message,
4782
- sessionHistory
4783
- );
4784
- refineMessages.stop();
4785
- if (refined) {
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
- refineSpinner.succeed("Setup updated");
4793
- printSetupSummary(refined);
4794
- console.log(chalk5.dim('Type "done" to accept, or describe more changes.'));
4795
- } else {
4796
- refineSpinner.fail("Refinement failed \u2014 could not parse AI response.");
4797
- console.log(chalk5.dim('Try rephrasing your request, or type "done" to keep the current setup.'));
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
- function summarizeSetup(action, setup) {
4802
- const descriptions = setup.fileDescriptions;
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 result = await llmJsonCall({
4811
- system: `You classify whether a user message is a valid request to modify AI agent config files (CLAUDE.md, .cursorrules, skills).
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
- return result.valid === true;
4820
- } catch {
4821
- return true;
4822
- }
4823
- }
4824
- async function evaluateDismissals(failingChecks, fingerprint) {
4825
- const fastModel = getFastModel();
4826
- const checkList = failingChecks.map((c) => ({
4827
- id: c.id,
4828
- name: c.name,
4829
- suggestion: c.suggestion
4830
- }));
4831
- try {
4832
- const result = await llmJsonCall({
4833
- system: `You evaluate whether scoring checks are applicable to a project.
4834
- Given the project's languages/frameworks and a list of failing checks, return which checks are NOT applicable.
4835
-
4836
- 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.
4837
- Do NOT dismiss checks that could reasonably apply even if the project doesn't use them yet.
4838
-
4839
- Return {"dismissed": [{"id": "check_id", "reason": "brief reason"}]} or {"dismissed": []} if all apply.`,
4840
- prompt: `Languages: ${fingerprint.languages.join(", ") || "none"}
4841
- Frameworks: ${fingerprint.frameworks.join(", ") || "none"}
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 promptInput3(question) {
4855
- const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
4856
- return new Promise((resolve2) => {
4857
- rl.question(chalk5.cyan(`${question} `), (answer) => {
4858
- rl.close();
4859
- resolve2(answer.trim());
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 promptAgent() {
4864
- const selected = await checkbox({
4865
- message: "Which coding agents do you use? (toggle with space)",
4866
- choices: [
4867
- { name: "Claude Code", value: "claude" },
4868
- { name: "Cursor", value: "cursor" },
4869
- { name: "Codex (OpenAI)", value: "codex" }
4870
- ],
4871
- validate: (items) => {
4872
- if (items.length === 0) return "At least one agent must be selected";
4873
- return true;
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 selected;
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
- async function promptHookType(targetAgent) {
4879
- const choices = [];
4880
- const hasClaude = targetAgent.includes("claude");
4881
- if (hasClaude) {
4882
- choices.push({ name: "Claude Code hook (auto-refresh on session end)", value: "claude" });
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
- choices.push({ name: "Git pre-commit hook (refresh before each commit)", value: "precommit" });
4885
- if (hasClaude) {
4886
- choices.push({ name: "Both (Claude Code + pre-commit)", value: "both" });
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
- choices.push({ name: "Skip for now", value: "skip" });
4889
- return select2({
4890
- message: "How would you like to auto-refresh your docs?",
4891
- choices
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
- async function promptWantsReview() {
4895
- const answer = await select2({
4896
- message: "Would you like to review the diffs before deciding?",
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, show me the diffs", value: true },
4899
- { name: "No, continue", value: false }
5100
+ { name: "Yes, find skills for my project", value: true },
5101
+ { name: "No, cancel", value: false }
4900
5102
  ]
4901
5103
  });
4902
- return answer;
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
- const fileInfos = stagedFiles.map((file) => {
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 interactiveDiffExplorer(files) {
4954
- if (!process.stdin.isTTY) {
4955
- for (const f of files) {
4956
- const icon = f.isNew ? chalk5.green("+") : chalk5.yellow("~");
4957
- const stats = f.isNew ? chalk5.dim(`${f.lines} lines`) : `${chalk5.green(`+${f.added}`)} ${chalk5.red(`-${f.removed}`)}`;
4958
- console.log(` ${icon} ${f.relativePath} ${stats}`);
4959
- }
4960
- console.log("");
4961
- return;
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 { stdin, stdout } = process;
4964
- let cursor = 0;
4965
- let viewing = null;
4966
- let scrollOffset = 0;
4967
- let lineCount = 0;
4968
- function getTermHeight() {
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
- function renderFileList() {
4972
- const lines = [];
4973
- lines.push(chalk5.bold(" Review changes"));
4974
- lines.push("");
4975
- for (let i = 0; i < files.length; i++) {
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
- function renderDiff(index) {
4987
- const f = files[index];
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
- const header = f.isNew ? ` ${chalk5.green("+")} ${f.relativePath} ${chalk5.dim("(new file)")}` : ` ${chalk5.yellow("~")} ${f.relativePath} ${chalk5.green(`+${f.added}`)} ${chalk5.red(`-${f.removed}`)}`;
4990
- lines.push(header);
4991
- lines.push(chalk5.dim(" " + "\u2500".repeat(60)));
4992
- const patchLines = f.patch.split("\n");
4993
- const bodyLines = patchLines.slice(4);
4994
- const maxVisible = getTermHeight() - 4;
4995
- const visibleLines = bodyLines.slice(scrollOffset, scrollOffset + maxVisible);
4996
- for (const line of visibleLines) {
4997
- if (line.startsWith("+")) {
4998
- lines.push(chalk5.green(" " + line));
4999
- } else if (line.startsWith("-")) {
5000
- lines.push(chalk5.red(" " + line));
5001
- } else if (line.startsWith("@@")) {
5002
- lines.push(chalk5.cyan(" " + line));
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(chalk5.dim(" " + line));
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(chalk5.dim(" \u2191\u2193 scroll \u23B5/esc back to file list"));
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 = viewing !== null ? renderDiff(viewing) : renderFileList();
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
- if (viewing !== null) {
5038
- const f = files[viewing];
5039
- const totalBody = f.patch.split("\n").length - 4;
5040
- const maxVisible = getTermHeight() - 4;
5041
- switch (key) {
5042
- case "\x1B[A":
5043
- scrollOffset = Math.max(0, scrollOffset - 1);
5044
- draw(false);
5045
- break;
5046
- case "\x1B[B":
5047
- scrollOffset = Math.min(Math.max(0, totalBody - maxVisible), scrollOffset + 1);
5048
- draw(false);
5049
- break;
5050
- case " ":
5051
- case "\x1B":
5052
- viewing = null;
5053
- scrollOffset = 0;
5054
- draw(false);
5055
- break;
5056
- case "q":
5057
- case "":
5058
- cleanup();
5059
- console.log("");
5060
- resolve2();
5061
- break;
5062
- }
5063
- } else {
5064
- switch (key) {
5065
- case "\x1B[A":
5066
- cursor = (cursor - 1 + files.length) % files.length;
5067
- draw(false);
5068
- break;
5069
- case "\x1B[B":
5070
- cursor = (cursor + 1) % files.length;
5071
- draw(false);
5072
- break;
5073
- case "\r":
5074
- case "\n":
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 promptReviewAction() {
5092
- return select2({
5093
- message: "What would you like to do?",
5094
- choices: [
5095
- { name: "Accept and apply", value: "accept" },
5096
- { name: "Refine via chat", value: "refine" },
5097
- { name: "Decline", value: "decline" }
5098
- ]
5099
- });
5100
- }
5101
- function printSetupSummary(setup) {
5102
- const claude = setup.claude;
5103
- const cursor = setup.cursor;
5104
- const fileDescriptions = setup.fileDescriptions;
5105
- const deletions = setup.deletions;
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
- const codex = setup.codex;
5132
- if (codex) {
5133
- if (codex.agentsMd) {
5134
- const icon = fs20.existsSync("AGENTS.md") ? chalk5.yellow("~") : chalk5.green("+");
5135
- const desc = getDescription("AGENTS.md");
5136
- console.log(` ${icon} ${chalk5.bold("AGENTS.md")}`);
5137
- if (desc) console.log(chalk5.dim(` ${desc}`));
5138
- console.log("");
5139
- }
5140
- const codexSkills = codex.skills;
5141
- if (Array.isArray(codexSkills) && codexSkills.length > 0) {
5142
- for (const skill of codexSkills) {
5143
- const skillPath = `.agents/skills/${skill.name}/SKILL.md`;
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
- if (cursor) {
5153
- if (cursor.cursorrules) {
5154
- const icon = fs20.existsSync(".cursorrules") ? chalk5.yellow("~") : chalk5.green("+");
5155
- const desc = getDescription(".cursorrules");
5156
- console.log(` ${icon} ${chalk5.bold(".cursorrules")}`);
5157
- if (desc) console.log(chalk5.dim(` ${desc}`));
5158
- console.log("");
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
- const cursorSkills = cursor.skills;
5161
- if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
5162
- for (const skill of cursorSkills) {
5163
- const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
5164
- const icon = fs20.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
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 ensurePermissions() {
5204
- const settingsPath = ".claude/settings.json";
5205
- let settings = {};
5206
- try {
5207
- if (fs20.existsSync(settingsPath)) {
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(` LLM: ${chalk7.yellow("Not configured")} \u2014 run ${chalk7.hex("#83D1EB")("caliber config")}`);
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(` Files managed: ${chalk7.cyan(manifest.entries.length.toString())}`);
5284
- for (const entry of manifest.entries) {
5285
- const exists = fs21.existsSync(entry.path);
5286
- const icon = exists ? chalk7.green("\u2713") : chalk7.red("\u2717");
5287
- console.log(` ${icon} ${entry.path} (${entry.action})`);
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/regenerate.ts
5293
- import chalk8 from "chalk";
5294
- import ora4 from "ora";
5295
- import select3 from "@inquirer/select";
5296
- async function regenerateCommand(options) {
5297
- const config = loadConfig();
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(chalk8.red("No LLM provider configured. Run ") + chalk8.hex("#83D1EB")("caliber config") + chalk8.red(" first."));
5300
- throw new Error("__exit__");
5301
- }
5302
- const manifest = readManifest();
5303
- if (!manifest) {
5304
- console.log(chalk8.yellow("No existing setup found. Run ") + chalk8.hex("#83D1EB")("caliber onboard") + chalk8.yellow(" first."));
5305
- throw new Error("__exit__");
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 targetAgent = readState()?.targetAgent ?? ["claude", "cursor"];
5308
- const spinner = ora4("Analyzing project...").start();
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 genSpinner = ora4("Regenerating setup...").start();
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
- void 0,
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) generatedSetup = result.setup;
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(`Regeneration failed: ${msg}`);
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 regenerate setup.");
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
- genSpinner.succeed("Setup regenerated");
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(chalk8.dim(`
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(chalk8.dim(" No changes needed \u2014 your configs are already up to date.\n"));
5543
+ console.log(chalk7.dim(" No changes needed \u2014 your configs are already up to date.\n"));
5357
5544
  cleanupStaging();
5358
- return;
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
- if (options.dryRun) {
5361
- console.log(chalk8.yellow("[Dry run] Would write:"));
5362
- for (const f of staged.stagedFiles) {
5363
- console.log(` ${f.isNew ? chalk8.green("+") : chalk8.yellow("~")} ${f.relativePath}`);
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
- cleanupStaging();
5366
- return;
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(chalk8.dim("Regeneration cancelled. No files were modified."));
5571
+ console.log(chalk7.dim("Setup declined. No files were modified."));
5378
5572
  return;
5379
5573
  }
5380
- const writeSpinner = ora4("Writing config files...").start();
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(` ${chalk8.green("\u2713")} ${file}`);
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(` ${chalk8.red("\u2717")} ${file}`);
5590
+ console.log(` ${chalk7.red("\u2717")} ${file}`);
5390
5591
  }
5391
5592
  }
5392
5593
  if (result.backupDir) {
5393
- console.log(chalk8.dim(`
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(chalk8.red(err instanceof Error ? err.message : "Unknown error"));
5599
+ console.error(chalk7.red(err instanceof Error ? err.message : "Unknown error"));
5399
5600
  throw new Error("__exit__");
5400
5601
  }
5401
- const sha = getCurrentHeadSha();
5402
- writeState({
5403
- lastRefreshSha: sha ?? "",
5404
- lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
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 { restored, removed } = undoSetup();
5413
- if (restored.length > 0 || removed.length > 0) {
5414
- console.log(chalk8.dim(` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`));
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
- console.log(chalk8.dim(" Run ") + chalk8.hex("#83D1EB")("caliber onboard --force") + chalk8.dim(" to override.\n"));
5419
- return;
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
- const skillsDir = path17.join(dir, ".claude", "skills");
5449
- if (fs22.existsSync(skillsDir)) {
5450
- for (const file of fs22.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
5451
- const filePath = path17.join(skillsDir, file);
5452
- items.push({
5453
- type: "skill",
5454
- platform: "claude",
5455
- name: file,
5456
- contentHash: hashFile(filePath),
5457
- path: filePath
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
- const mcpJsonPath = path17.join(dir, ".mcp.json");
5462
- if (fs22.existsSync(mcpJsonPath)) {
5463
- try {
5464
- const mcpJson = JSON.parse(fs22.readFileSync(mcpJsonPath, "utf-8"));
5465
- if (mcpJson.mcpServers) {
5466
- for (const name of Object.keys(mcpJson.mcpServers)) {
5467
- items.push({
5468
- type: "mcp",
5469
- platform: "claude",
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
- const agentsMdPath = path17.join(dir, "AGENTS.md");
5480
- if (fs22.existsSync(agentsMdPath)) {
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 codexSkillsDir = path17.join(dir, ".agents", "skills");
5490
- if (fs22.existsSync(codexSkillsDir)) {
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
- for (const name of fs22.readdirSync(codexSkillsDir)) {
5493
- const skillFile = path17.join(codexSkillsDir, name, "SKILL.md");
5494
- if (fs22.existsSync(skillFile)) {
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
- const cursorrulesPath = path17.join(dir, ".cursorrules");
5508
- if (fs22.existsSync(cursorrulesPath)) {
5509
- items.push({
5510
- type: "rule",
5511
- platform: "cursor",
5512
- name: ".cursorrules",
5513
- contentHash: hashFile(cursorrulesPath),
5514
- path: cursorrulesPath
5515
- });
5516
- }
5517
- const cursorRulesDir = path17.join(dir, ".cursor", "rules");
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
- for (const name of fs22.readdirSync(cursorSkillsDir)) {
5534
- const skillFile = path17.join(cursorSkillsDir, name, "SKILL.md");
5535
- if (fs22.existsSync(skillFile)) {
5536
- items.push({
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
- } catch {
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
- const cursorMcpPath = path17.join(dir, ".cursor", "mcp.json");
5549
- if (fs22.existsSync(cursorMcpPath)) {
5550
- try {
5551
- const mcpJson = JSON.parse(fs22.readFileSync(cursorMcpPath, "utf-8"));
5552
- if (mcpJson.mcpServers) {
5553
- for (const name of Object.keys(mcpJson.mcpServers)) {
5554
- items.push({
5555
- type: "mcp",
5556
- platform: "cursor",
5557
- name,
5558
- contentHash: hashJson(mcpJson.mcpServers[name]),
5559
- path: cursorMcpPath
5560
- });
5561
- }
5562
- }
5563
- } catch {
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 hashFile(filePath) {
5569
- const text = fs22.readFileSync(filePath, "utf-8");
5570
- return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
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 hashJson(obj) {
5573
- return crypto2.createHash("sha256").update(JSON.stringify(obj)).digest("hex");
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
- // src/commands/recommend.ts
5577
- function detectLocalPlatforms() {
5578
- const items = scanLocalState(process.cwd());
5579
- const platforms = /* @__PURE__ */ new Set();
5580
- for (const item of items) {
5581
- platforms.add(item.platform);
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 getSkillPath(platform, slug) {
5586
- if (platform === "cursor") {
5587
- return join8(".cursor", "skills", slug, "SKILL.md");
5588
- }
5589
- if (platform === "codex") {
5590
- return join8(".agents", "skills", slug, "SKILL.md");
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
- return join8(".claude", "skills", slug, "SKILL.md");
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 getInstalledSkills() {
5595
- const installed = /* @__PURE__ */ new Set();
5596
- const dirs = [
5597
- join8(process.cwd(), ".claude", "skills"),
5598
- join8(process.cwd(), ".cursor", "skills"),
5599
- join8(process.cwd(), ".agents", "skills")
5600
- ];
5601
- for (const dir of dirs) {
5602
- try {
5603
- const entries = readdirSync5(dir, { withFileTypes: true });
5604
- for (const entry of entries) {
5605
- if (entry.isDirectory()) {
5606
- installed.add(entry.name.toLowerCase());
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
- return installed;
5613
- }
5614
- async function searchSkillsSh(technologies) {
5615
- const bestBySlug = /* @__PURE__ */ new Map();
5616
- for (const tech of technologies) {
5617
- try {
5618
- const resp = await fetch(`https://skills.sh/api/search?q=${encodeURIComponent(tech)}&limit=10`, {
5619
- signal: AbortSignal.timeout(1e4)
5620
- });
5621
- if (!resp.ok) continue;
5622
- const data = await resp.json();
5623
- if (!data.skills?.length) continue;
5624
- for (const skill of data.skills) {
5625
- const existing = bestBySlug.get(skill.skillId);
5626
- if (existing && existing.installs >= (skill.installs ?? 0)) continue;
5627
- bestBySlug.set(skill.skillId, {
5628
- name: skill.name,
5629
- slug: skill.skillId,
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
- return Array.from(bestBySlug.values());
5643
- }
5644
- async function searchTessl(technologies) {
5645
- const results = [];
5646
- const seen = /* @__PURE__ */ new Set();
5647
- for (const tech of technologies) {
5648
- try {
5649
- const resp = await fetch(`https://tessl.io/registry?q=${encodeURIComponent(tech)}`, {
5650
- signal: AbortSignal.timeout(1e4)
5651
- });
5652
- if (!resp.ok) continue;
5653
- const html = await resp.text();
5654
- const linkMatches = html.matchAll(/\/registry\/skills\/github\/([^/]+)\/([^/]+)\/([^/"]+)/g);
5655
- for (const match of linkMatches) {
5656
- const [, org, repo, skillName] = match;
5657
- const slug = `${org}-${repo}-${skillName}`.toLowerCase();
5658
- if (seen.has(slug)) continue;
5659
- seen.add(slug);
5660
- results.push({
5661
- name: skillName,
5662
- slug,
5663
- source_url: `https://github.com/${org}/${repo}`,
5664
- score: 0,
5665
- reason: `Skill from ${org}/${repo}`,
5666
- detected_technology: tech,
5667
- item_type: "skill"
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
- return results;
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
- var AWESOME_CLAUDE_CODE_URL = "https://raw.githubusercontent.com/hesreallyhim/awesome-claude-code/main/README.md";
5677
- async function searchAwesomeClaudeCode(technologies) {
5952
+ function ensurePermissions() {
5953
+ const settingsPath = ".claude/settings.json";
5954
+ let settings = {};
5678
5955
  try {
5679
- const resp = await fetch(AWESOME_CLAUDE_CODE_URL, {
5680
- signal: AbortSignal.timeout(1e4)
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
- async function searchAllProviders(technologies, platform) {
5711
- const searches = [
5712
- searchSkillsSh(technologies),
5713
- searchTessl(technologies)
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
- if (platform === "claude" || !platform) {
5716
- searches.push(searchAwesomeClaudeCode(technologies));
5717
- }
5718
- const results = await Promise.all(searches);
5719
- const seen = /* @__PURE__ */ new Set();
5720
- const combined = [];
5721
- for (const batch of results) {
5722
- for (const result of batch) {
5723
- const key = result.name.toLowerCase().replace(/[-_]/g, "");
5724
- if (seen.has(key)) continue;
5725
- seen.add(key);
5726
- combined.push(result);
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
- Scoring guidelines:
5743
- - 90-100: Directly matches a core technology or workflow in the project
5744
- - 70-89: Relevant to the project's stack, patterns, or development workflow
5745
- - 50-69: Tangentially related or generic but useful
5746
- - 0-49: Not relevant to this project
5747
-
5748
- Be selective. Prefer specific, high-quality matches over generic ones.
5749
- A skill for "React testing" is only relevant if the project uses React.
5750
- A generic "TypeScript best practices" skill is less valuable than one targeting the project's actual framework.
5751
- Return ONLY the JSON array.`,
5752
- prompt: `PROJECT CONTEXT:
5753
- ${projectContext}
5754
-
5755
- DETECTED TECHNOLOGIES:
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
- if (fingerprint.existingConfigs.claudeMd) {
5782
- parts.push(`
5783
- Existing CLAUDE.md (first 500 chars):
5784
- ${fingerprint.existingConfigs.claudeMd.slice(0, 500)}`);
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
- const deps = extractTopDeps();
5787
- if (deps.length > 0) {
5788
- parts.push(`
5789
- Dependencies: ${deps.slice(0, 30).join(", ")}`);
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
- const installed = getInstalledSkills();
5792
- if (installed.size > 0) {
5793
- parts.push(`
5794
- Already installed skills: ${Array.from(installed).join(", ")}`);
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
- return parts.join("\n");
6038
+ console.log("");
5797
6039
  }
5798
- function extractTopDeps() {
5799
- const pkgPath = join8(process.cwd(), "package.json");
5800
- if (!existsSync9(pkgPath)) return [];
5801
- try {
5802
- const pkg3 = JSON.parse(readFileSync7(pkgPath, "utf-8"));
5803
- const deps = Object.keys(pkg3.dependencies ?? {});
5804
- const trivial = /* @__PURE__ */ new Set([
5805
- "typescript",
5806
- "tslib",
5807
- "ts-node",
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
- async function recommendCommand(options) {
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
- const platforms = detectLocalPlatforms();
5877
- const installedSkills = getInstalledSkills();
5878
- const technologies = [...new Set([
5879
- ...fingerprint.languages,
5880
- ...fingerprint.frameworks,
5881
- ...extractTopDeps()
5882
- ].filter(Boolean))];
5883
- if (technologies.length === 0) {
5884
- console.log(chalk9.yellow("Could not detect any languages or dependencies. Try running from a project root."));
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
- const primaryPlatform = platforms.includes("claude") ? "claude" : platforms[0];
5888
- const searchSpinner = ora5("Searching skill registries...").start();
5889
- const allCandidates = await searchAllProviders(technologies, primaryPlatform);
5890
- if (!allCandidates.length) {
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
- const newCandidates = allCandidates.filter((c) => !installedSkills.has(c.slug.toLowerCase()));
5895
- const filteredCount = allCandidates.length - newCandidates.length;
5896
- if (!newCandidates.length) {
5897
- searchSpinner.succeed(`Found ${allCandidates.length} skills \u2014 all already installed.`);
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
- searchSpinner.succeed(
5901
- `Found ${allCandidates.length} skills` + (filteredCount > 0 ? chalk9.dim(` (${filteredCount} already installed)`) : "")
5902
- );
5903
- let results;
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
- } else {
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 unavailableCount = results.length - available.length;
5934
- fetchSpinner.succeed(
5935
- `${available.length} installable skill${available.length > 1 ? "s" : ""}` + (unavailableCount > 0 ? chalk9.dim(` (${unavailableCount} unavailable)`) : "")
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
- async function interactiveSelect2(recs) {
5943
- if (!process.stdin.isTTY) {
5944
- printRecommendations(recs);
5945
- return null;
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 selected = /* @__PURE__ */ new Set();
5948
- let cursor = 0;
5949
- const { stdin, stdout } = process;
5950
- let lineCount = 0;
5951
- const hasScores = recs.some((r) => r.score > 0);
5952
- function render() {
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
- lines.push(chalk9.dim(" " + "\u2500".repeat(70)));
5962
- for (let i = 0; i < recs.length; i++) {
5963
- const rec = recs[i];
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
- lines.push("");
5974
- lines.push(chalk9.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q cancel"));
5975
- return lines.join("\n");
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
- stdout.write("\x1B[0J");
5982
- const output = render();
5983
- stdout.write(output + "\n");
5984
- lineCount = output.split("\n").length;
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
- return new Promise((resolve2) => {
5987
- console.log("");
5988
- draw(true);
5989
- stdin.setRawMode(true);
5990
- stdin.resume();
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
- async function fetchSkillContent(rec) {
6042
- if (!rec.source_url) return null;
6043
- const repoPath = rec.source_url.replace("https://github.com/", "");
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 resp = await fetch(url, { signal: AbortSignal.timeout(1e4) });
6053
- if (resp.ok) {
6054
- const text = await resp.text();
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
- try {
6061
- const resp = await fetch(
6062
- `https://api.github.com/repos/${repoPath}/git/trees/HEAD?recursive=1`,
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 chalk10 from "chalk";
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 = chalk10.gray(" " + "\u2500".repeat(53));
6199
+ const separator = chalk11.gray(" " + "\u2500".repeat(53));
6138
6200
  console.log(separator);
6139
6201
  if (result.score < 40) {
6140
- console.log(chalk10.gray(" Run ") + chalk10.hex("#83D1EB")("caliber onboard") + chalk10.gray(" to generate a complete, optimized setup."));
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(chalk10.gray(" Run ") + chalk10.hex("#83D1EB")("caliber onboard") + chalk10.gray(" to improve your setup."));
6204
+ console.log(chalk11.gray(" Run ") + chalk11.hex("#83D1EB")("caliber onboard") + chalk11.gray(" to improve your setup."));
6143
6205
  } else {
6144
- console.log(chalk10.green(" Looking good!") + chalk10.gray(" Run ") + chalk10.hex("#83D1EB")("caliber regenerate") + chalk10.gray(" to rebuild from scratch."));
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 fs24 from "fs";
6212
+ import fs25 from "fs";
6151
6213
  import path19 from "path";
6152
- import chalk11 from "chalk";
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 fs23 from "fs";
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
- fs23.writeFileSync("CLAUDE.md", docs.claudeMd);
6295
+ fs24.writeFileSync("CLAUDE.md", docs.claudeMd);
6234
6296
  written.push("CLAUDE.md");
6235
6297
  }
6236
6298
  if (docs.readmeMd) {
6237
- fs23.writeFileSync("README.md", docs.readmeMd);
6299
+ fs24.writeFileSync("README.md", docs.readmeMd);
6238
6300
  written.push("README.md");
6239
6301
  }
6240
6302
  if (docs.cursorrules) {
6241
- fs23.writeFileSync(".cursorrules", docs.cursorrules);
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 (!fs23.existsSync(rulesDir)) fs23.mkdirSync(rulesDir, { recursive: true });
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
- fs23.writeFileSync(filePath, rule.content);
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 (!fs23.existsSync(skillsDir)) fs23.mkdirSync(skillsDir, { recursive: true });
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
- fs23.writeFileSync(filePath, skill.content);
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 = fs24.readdirSync(parentDir, { withFileTypes: true });
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 (fs24.existsSync(path19.join(childPath, ".git"))) {
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 ? `${chalk11.bold(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, chalk11.dim(`${prefix}No changes since last refresh.`));
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(` ${chalk11.yellow("~")} ${doc}`);
6452
+ console.log(` ${chalk12.yellow("~")} ${doc}`);
6391
6453
  }
6392
6454
  if (response.changesSummary) {
6393
- console.log(chalk11.dim(`
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, ` ${chalk11.green("\u2713")} ${file}`);
6463
+ log(quiet, ` ${chalk12.green("\u2713")} ${file}`);
6402
6464
  }
6403
6465
  if (response.changesSummary) {
6404
- log(quiet, chalk11.dim(`
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(chalk11.red("No LLM provider configured. Run ") + chalk11.hex("#83D1EB")("caliber config") + chalk11.red(" (e.g. choose Cursor) or set an API key."));
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(chalk11.red("Not inside a git repository and no git repos found in child directories."));
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, chalk11.dim(`Found ${repos.length} git repo${repos.length === 1 ? "" : "s"}
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, chalk11.yellow(`${repoName}: refresh failed \u2014 ${err instanceof Error ? err.message : "unknown error"}`));
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(chalk11.red(`Refresh failed: ${msg}`));
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 chalk12 from "chalk";
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(chalk12.bold("\n Hooks\n"));
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 ? chalk12.green("\u2713") : chalk12.dim("\u2717");
6478
- const state = installed ? chalk12.green("enabled") : chalk12.dim("disabled");
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(chalk12.dim(` ${hook.description}`));
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(chalk12.dim(` ${hook.label} already enabled.`));
6551
+ console.log(chalk13.dim(` ${hook.label} already enabled.`));
6490
6552
  } else {
6491
- console.log(chalk12.green(" \u2713") + ` ${hook.label} enabled`);
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(chalk12.dim(` ${hook.label} already disabled.`));
6562
+ console.log(chalk13.dim(` ${hook.label} already disabled.`));
6501
6563
  } else {
6502
- console.log(chalk12.green(" \u2713") + ` ${hook.label} removed`);
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(chalk12.bold(" Hooks"));
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 ? chalk12.green("[on] ") : chalk12.dim("[off]");
6523
- const ptr = i === cursor ? chalk12.cyan(">") : " ";
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(chalk12.dim(` ${hook.description}`));
6587
+ lines.push(chalk13.dim(` ${hook.description}`));
6526
6588
  }
6527
6589
  lines.push("");
6528
- lines.push(chalk12.dim(" \u2191\u2193 navigate \u23B5 toggle a all on n all off \u23CE apply q cancel"));
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(chalk12.green(" \u2713") + ` ${hook.label} enabled`);
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(chalk12.green(" \u2713") + ` ${hook.label} disabled`);
6625
+ console.log(chalk13.green(" \u2713") + ` ${hook.label} disabled`);
6564
6626
  changed++;
6565
6627
  }
6566
6628
  }
6567
6629
  if (changed === 0) {
6568
- console.log(chalk12.dim(" No changes."));
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(chalk12.dim("\n Cancelled.\n"));
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 chalk13 from "chalk";
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(chalk13.bold("\nCurrent Configuration\n"));
6621
- console.log(` Provider: ${chalk13.cyan(existing.provider)}`);
6622
- console.log(` Model: ${chalk13.cyan(displayModel)}`);
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: ${chalk13.cyan(fastModel)}`);
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: ${chalk13.dim(masked)}`);
6690
+ console.log(` API Key: ${chalk14.dim(masked)}`);
6629
6691
  }
6630
6692
  if (existing.provider === "cursor") {
6631
- console.log(` Seat: ${chalk13.dim("Cursor (agent acp)")}`);
6693
+ console.log(` Seat: ${chalk14.dim("Cursor (agent acp)")}`);
6632
6694
  }
6633
6695
  if (existing.provider === "claude-cli") {
6634
- console.log(` Seat: ${chalk13.dim("Claude Code (claude -p)")}`);
6696
+ console.log(` Seat: ${chalk14.dim("Claude Code (claude -p)")}`);
6635
6697
  }
6636
6698
  if (existing.baseUrl) {
6637
- console.log(` Base URL: ${chalk13.dim(existing.baseUrl)}`);
6699
+ console.log(` Base URL: ${chalk14.dim(existing.baseUrl)}`);
6638
6700
  }
6639
6701
  if (existing.vertexProjectId) {
6640
- console.log(` Vertex Project: ${chalk13.dim(existing.vertexProjectId)}`);
6641
- console.log(` Vertex Region: ${chalk13.dim(existing.vertexRegion || "us-east5")}`);
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: ${chalk13.dim(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.VERTEX_PROJECT_ID || process.env.CALIBER_USE_CURSOR_SEAT || process.env.CALIBER_USE_CLAUDE_CLI ? "environment variables" : getConfigFilePath())}`);
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(chalk13.green("\n\u2713 Configuration saved"));
6648
- console.log(chalk13.dim(` ${getConfigFilePath()}
6709
+ console.log(chalk14.green("\n\u2713 Configuration saved"));
6710
+ console.log(chalk14.dim(` ${getConfigFilePath()}
6649
6711
  `));
6650
- console.log(chalk13.dim(" You can also set environment variables instead:"));
6651
- console.log(chalk13.dim(" ANTHROPIC_API_KEY, OPENAI_API_KEY, VERTEX_PROJECT_ID, CALIBER_USE_CURSOR_SEAT=1, or CALIBER_USE_CLAUDE_CLI=1\n"));
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 chalk14 from "chalk";
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 fs25 from "fs";
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 (!fs25.existsSync(LEARNING_DIR)) {
6696
- fs25.mkdirSync(LEARNING_DIR, { recursive: true });
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
- fs25.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
6776
+ fs26.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
6715
6777
  const count = getEventCount();
6716
6778
  if (count > LEARNING_MAX_EVENTS) {
6717
- const lines = fs25.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
6779
+ const lines = fs26.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
6718
6780
  const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
6719
- fs25.writeFileSync(filePath, kept.join("\n") + "\n");
6781
+ fs26.writeFileSync(filePath, kept.join("\n") + "\n");
6720
6782
  }
6721
6783
  }
6722
6784
  function readAllEvents() {
6723
6785
  const filePath = sessionFilePath();
6724
- if (!fs25.existsSync(filePath)) return [];
6725
- const lines = fs25.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
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 (!fs25.existsSync(filePath)) return 0;
6731
- const content = fs25.readFileSync(filePath, "utf-8");
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 (fs25.existsSync(filePath)) fs25.unlinkSync(filePath);
6798
+ if (fs26.existsSync(filePath)) fs26.unlinkSync(filePath);
6737
6799
  }
6738
6800
  function readState2() {
6739
6801
  const filePath = stateFilePath();
6740
- if (!fs25.existsSync(filePath)) return { ...DEFAULT_STATE };
6802
+ if (!fs26.existsSync(filePath)) return { ...DEFAULT_STATE };
6741
6803
  try {
6742
- return JSON.parse(fs25.readFileSync(filePath, "utf-8"));
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
- fs25.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
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 fs26 from "fs";
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 (fs26.existsSync(claudeMdPath)) {
6778
- existing = fs26.readFileSync(claudeMdPath, "utf-8");
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
- fs26.writeFileSync(claudeMdPath, updated);
6854
+ fs27.writeFileSync(claudeMdPath, updated);
6793
6855
  }
6794
6856
  function writeLearnedSkill(skill) {
6795
6857
  const skillDir = path21.join(".claude", "skills", skill.name);
6796
- if (!fs26.existsSync(skillDir)) fs26.mkdirSync(skillDir, { recursive: true });
6858
+ if (!fs27.existsSync(skillDir)) fs27.mkdirSync(skillDir, { recursive: true });
6797
6859
  const skillPath = path21.join(skillDir, "SKILL.md");
6798
- if (!skill.isNew && fs26.existsSync(skillPath)) {
6799
- const existing = fs26.readFileSync(skillPath, "utf-8");
6800
- fs26.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
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
- fs26.writeFileSync(skillPath, frontmatter + skill.content);
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 (!fs26.existsSync(claudeMdPath)) return null;
6816
- const content = fs26.readFileSync(claudeMdPath, "utf-8");
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(chalk14.dim("Learning hooks already installed."));
7018
+ console.log(chalk15.dim("Learning hooks already installed."));
6957
7019
  return;
6958
7020
  }
6959
- console.log(chalk14.green("\u2713") + " Learning hooks installed in .claude/settings.json");
6960
- console.log(chalk14.dim(" PostToolUse, PostToolUseFailure, and SessionEnd hooks active."));
6961
- console.log(chalk14.dim(" Session learnings will be written to CLAUDE.md and skills."));
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(chalk14.dim("Learning hooks not found."));
7028
+ console.log(chalk15.dim("Learning hooks not found."));
6967
7029
  return;
6968
7030
  }
6969
- console.log(chalk14.green("\u2713") + " Learning hooks removed from .claude/settings.json");
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(chalk14.bold("Session Learning Status"));
7037
+ console.log(chalk15.bold("Session Learning Status"));
6976
7038
  console.log();
6977
7039
  if (installed) {
6978
- console.log(chalk14.green("\u2713") + " Learning hooks are " + chalk14.green("installed"));
7040
+ console.log(chalk15.green("\u2713") + " Learning hooks are " + chalk15.green("installed"));
6979
7041
  } else {
6980
- console.log(chalk14.dim("\u2717") + " Learning hooks are " + chalk14.yellow("not installed"));
6981
- console.log(chalk14.dim(" Run `caliber learn install` to enable session learning."));
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: ${chalk14.cyan(String(eventCount))}`);
6985
- console.log(`Total this session: ${chalk14.cyan(String(state.eventCount))}`);
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: ${chalk14.cyan(state.lastAnalysisTimestamp)}`);
7049
+ console.log(`Last analysis: ${chalk15.cyan(state.lastAnalysisTimestamp)}`);
6988
7050
  } else {
6989
- console.log(`Last analysis: ${chalk14.dim("none")}`);
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: ${chalk14.cyan(String(lineCount))}`);
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
- fs27.readFileSync(path22.resolve(__dirname, "..", "package.json"), "utf-8")
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("recommend").description("Discover and install skill recommendations").option("--generate", "Force fresh recommendation search").action(recommendCommand);
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 fs28 from "fs";
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 chalk15 from "chalk";
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
- fs28.readFileSync(path23.resolve(__dirname_vc, "..", "package.json"), "utf-8")
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(fs28.readFileSync(pkgPath, "utf-8")).version;
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
- chalk15.yellow(
7135
+ chalk16.yellow(
7074
7136
  `
7075
7137
  Update available: ${current} -> ${latest}
7076
- Run ${chalk15.bold("npm install -g @rely-ai/caliber")} to upgrade.
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
- chalk15.yellow(`
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(chalk15.yellow(`Run ${chalk15.bold(`npm install -g @rely-ai/caliber@${latest}`)} manually.
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(chalk15.green(`Updated to ${latest}`));
7167
+ spinner.succeed(chalk16.green(`Updated to ${latest}`));
7106
7168
  const args = process.argv.slice(2);
7107
- console.log(chalk15.dim(`
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(chalk15.dim(` ${errMsg}`));
7182
+ if (errMsg && !errMsg.includes("SIGTERM")) console.log(chalk16.dim(` ${errMsg}`));
7121
7183
  }
7122
7184
  console.log(
7123
- chalk15.yellow(
7124
- `Run ${chalk15.bold(`npm install -g @rely-ai/caliber@${latest}`)} manually to upgrade.
7185
+ chalk16.yellow(
7186
+ `Run ${chalk16.bold(`npm install -g @rely-ai/caliber@${latest}`)} manually to upgrade.
7125
7187
  `
7126
7188
  )
7127
7189
  );