@rely-ai/caliber 1.6.0 → 1.7.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 (2) hide show
  1. package/dist/bin.js +519 -833
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -20,6 +20,120 @@ var __copyProps = (to, from, except, desc) => {
20
20
  };
21
21
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
22
 
23
+ // src/llm/config.ts
24
+ var config_exports = {};
25
+ __export(config_exports, {
26
+ DEFAULT_FAST_MODELS: () => DEFAULT_FAST_MODELS,
27
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
28
+ getConfigFilePath: () => getConfigFilePath,
29
+ getFastModel: () => getFastModel,
30
+ loadConfig: () => loadConfig,
31
+ readConfigFile: () => readConfigFile,
32
+ resolveFromEnv: () => resolveFromEnv,
33
+ writeConfigFile: () => writeConfigFile
34
+ });
35
+ import fs4 from "fs";
36
+ import path4 from "path";
37
+ import os from "os";
38
+ function loadConfig() {
39
+ const envConfig = resolveFromEnv();
40
+ if (envConfig) return envConfig;
41
+ return readConfigFile();
42
+ }
43
+ function resolveFromEnv() {
44
+ if (process.env.ANTHROPIC_API_KEY) {
45
+ return {
46
+ provider: "anthropic",
47
+ apiKey: process.env.ANTHROPIC_API_KEY,
48
+ model: process.env.CALIBER_MODEL || DEFAULT_MODELS.anthropic
49
+ };
50
+ }
51
+ if (process.env.VERTEX_PROJECT_ID || process.env.GCP_PROJECT_ID) {
52
+ return {
53
+ provider: "vertex",
54
+ model: process.env.CALIBER_MODEL || DEFAULT_MODELS.vertex,
55
+ vertexProjectId: process.env.VERTEX_PROJECT_ID || process.env.GCP_PROJECT_ID,
56
+ vertexRegion: process.env.VERTEX_REGION || process.env.GCP_REGION || "us-east5",
57
+ vertexCredentials: process.env.VERTEX_SA_CREDENTIALS || process.env.GOOGLE_APPLICATION_CREDENTIALS
58
+ };
59
+ }
60
+ if (process.env.OPENAI_API_KEY) {
61
+ return {
62
+ provider: "openai",
63
+ apiKey: process.env.OPENAI_API_KEY,
64
+ model: process.env.CALIBER_MODEL || DEFAULT_MODELS.openai,
65
+ baseUrl: process.env.OPENAI_BASE_URL
66
+ };
67
+ }
68
+ if (process.env.CALIBER_USE_CURSOR_SEAT === "1" || process.env.CALIBER_USE_CURSOR_SEAT === "true") {
69
+ return {
70
+ provider: "cursor",
71
+ model: DEFAULT_MODELS.cursor
72
+ };
73
+ }
74
+ if (process.env.CALIBER_USE_CLAUDE_CLI === "1" || process.env.CALIBER_USE_CLAUDE_CLI === "true") {
75
+ return {
76
+ provider: "claude-cli",
77
+ model: DEFAULT_MODELS["claude-cli"]
78
+ };
79
+ }
80
+ return null;
81
+ }
82
+ function readConfigFile() {
83
+ try {
84
+ if (!fs4.existsSync(CONFIG_FILE)) return null;
85
+ const raw = fs4.readFileSync(CONFIG_FILE, "utf-8");
86
+ const parsed = JSON.parse(raw);
87
+ if (!parsed.provider || !["anthropic", "vertex", "openai", "cursor", "claude-cli"].includes(parsed.provider)) {
88
+ return null;
89
+ }
90
+ return parsed;
91
+ } catch {
92
+ return null;
93
+ }
94
+ }
95
+ function writeConfigFile(config) {
96
+ if (!fs4.existsSync(CONFIG_DIR)) {
97
+ fs4.mkdirSync(CONFIG_DIR, { recursive: true });
98
+ }
99
+ const sanitized = { ...config };
100
+ if (sanitized.apiKey) {
101
+ sanitized.apiKey = sanitized.apiKey.trim();
102
+ }
103
+ fs4.writeFileSync(CONFIG_FILE, JSON.stringify(sanitized, null, 2) + "\n", { mode: 384 });
104
+ }
105
+ function getConfigFilePath() {
106
+ return CONFIG_FILE;
107
+ }
108
+ function getFastModel() {
109
+ if (process.env.CALIBER_FAST_MODEL) return process.env.CALIBER_FAST_MODEL;
110
+ if (process.env.ANTHROPIC_SMALL_FAST_MODEL) return process.env.ANTHROPIC_SMALL_FAST_MODEL;
111
+ const config = loadConfig();
112
+ if (config?.fastModel) return config.fastModel;
113
+ if (config?.provider) return DEFAULT_FAST_MODELS[config.provider];
114
+ return void 0;
115
+ }
116
+ var CONFIG_DIR, CONFIG_FILE, DEFAULT_MODELS, DEFAULT_FAST_MODELS;
117
+ var init_config = __esm({
118
+ "src/llm/config.ts"() {
119
+ "use strict";
120
+ CONFIG_DIR = path4.join(os.homedir(), ".caliber");
121
+ CONFIG_FILE = path4.join(CONFIG_DIR, "config.json");
122
+ DEFAULT_MODELS = {
123
+ anthropic: "claude-sonnet-4-6",
124
+ vertex: "claude-sonnet-4-6",
125
+ openai: "gpt-4.1",
126
+ cursor: "default",
127
+ "claude-cli": "default"
128
+ };
129
+ DEFAULT_FAST_MODELS = {
130
+ anthropic: "claude-haiku-4-5-20251001",
131
+ vertex: "claude-haiku-4-5-20251001",
132
+ openai: "gpt-4.1-mini"
133
+ };
134
+ }
135
+ });
136
+
23
137
  // src/constants.ts
24
138
  var constants_exports = {};
25
139
  __export(constants_exports, {
@@ -51,17 +165,17 @@ var init_constants = __esm({
51
165
 
52
166
  // src/cli.ts
53
167
  import { Command } from "commander";
54
- import fs28 from "fs";
55
- import path22 from "path";
168
+ import fs27 from "fs";
169
+ import path21 from "path";
56
170
  import { fileURLToPath } from "url";
57
171
 
58
172
  // src/commands/onboard.ts
59
173
  import chalk7 from "chalk";
60
- import ora3 from "ora";
61
- import readline4 from "readline";
62
- import select4 from "@inquirer/select";
174
+ import ora2 from "ora";
175
+ import readline3 from "readline";
176
+ import select5 from "@inquirer/select";
63
177
  import checkbox from "@inquirer/checkbox";
64
- import fs22 from "fs";
178
+ import fs21 from "fs";
65
179
 
66
180
  // src/fingerprint/index.ts
67
181
  import fs6 from "fs";
@@ -507,102 +621,8 @@ function estimateSummarySize(summary) {
507
621
  return summary.path.length + summary.imports.reduce((s, i) => s + i.length, 0) + summary.exports.reduce((s, e) => s + e.length, 0) + summary.functions.reduce((s, f) => s + f.length, 0) + summary.classes.reduce((s, c) => s + c.length, 0) + summary.types.reduce((s, t) => s + t.length, 0) + summary.routes.reduce((s, r) => s + r.length, 0);
508
622
  }
509
623
 
510
- // src/llm/config.ts
511
- import fs4 from "fs";
512
- import path4 from "path";
513
- import os from "os";
514
- var CONFIG_DIR = path4.join(os.homedir(), ".caliber");
515
- var CONFIG_FILE = path4.join(CONFIG_DIR, "config.json");
516
- var DEFAULT_MODELS = {
517
- anthropic: "claude-sonnet-4-6",
518
- vertex: "claude-sonnet-4-6",
519
- openai: "gpt-4.1",
520
- cursor: "default",
521
- "claude-cli": "default"
522
- };
523
- var DEFAULT_FAST_MODELS = {
524
- anthropic: "claude-haiku-4-5-20251001",
525
- vertex: "claude-haiku-4-5-20251001",
526
- openai: "gpt-4.1-mini"
527
- };
528
- function loadConfig() {
529
- const envConfig = resolveFromEnv();
530
- if (envConfig) return envConfig;
531
- return readConfigFile();
532
- }
533
- function resolveFromEnv() {
534
- if (process.env.ANTHROPIC_API_KEY) {
535
- return {
536
- provider: "anthropic",
537
- apiKey: process.env.ANTHROPIC_API_KEY,
538
- model: process.env.CALIBER_MODEL || DEFAULT_MODELS.anthropic
539
- };
540
- }
541
- if (process.env.VERTEX_PROJECT_ID || process.env.GCP_PROJECT_ID) {
542
- return {
543
- provider: "vertex",
544
- model: process.env.CALIBER_MODEL || DEFAULT_MODELS.vertex,
545
- vertexProjectId: process.env.VERTEX_PROJECT_ID || process.env.GCP_PROJECT_ID,
546
- vertexRegion: process.env.VERTEX_REGION || process.env.GCP_REGION || "us-east5",
547
- vertexCredentials: process.env.VERTEX_SA_CREDENTIALS || process.env.GOOGLE_APPLICATION_CREDENTIALS
548
- };
549
- }
550
- if (process.env.OPENAI_API_KEY) {
551
- return {
552
- provider: "openai",
553
- apiKey: process.env.OPENAI_API_KEY,
554
- model: process.env.CALIBER_MODEL || DEFAULT_MODELS.openai,
555
- baseUrl: process.env.OPENAI_BASE_URL
556
- };
557
- }
558
- if (process.env.CALIBER_USE_CURSOR_SEAT === "1" || process.env.CALIBER_USE_CURSOR_SEAT === "true") {
559
- return {
560
- provider: "cursor",
561
- model: DEFAULT_MODELS.cursor
562
- };
563
- }
564
- if (process.env.CALIBER_USE_CLAUDE_CLI === "1" || process.env.CALIBER_USE_CLAUDE_CLI === "true") {
565
- return {
566
- provider: "claude-cli",
567
- model: DEFAULT_MODELS["claude-cli"]
568
- };
569
- }
570
- return null;
571
- }
572
- function readConfigFile() {
573
- try {
574
- if (!fs4.existsSync(CONFIG_FILE)) return null;
575
- const raw = fs4.readFileSync(CONFIG_FILE, "utf-8");
576
- const parsed = JSON.parse(raw);
577
- if (!parsed.provider || !["anthropic", "vertex", "openai", "cursor", "claude-cli"].includes(parsed.provider)) {
578
- return null;
579
- }
580
- return parsed;
581
- } catch {
582
- return null;
583
- }
584
- }
585
- function writeConfigFile(config) {
586
- if (!fs4.existsSync(CONFIG_DIR)) {
587
- fs4.mkdirSync(CONFIG_DIR, { recursive: true });
588
- }
589
- const sanitized = { ...config };
590
- if (sanitized.apiKey) {
591
- sanitized.apiKey = sanitized.apiKey.trim();
592
- }
593
- fs4.writeFileSync(CONFIG_FILE, JSON.stringify(sanitized, null, 2) + "\n", { mode: 384 });
594
- }
595
- function getConfigFilePath() {
596
- return CONFIG_FILE;
597
- }
598
- function getFastModel() {
599
- if (process.env.CALIBER_FAST_MODEL) return process.env.CALIBER_FAST_MODEL;
600
- if (process.env.ANTHROPIC_SMALL_FAST_MODEL) return process.env.ANTHROPIC_SMALL_FAST_MODEL;
601
- const config = loadConfig();
602
- if (config?.fastModel) return config.fastModel;
603
- if (config?.provider) return DEFAULT_FAST_MODELS[config.provider];
604
- return void 0;
605
- }
624
+ // src/llm/index.ts
625
+ init_config();
606
626
 
607
627
  // src/llm/anthropic.ts
608
628
  import Anthropic from "@anthropic-ai/sdk";
@@ -623,6 +643,14 @@ var AnthropicProvider = class {
623
643
  const block = response.content[0];
624
644
  return block.type === "text" ? block.text : "";
625
645
  }
646
+ async listModels() {
647
+ const models = [];
648
+ const page = await this.client.models.list({ limit: 100 });
649
+ for (const model of page.data) {
650
+ models.push(model.id);
651
+ }
652
+ return models;
653
+ }
626
654
  async stream(options, callbacks) {
627
655
  const messages = options.messages ? [
628
656
  ...options.messages.map((msg) => ({
@@ -743,6 +771,13 @@ var OpenAICompatProvider = class {
743
771
  });
744
772
  return response.choices[0]?.message?.content || "";
745
773
  }
774
+ async listModels() {
775
+ const models = [];
776
+ for await (const model of this.client.models.list()) {
777
+ models.push(model.id);
778
+ }
779
+ return models;
780
+ }
746
781
  async stream(options, callbacks) {
747
782
  const messages = [
748
783
  { role: "system", content: options.system }
@@ -1108,7 +1143,108 @@ function estimateTokens(text) {
1108
1143
  return Math.ceil(text.length / 4);
1109
1144
  }
1110
1145
 
1146
+ // src/llm/model-recovery.ts
1147
+ init_config();
1148
+ import chalk from "chalk";
1149
+ import select from "@inquirer/select";
1150
+ var KNOWN_MODELS = {
1151
+ anthropic: [
1152
+ "claude-sonnet-4-6",
1153
+ "claude-sonnet-4-5-20250514",
1154
+ "claude-haiku-4-5-20251001",
1155
+ "claude-opus-4-6",
1156
+ "claude-opus-4-1-20250620"
1157
+ ],
1158
+ vertex: [
1159
+ "claude-sonnet-4-6@20250514",
1160
+ "claude-sonnet-4-5-20250514",
1161
+ "claude-haiku-4-5-20251001",
1162
+ "claude-opus-4-6@20250605",
1163
+ "claude-opus-4-1-20250620"
1164
+ ],
1165
+ openai: [
1166
+ "gpt-4.1",
1167
+ "gpt-4.1-mini",
1168
+ "gpt-4o",
1169
+ "gpt-4o-mini",
1170
+ "o3-mini"
1171
+ ],
1172
+ cursor: [],
1173
+ "claude-cli": []
1174
+ };
1175
+ function isModelNotAvailableError(error) {
1176
+ const msg = error.message.toLowerCase();
1177
+ const status = error.status;
1178
+ if (status === 404 && msg.includes("model")) return true;
1179
+ if (msg.includes("model") && (msg.includes("not found") || msg.includes("not_found"))) return true;
1180
+ if (msg.includes("model") && msg.includes("not available")) return true;
1181
+ if (msg.includes("model") && msg.includes("does not exist")) return true;
1182
+ if (msg.includes("publisher model")) return true;
1183
+ return false;
1184
+ }
1185
+ function filterRelevantModels(models, provider) {
1186
+ switch (provider) {
1187
+ case "anthropic":
1188
+ case "vertex":
1189
+ return models.filter((m) => m.startsWith("claude-"));
1190
+ case "openai":
1191
+ return models.filter(
1192
+ (m) => m.startsWith("gpt-4") || m.startsWith("gpt-3.5") || m.startsWith("o1") || m.startsWith("o3")
1193
+ );
1194
+ default:
1195
+ return models;
1196
+ }
1197
+ }
1198
+ async function handleModelNotAvailable(failedModel, provider, config) {
1199
+ if (!process.stdin.isTTY) {
1200
+ console.error(
1201
+ chalk.red(`Model "${failedModel}" is not available. Run \`caliber config\` to select a different model.`)
1202
+ );
1203
+ return null;
1204
+ }
1205
+ console.log(chalk.yellow(`
1206
+ \u26A0 Model "${failedModel}" is not available on your ${config.provider} deployment.`));
1207
+ let models = [];
1208
+ if (provider.listModels) {
1209
+ try {
1210
+ const allModels = await provider.listModels();
1211
+ models = filterRelevantModels(allModels, config.provider);
1212
+ } catch {
1213
+ }
1214
+ }
1215
+ if (models.length === 0) {
1216
+ models = KNOWN_MODELS[config.provider] ?? [];
1217
+ }
1218
+ models = models.filter((m) => m !== failedModel);
1219
+ if (models.length === 0) {
1220
+ console.log(chalk.red(" No alternative models found. Run `caliber config` to configure manually."));
1221
+ return null;
1222
+ }
1223
+ console.log("");
1224
+ let selected;
1225
+ try {
1226
+ selected = await select({
1227
+ message: "Pick an available model",
1228
+ choices: models.map((m) => ({ name: m, value: m }))
1229
+ });
1230
+ } catch {
1231
+ return null;
1232
+ }
1233
+ const isDefaultModel = failedModel === config.model;
1234
+ const updatedConfig = isDefaultModel ? { ...config, model: selected } : { ...config, fastModel: selected };
1235
+ writeConfigFile(updatedConfig);
1236
+ if (isDefaultModel) {
1237
+ process.env.CALIBER_MODEL = selected;
1238
+ } else {
1239
+ process.env.CALIBER_FAST_MODEL = selected;
1240
+ }
1241
+ console.log(chalk.green(`\u2713 Switched to ${selected}
1242
+ `));
1243
+ return selected;
1244
+ }
1245
+
1111
1246
  // src/llm/index.ts
1247
+ init_config();
1112
1248
  var cachedProvider = null;
1113
1249
  var cachedConfig = null;
1114
1250
  function createProvider(config) {
@@ -1151,6 +1287,10 @@ function getProvider() {
1151
1287
  cachedProvider = createProvider(config);
1152
1288
  return cachedProvider;
1153
1289
  }
1290
+ function resetProvider() {
1291
+ cachedProvider = null;
1292
+ cachedConfig = null;
1293
+ }
1154
1294
  var TRANSIENT_ERRORS = ["terminated", "ECONNRESET", "ETIMEDOUT", "socket hang up", "other side closed"];
1155
1295
  var MAX_RETRIES = 3;
1156
1296
  function isTransientError(error) {
@@ -1168,6 +1308,16 @@ async function llmCall(options) {
1168
1308
  return await provider.call(options);
1169
1309
  } catch (err) {
1170
1310
  const error = err instanceof Error ? err : new Error(String(err));
1311
+ if (isModelNotAvailableError(error) && cachedConfig) {
1312
+ const failedModel = options.model || cachedConfig.model;
1313
+ const newModel = await handleModelNotAvailable(failedModel, provider, cachedConfig);
1314
+ if (newModel) {
1315
+ resetProvider();
1316
+ const newProvider = getProvider();
1317
+ return await newProvider.call({ ...options, model: newModel });
1318
+ }
1319
+ throw error;
1320
+ }
1171
1321
  if (isOverloaded(error) && attempt < MAX_RETRIES) {
1172
1322
  await new Promise((r) => setTimeout(r, 2e3));
1173
1323
  continue;
@@ -1185,6 +1335,40 @@ async function llmJsonCall(options) {
1185
1335
  const text = await llmCall(options);
1186
1336
  return parseJsonResponse(text);
1187
1337
  }
1338
+ async function validateModel(options) {
1339
+ const provider = getProvider();
1340
+ const config = cachedConfig;
1341
+ if (!config) return;
1342
+ const modelsToCheck = [config.model];
1343
+ if (options?.fast) {
1344
+ const { getFastModel: getFastModel2 } = await Promise.resolve().then(() => (init_config(), config_exports));
1345
+ const fast = getFastModel2();
1346
+ if (fast && fast !== config.model) modelsToCheck.push(fast);
1347
+ }
1348
+ for (const model of modelsToCheck) {
1349
+ try {
1350
+ await provider.call({
1351
+ system: "Respond with OK",
1352
+ prompt: "ping",
1353
+ model,
1354
+ maxTokens: 1
1355
+ });
1356
+ } catch (err) {
1357
+ const error = err instanceof Error ? err : new Error(String(err));
1358
+ if (isModelNotAvailableError(error)) {
1359
+ const newModel = await handleModelNotAvailable(model, provider, config);
1360
+ if (newModel) {
1361
+ resetProvider();
1362
+ return;
1363
+ }
1364
+ throw error;
1365
+ }
1366
+ }
1367
+ }
1368
+ }
1369
+
1370
+ // src/ai/detect.ts
1371
+ init_config();
1188
1372
 
1189
1373
  // src/ai/prompts.ts
1190
1374
  var GENERATION_SYSTEM_PROMPT = `You are an expert auditor for coding agent configurations (Claude Code, Cursor, and Codex).
@@ -1479,6 +1663,7 @@ async function detectProjectStack(fileTree, fileContents) {
1479
1663
  }
1480
1664
 
1481
1665
  // src/fingerprint/index.ts
1666
+ init_config();
1482
1667
  async function collectFingerprint(dir) {
1483
1668
  const gitRemoteUrl = getGitRemoteUrl();
1484
1669
  const fileTree = getFileTree(dir);
@@ -2176,9 +2361,9 @@ function cleanupStaging() {
2176
2361
  }
2177
2362
 
2178
2363
  // src/utils/review.ts
2179
- import chalk from "chalk";
2364
+ import chalk2 from "chalk";
2180
2365
  import fs14 from "fs";
2181
- import select from "@inquirer/select";
2366
+ import select2 from "@inquirer/select";
2182
2367
  import { createTwoFilesPatch } from "diff";
2183
2368
 
2184
2369
  // src/utils/editor.ts
@@ -2221,7 +2406,7 @@ function openDiffsInEditor(editor, files) {
2221
2406
 
2222
2407
  // src/utils/review.ts
2223
2408
  async function promptWantsReview() {
2224
- return select({
2409
+ return select2({
2225
2410
  message: "Would you like to review the diffs before deciding?",
2226
2411
  choices: [
2227
2412
  { name: "Yes, show me the diffs", value: true },
@@ -2242,7 +2427,7 @@ async function promptReviewMethod() {
2242
2427
  return { name: "Terminal", value: "terminal" };
2243
2428
  }
2244
2429
  });
2245
- return select({ message: "How would you like to review the changes?", choices });
2430
+ return select2({ message: "How would you like to review the changes?", choices });
2246
2431
  }
2247
2432
  async function openReview(method, stagedFiles) {
2248
2433
  if (method === "cursor" || method === "vscode") {
@@ -2250,7 +2435,7 @@ async function openReview(method, stagedFiles) {
2250
2435
  originalPath: f.originalPath,
2251
2436
  proposedPath: f.proposedPath
2252
2437
  })));
2253
- console.log(chalk.dim(" Diffs opened in your editor.\n"));
2438
+ console.log(chalk2.dim(" Diffs opened in your editor.\n"));
2254
2439
  return;
2255
2440
  }
2256
2441
  const fileInfos = stagedFiles.map((file) => {
@@ -2281,8 +2466,8 @@ async function openReview(method, stagedFiles) {
2281
2466
  async function interactiveDiffExplorer(files) {
2282
2467
  if (!process.stdin.isTTY) {
2283
2468
  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}`)}`;
2469
+ const icon = f.isNew ? chalk2.green("+") : chalk2.yellow("~");
2470
+ const stats = f.isNew ? chalk2.dim(`${f.lines} lines`) : `${chalk2.green(`+${f.added}`)} ${chalk2.red(`-${f.removed}`)}`;
2286
2471
  console.log(` ${icon} ${f.relativePath} ${stats}`);
2287
2472
  }
2288
2473
  console.log("");
@@ -2298,47 +2483,47 @@ async function interactiveDiffExplorer(files) {
2298
2483
  }
2299
2484
  function renderFileList() {
2300
2485
  const lines = [];
2301
- lines.push(chalk.bold(" Review changes"));
2486
+ lines.push(chalk2.bold(" Review changes"));
2302
2487
  lines.push("");
2303
2488
  for (let i = 0; i < files.length; i++) {
2304
2489
  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}`)}`;
2490
+ const ptr = i === cursor ? chalk2.cyan(">") : " ";
2491
+ const icon = f.isNew ? chalk2.green("+") : chalk2.yellow("~");
2492
+ const stats = f.isNew ? chalk2.dim(`${f.lines} lines`) : `${chalk2.green(`+${f.added}`)} ${chalk2.red(`-${f.removed}`)}`;
2308
2493
  lines.push(` ${ptr} ${icon} ${f.relativePath} ${stats}`);
2309
2494
  }
2310
2495
  lines.push("");
2311
- lines.push(chalk.dim(" \u2191\u2193 navigate \u23CE view diff q done"));
2496
+ lines.push(chalk2.dim(" \u2191\u2193 navigate \u23CE view diff q done"));
2312
2497
  return lines.join("\n");
2313
2498
  }
2314
2499
  function renderDiff(index) {
2315
2500
  const f = files[index];
2316
2501
  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}`)}`;
2502
+ const header = f.isNew ? ` ${chalk2.green("+")} ${f.relativePath} ${chalk2.dim("(new file)")}` : ` ${chalk2.yellow("~")} ${f.relativePath} ${chalk2.green(`+${f.added}`)} ${chalk2.red(`-${f.removed}`)}`;
2318
2503
  lines.push(header);
2319
- lines.push(chalk.dim(" " + "\u2500".repeat(60)));
2504
+ lines.push(chalk2.dim(" " + "\u2500".repeat(60)));
2320
2505
  const patchLines = f.patch.split("\n");
2321
2506
  const bodyLines = patchLines.slice(4);
2322
2507
  const maxVisible = getTermHeight() - 4;
2323
2508
  const visibleLines = bodyLines.slice(scrollOffset, scrollOffset + maxVisible);
2324
2509
  for (const line of visibleLines) {
2325
2510
  if (line.startsWith("+")) {
2326
- lines.push(chalk.green(" " + line));
2511
+ lines.push(chalk2.green(" " + line));
2327
2512
  } else if (line.startsWith("-")) {
2328
- lines.push(chalk.red(" " + line));
2513
+ lines.push(chalk2.red(" " + line));
2329
2514
  } else if (line.startsWith("@@")) {
2330
- lines.push(chalk.cyan(" " + line));
2515
+ lines.push(chalk2.cyan(" " + line));
2331
2516
  } else {
2332
- lines.push(chalk.dim(" " + line));
2517
+ lines.push(chalk2.dim(" " + line));
2333
2518
  }
2334
2519
  }
2335
2520
  const totalBody = bodyLines.length;
2336
2521
  if (totalBody > maxVisible) {
2337
2522
  const pct = Math.round((scrollOffset + maxVisible) / totalBody * 100);
2338
- lines.push(chalk.dim(` \u2500\u2500 ${Math.min(pct, 100)}% \u2500\u2500`));
2523
+ lines.push(chalk2.dim(` \u2500\u2500 ${Math.min(pct, 100)}% \u2500\u2500`));
2339
2524
  }
2340
2525
  lines.push("");
2341
- lines.push(chalk.dim(" \u2191\u2193 scroll \u23B5/esc back to file list"));
2526
+ lines.push(chalk2.dim(" \u2191\u2193 scroll \u23B5/esc back to file list"));
2342
2527
  return lines.join("\n");
2343
2528
  }
2344
2529
  function draw(initial) {
@@ -2744,7 +2929,7 @@ function getCurrentHeadSha() {
2744
2929
  }
2745
2930
 
2746
2931
  // src/utils/spinner-messages.ts
2747
- import chalk2 from "chalk";
2932
+ import chalk3 from "chalk";
2748
2933
  var GENERATION_MESSAGES = [
2749
2934
  "Analyzing your project structure and dependencies...",
2750
2935
  "Mapping out build commands and test workflows...",
@@ -2794,9 +2979,9 @@ var SpinnerMessages = class {
2794
2979
  this.currentBaseMessage = this.messages[0];
2795
2980
  this.updateSpinnerText();
2796
2981
  if (this.showElapsedTime) {
2797
- this.spinner.suffixText = chalk2.dim(`(${this.formatElapsed()})`);
2982
+ this.spinner.suffixText = chalk3.dim(`(${this.formatElapsed()})`);
2798
2983
  this.elapsedTimer = setInterval(() => {
2799
- this.spinner.suffixText = chalk2.dim(`(${this.formatElapsed()})`);
2984
+ this.spinner.suffixText = chalk3.dim(`(${this.formatElapsed()})`);
2800
2985
  }, 1e3);
2801
2986
  }
2802
2987
  this.timer = setInterval(() => {
@@ -2830,14 +3015,18 @@ var SpinnerMessages = class {
2830
3015
  }
2831
3016
  };
2832
3017
 
3018
+ // src/commands/onboard.ts
3019
+ init_config();
3020
+
2833
3021
  // src/commands/interactive-provider-setup.ts
2834
- import chalk3 from "chalk";
3022
+ init_config();
3023
+ import chalk4 from "chalk";
2835
3024
  import readline2 from "readline";
2836
- import select2 from "@inquirer/select";
3025
+ import select3 from "@inquirer/select";
2837
3026
  function promptInput(question) {
2838
3027
  const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
2839
3028
  return new Promise((resolve2) => {
2840
- rl.question(chalk3.cyan(`${question} `), (answer) => {
3029
+ rl.question(chalk4.cyan(`${question} `), (answer) => {
2841
3030
  rl.close();
2842
3031
  resolve2(answer.trim());
2843
3032
  });
@@ -2852,7 +3041,7 @@ var PROVIDER_CHOICES = [
2852
3041
  ];
2853
3042
  async function runInteractiveProviderSetup(options) {
2854
3043
  const message = options?.selectMessage ?? "Select LLM provider";
2855
- const provider = await select2({
3044
+ const provider = await select3({
2856
3045
  message,
2857
3046
  choices: PROVIDER_CHOICES
2858
3047
  });
@@ -2860,19 +3049,19 @@ async function runInteractiveProviderSetup(options) {
2860
3049
  switch (provider) {
2861
3050
  case "claude-cli": {
2862
3051
  config.model = "default";
2863
- console.log(chalk3.dim(" Run `claude` once and log in with your Pro/Max/Team account if you haven't."));
3052
+ console.log(chalk4.dim(" Run `claude` once and log in with your Pro/Max/Team account if you haven't."));
2864
3053
  break;
2865
3054
  }
2866
3055
  case "cursor": {
2867
3056
  config.model = "default";
2868
- console.log(chalk3.dim(" Run `agent login` if you haven't, or set CURSOR_API_KEY."));
3057
+ console.log(chalk4.dim(" Run `agent login` if you haven't, or set CURSOR_API_KEY."));
2869
3058
  break;
2870
3059
  }
2871
3060
  case "anthropic": {
2872
- console.log(chalk3.dim(" Get a key at https://console.anthropic.com (same account as Claude Pro/Team/Max)."));
3061
+ console.log(chalk4.dim(" Get a key at https://console.anthropic.com (same account as Claude Pro/Team/Max)."));
2873
3062
  config.apiKey = await promptInput("Anthropic API key:");
2874
3063
  if (!config.apiKey) {
2875
- console.log(chalk3.red("API key is required."));
3064
+ console.log(chalk4.red("API key is required."));
2876
3065
  throw new Error("__exit__");
2877
3066
  }
2878
3067
  config.model = await promptInput(`Model (default: ${DEFAULT_MODELS.anthropic}):`) || DEFAULT_MODELS.anthropic;
@@ -2881,7 +3070,7 @@ async function runInteractiveProviderSetup(options) {
2881
3070
  case "vertex": {
2882
3071
  config.vertexProjectId = await promptInput("GCP Project ID:");
2883
3072
  if (!config.vertexProjectId) {
2884
- console.log(chalk3.red("Project ID is required."));
3073
+ console.log(chalk4.red("Project ID is required."));
2885
3074
  throw new Error("__exit__");
2886
3075
  }
2887
3076
  config.vertexRegion = await promptInput("Region (default: us-east5):") || "us-east5";
@@ -2892,7 +3081,7 @@ async function runInteractiveProviderSetup(options) {
2892
3081
  case "openai": {
2893
3082
  config.apiKey = await promptInput("API key:");
2894
3083
  if (!config.apiKey) {
2895
- console.log(chalk3.red("API key is required."));
3084
+ console.log(chalk4.red("API key is required."));
2896
3085
  throw new Error("__exit__");
2897
3086
  }
2898
3087
  config.baseUrl = await promptInput("Base URL (leave empty for OpenAI, or enter custom endpoint):") || void 0;
@@ -3016,15 +3205,15 @@ function computeGrade(score) {
3016
3205
  // src/scoring/checks/coverage.ts
3017
3206
  import { readFileSync, readdirSync } from "fs";
3018
3207
  import { join } from "path";
3019
- function readFileOrNull(path24) {
3208
+ function readFileOrNull(path23) {
3020
3209
  try {
3021
- return readFileSync(path24, "utf-8");
3210
+ return readFileSync(path23, "utf-8");
3022
3211
  } catch {
3023
3212
  return null;
3024
3213
  }
3025
3214
  }
3026
- function readJsonOrNull(path24) {
3027
- const content = readFileOrNull(path24);
3215
+ function readJsonOrNull(path23) {
3216
+ const content = readFileOrNull(path23);
3028
3217
  if (!content) return null;
3029
3218
  try {
3030
3219
  return JSON.parse(content);
@@ -3392,9 +3581,9 @@ function checkExistence(dir) {
3392
3581
  // src/scoring/checks/quality.ts
3393
3582
  import { readFileSync as readFileSync3 } from "fs";
3394
3583
  import { join as join3 } from "path";
3395
- function readFileOrNull2(path24) {
3584
+ function readFileOrNull2(path23) {
3396
3585
  try {
3397
- return readFileSync3(path24, "utf-8");
3586
+ return readFileSync3(path23, "utf-8");
3398
3587
  } catch {
3399
3588
  return null;
3400
3589
  }
@@ -3547,15 +3736,15 @@ function checkQuality(dir) {
3547
3736
  // src/scoring/checks/accuracy.ts
3548
3737
  import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync } from "fs";
3549
3738
  import { join as join4 } from "path";
3550
- function readFileOrNull3(path24) {
3739
+ function readFileOrNull3(path23) {
3551
3740
  try {
3552
- return readFileSync4(path24, "utf-8");
3741
+ return readFileSync4(path23, "utf-8");
3553
3742
  } catch {
3554
3743
  return null;
3555
3744
  }
3556
3745
  }
3557
- function readJsonOrNull2(path24) {
3558
- const content = readFileOrNull3(path24);
3746
+ function readJsonOrNull2(path23) {
3747
+ const content = readFileOrNull3(path23);
3559
3748
  if (!content) return null;
3560
3749
  try {
3561
3750
  return JSON.parse(content);
@@ -3738,9 +3927,9 @@ function checkAccuracy(dir) {
3738
3927
  // src/scoring/checks/freshness.ts
3739
3928
  import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
3740
3929
  import { join as join5 } from "path";
3741
- function readFileOrNull4(path24) {
3930
+ function readFileOrNull4(path23) {
3742
3931
  try {
3743
- return readFileSync5(path24, "utf-8");
3932
+ return readFileSync5(path23, "utf-8");
3744
3933
  } catch {
3745
3934
  return null;
3746
3935
  }
@@ -3854,9 +4043,9 @@ function checkFreshness(dir) {
3854
4043
  import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "fs";
3855
4044
  import { execSync as execSync7 } from "child_process";
3856
4045
  import { join as join6 } from "path";
3857
- function readFileOrNull5(path24) {
4046
+ function readFileOrNull5(path23) {
3858
4047
  try {
3859
- return readFileSync6(path24, "utf-8");
4048
+ return readFileSync6(path23, "utf-8");
3860
4049
  } catch {
3861
4050
  return null;
3862
4051
  }
@@ -4030,7 +4219,7 @@ function computeLocalScore(dir, targetAgent) {
4030
4219
  }
4031
4220
 
4032
4221
  // src/scoring/display.ts
4033
- import chalk4 from "chalk";
4222
+ import chalk5 from "chalk";
4034
4223
  var AGENT_DISPLAY_NAMES = {
4035
4224
  claude: "Claude Code",
4036
4225
  cursor: "Cursor",
@@ -4048,31 +4237,31 @@ var CATEGORY_ORDER = ["existence", "quality", "coverage", "accuracy", "freshness
4048
4237
  function gradeColor(grade) {
4049
4238
  switch (grade) {
4050
4239
  case "A":
4051
- return chalk4.green;
4240
+ return chalk5.green;
4052
4241
  case "B":
4053
- return chalk4.greenBright;
4242
+ return chalk5.greenBright;
4054
4243
  case "C":
4055
- return chalk4.yellow;
4244
+ return chalk5.yellow;
4056
4245
  case "D":
4057
- return chalk4.hex("#f97316");
4246
+ return chalk5.hex("#f97316");
4058
4247
  case "F":
4059
- return chalk4.red;
4248
+ return chalk5.red;
4060
4249
  default:
4061
- return chalk4.white;
4250
+ return chalk5.white;
4062
4251
  }
4063
4252
  }
4064
4253
  function progressBar(score, max, width = 40) {
4065
4254
  const filled = Math.round(score / max * width);
4066
4255
  const empty = width - filled;
4067
- const bar = chalk4.hex("#f97316")("\u2593".repeat(filled)) + chalk4.gray("\u2591".repeat(empty));
4256
+ const bar = chalk5.hex("#f97316")("\u2593".repeat(filled)) + chalk5.gray("\u2591".repeat(empty));
4068
4257
  return bar;
4069
4258
  }
4070
4259
  function formatCheck(check) {
4071
- const icon = check.passed ? chalk4.green("\u2713") : check.earnedPoints < 0 ? chalk4.red("\u2717") : chalk4.gray("\u2717");
4072
- const points = check.passed ? chalk4.green(`+${check.earnedPoints}`.padStart(4)) : check.earnedPoints < 0 ? chalk4.red(`${check.earnedPoints}`.padStart(4)) : chalk4.gray(" \u2014");
4073
- const name = check.passed ? chalk4.white(check.name) : chalk4.gray(check.name);
4074
- const detail = check.detail ? chalk4.gray(` (${check.detail})`) : "";
4075
- const suggestion = !check.passed && check.suggestion ? chalk4.gray(`
4260
+ const icon = check.passed ? chalk5.green("\u2713") : check.earnedPoints < 0 ? chalk5.red("\u2717") : chalk5.gray("\u2717");
4261
+ const points = check.passed ? chalk5.green(`+${check.earnedPoints}`.padStart(4)) : check.earnedPoints < 0 ? chalk5.red(`${check.earnedPoints}`.padStart(4)) : chalk5.gray(" \u2014");
4262
+ const name = check.passed ? chalk5.white(check.name) : chalk5.gray(check.name);
4263
+ const detail = check.detail ? chalk5.gray(` (${check.detail})`) : "";
4264
+ const suggestion = !check.passed && check.suggestion ? chalk5.gray(`
4076
4265
  \u2192 ${check.suggestion}`) : "";
4077
4266
  return ` ${icon} ${name.padEnd(38)}${points}${detail}${suggestion}`;
4078
4267
  }
@@ -4080,19 +4269,19 @@ function displayScore(result) {
4080
4269
  const gc = gradeColor(result.grade);
4081
4270
  const agentLabel = result.targetAgent.map((a) => AGENT_DISPLAY_NAMES[a] || a).join(" + ");
4082
4271
  console.log("");
4083
- 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"));
4272
+ console.log(chalk5.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"));
4084
4273
  console.log("");
4085
- console.log(` ${chalk4.bold("Agent Config Score")} ${gc(chalk4.bold(`${result.score} / ${result.maxScore}`))} Grade ${gc(chalk4.bold(result.grade))}`);
4274
+ console.log(` ${chalk5.bold("Agent Config Score")} ${gc(chalk5.bold(`${result.score} / ${result.maxScore}`))} Grade ${gc(chalk5.bold(result.grade))}`);
4086
4275
  console.log(` ${progressBar(result.score, result.maxScore)}`);
4087
- console.log(chalk4.dim(` Target: ${agentLabel}`));
4276
+ console.log(chalk5.dim(` Target: ${agentLabel}`));
4088
4277
  console.log("");
4089
- 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"));
4278
+ console.log(chalk5.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"));
4090
4279
  console.log("");
4091
4280
  for (const category of CATEGORY_ORDER) {
4092
4281
  const summary = result.categories[category];
4093
4282
  const categoryChecks = result.checks.filter((c) => c.category === category);
4094
4283
  console.log(
4095
- chalk4.gray(` ${CATEGORY_LABELS[category]}`) + chalk4.gray(" ".repeat(Math.max(1, 45 - CATEGORY_LABELS[category].length))) + chalk4.white(`${summary.earned}`) + chalk4.gray(` / ${summary.max}`)
4284
+ chalk5.gray(` ${CATEGORY_LABELS[category]}`) + chalk5.gray(" ".repeat(Math.max(1, 45 - CATEGORY_LABELS[category].length))) + chalk5.white(`${summary.earned}`) + chalk5.gray(` / ${summary.max}`)
4096
4285
  );
4097
4286
  for (const check of categoryChecks) {
4098
4287
  console.log(formatCheck(check));
@@ -4105,565 +4294,69 @@ function displayScoreSummary(result) {
4105
4294
  const agentLabel = result.targetAgent.map((a) => AGENT_DISPLAY_NAMES[a] || a).join(" + ");
4106
4295
  console.log("");
4107
4296
  console.log(
4108
- 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)}`)
4297
+ chalk5.gray(" ") + gc(`${result.score}/${result.maxScore}`) + chalk5.gray(` (Grade ${result.grade})`) + chalk5.gray(` \xB7 ${agentLabel}`) + chalk5.gray(` \xB7 ${progressBar(result.score, result.maxScore, 20)}`)
4109
4298
  );
4110
4299
  const failing = result.checks.filter((c) => !c.passed);
4111
4300
  if (failing.length > 0) {
4112
4301
  const shown = failing.slice(0, 5);
4113
4302
  for (const check of shown) {
4114
- console.log(chalk4.gray(` \u2717 ${check.name}`));
4303
+ console.log(chalk5.gray(` \u2717 ${check.name}`));
4115
4304
  }
4116
4305
  const remaining = failing.length - shown.length;
4117
4306
  const moreText = remaining > 0 ? ` (+${remaining} more)` : "";
4118
- console.log(chalk4.dim(`
4119
- Run ${chalk4.hex("#83D1EB")("caliber score")} for details.${moreText}`));
4307
+ console.log(chalk5.dim(`
4308
+ Run ${chalk5.hex("#83D1EB")("caliber score")} for details.${moreText}`));
4120
4309
  }
4121
4310
  console.log("");
4122
4311
  }
4123
4312
  function displayScoreDelta(before, after) {
4124
4313
  const delta = after.score - before.score;
4125
4314
  const deltaStr = delta >= 0 ? `+${delta}` : `${delta}`;
4126
- const deltaColor = delta >= 0 ? chalk4.green : chalk4.red;
4315
+ const deltaColor = delta >= 0 ? chalk5.green : chalk5.red;
4127
4316
  const beforeGc = gradeColor(before.grade);
4128
4317
  const afterGc = gradeColor(after.grade);
4129
4318
  console.log("");
4130
- 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"));
4319
+ console.log(chalk5.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"));
4131
4320
  console.log("");
4132
4321
  console.log(
4133
- ` Score: ${beforeGc(`${before.score}`)} ${chalk4.gray("\u2192")} ${afterGc(`${after.score}`)} ${deltaColor(deltaStr + " pts")} ${beforeGc(before.grade)} ${chalk4.gray("\u2192")} ${afterGc(after.grade)}`
4322
+ ` Score: ${beforeGc(`${before.score}`)} ${chalk5.gray("\u2192")} ${afterGc(`${after.score}`)} ${deltaColor(deltaStr + " pts")} ${beforeGc(before.grade)} ${chalk5.gray("\u2192")} ${afterGc(after.grade)}`
4134
4323
  );
4135
- console.log(` ${progressBar(before.score, before.maxScore, 19)} ${chalk4.gray("\u2192")} ${progressBar(after.score, after.maxScore, 19)}`);
4324
+ console.log(` ${progressBar(before.score, before.maxScore, 19)} ${chalk5.gray("\u2192")} ${progressBar(after.score, after.maxScore, 19)}`);
4136
4325
  console.log("");
4137
- 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"));
4326
+ console.log(chalk5.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"));
4138
4327
  console.log("");
4139
4328
  const improved = after.checks.filter((ac) => {
4140
4329
  const bc = before.checks.find((b) => b.id === ac.id);
4141
4330
  return bc && ac.earnedPoints > bc.earnedPoints;
4142
4331
  });
4143
4332
  if (improved.length > 0) {
4144
- console.log(chalk4.gray(" What improved:"));
4333
+ console.log(chalk5.gray(" What improved:"));
4145
4334
  for (const check of improved) {
4146
4335
  const bc = before.checks.find((b) => b.id === check.id);
4147
4336
  const gain = check.earnedPoints - bc.earnedPoints;
4148
4337
  console.log(
4149
- chalk4.green(" +") + chalk4.white(` ${check.name.padEnd(50)}`) + chalk4.green(`+${gain}`)
4338
+ chalk5.green(" +") + chalk5.white(` ${check.name.padEnd(50)}`) + chalk5.green(`+${gain}`)
4150
4339
  );
4151
4340
  }
4152
4341
  console.log("");
4153
4342
  }
4154
4343
  }
4155
4344
 
4156
- // src/mcp/index.ts
4157
- import chalk5 from "chalk";
4158
- import ora from "ora";
4159
- import readline3 from "readline";
4160
- import fs20 from "fs";
4161
- import path16 from "path";
4162
-
4163
- // src/mcp/search.ts
4164
- var AWESOME_MCP_URL = "https://raw.githubusercontent.com/punkpeye/awesome-mcp-servers/main/README.md";
4165
- var GITHUB_SEARCH_URL = "https://github.com/search";
4166
- var SEARCH_HEADERS = {
4167
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
4168
- "Accept": "text/html"
4169
- };
4170
- function parseGitHubSearchHtml(html) {
4171
- try {
4172
- const scriptMatch = html.match(/<script type="application\/json" data-target="react-app\.embeddedData">([\s\S]*?)<\/script>/);
4173
- if (!scriptMatch) {
4174
- return [];
4175
- }
4176
- const data = JSON.parse(scriptMatch[1]);
4177
- const results = data?.payload?.results;
4178
- if (!Array.isArray(results)) {
4179
- return [];
4180
- }
4181
- return results.map((r) => {
4182
- const repo = r.repo?.repository;
4183
- const ownerLogin = repo?.owner_login || "";
4184
- const name = repo?.name || "";
4185
- const description = typeof r.hl_trunc_description === "string" ? r.hl_trunc_description.replace(/<\/?em>/g, "") : null;
4186
- return {
4187
- full_name: `${ownerLogin}/${name}`,
4188
- description,
4189
- stargazers_count: typeof r.followers === "number" ? r.followers : 0,
4190
- pushed_at: repo?.updated_at || "",
4191
- owner_login: ownerLogin
4192
- };
4193
- });
4194
- } catch {
4195
- return [];
4196
- }
4197
- }
4198
- async function searchAllMcpSources(toolDeps) {
4199
- if (toolDeps.length === 0) return [];
4200
- const searches = [
4201
- searchAwesomeMcpLists(toolDeps),
4202
- ...toolDeps.map((dep) => searchGitHub(dep)),
4203
- ...toolDeps.map((dep) => searchVendorOrg(dep))
4204
- ];
4205
- const results = await Promise.all(searches);
4206
- const seen = /* @__PURE__ */ new Map();
4207
- for (const batch of results) {
4208
- for (const candidate of batch) {
4209
- const key = candidate.repoFullName.toLowerCase();
4210
- const existing = seen.get(key);
4211
- if (!existing || candidate.vendor || candidate.stars > existing.stars) {
4212
- seen.set(key, candidate);
4213
- }
4214
- }
4215
- }
4216
- return Array.from(seen.values());
4217
- }
4218
- async function searchAwesomeMcpLists(toolDeps) {
4219
- try {
4220
- const resp = await fetch(AWESOME_MCP_URL, {
4221
- signal: AbortSignal.timeout(1e4)
4222
- });
4223
- if (!resp.ok) return [];
4224
- const markdown = await resp.text();
4225
- const candidates = [];
4226
- const depLower = toolDeps.map((d) => d.toLowerCase().replace(/^@[^/]+\//, ""));
4227
- const itemPattern = /^[-*]\s+\[([^\]]+)\]\(([^)]+)\)\s*[-–—:]\s*(.*)/gm;
4228
- let match;
4229
- while ((match = itemPattern.exec(markdown)) !== null) {
4230
- const [, name, url, description] = match;
4231
- if (!url.includes("github.com")) continue;
4232
- const text = `${name} ${description}`.toLowerCase();
4233
- const matchedDep = depLower.find((d) => text.includes(d));
4234
- if (!matchedDep) continue;
4235
- const repoMatch = url.match(/github\.com\/([^/]+\/[^/]+)/);
4236
- if (!repoMatch) continue;
4237
- candidates.push({
4238
- name: name.trim(),
4239
- repoFullName: repoMatch[1],
4240
- url: url.trim(),
4241
- description: description.trim().slice(0, 200),
4242
- stars: 0,
4243
- lastPush: "",
4244
- vendor: false,
4245
- score: 0,
4246
- reason: "",
4247
- matchedDep: toolDeps.find((d) => d.toLowerCase().replace(/^@[^/]+\//, "") === matchedDep) || matchedDep
4248
- });
4249
- }
4250
- return candidates;
4251
- } catch {
4252
- return [];
4253
- }
4254
- }
4255
- async function searchGitHub(dep) {
4256
- const depName = dep.replace(/^@[^/]+\//, "");
4257
- try {
4258
- const query = encodeURIComponent(`${depName} mcp server`);
4259
- const url = `${GITHUB_SEARCH_URL}?q=${query}&type=repositories&s=stars&o=desc`;
4260
- const resp = await fetch(url, {
4261
- signal: AbortSignal.timeout(1e4),
4262
- headers: SEARCH_HEADERS
4263
- });
4264
- if (!resp.ok) return [];
4265
- const html = await resp.text();
4266
- const repos = parseGitHubSearchHtml(html).slice(0, 5);
4267
- return repos.map((repo) => ({
4268
- name: repo.full_name.split("/")[1],
4269
- repoFullName: repo.full_name,
4270
- url: `https://github.com/${repo.full_name}`,
4271
- description: repo.description || "",
4272
- stars: repo.stargazers_count,
4273
- lastPush: repo.pushed_at,
4274
- vendor: false,
4275
- score: 0,
4276
- reason: "",
4277
- matchedDep: dep
4278
- }));
4279
- } catch {
4280
- return [];
4281
- }
4282
- }
4283
- async function searchVendorOrg(dep) {
4284
- const depName = dep.replace(/^@([^/]+)\/.*/, "$1").replace(/^@/, "");
4285
- const orgName = depName.toLowerCase();
4286
- try {
4287
- const query = encodeURIComponent(`mcp org:${orgName}`);
4288
- const url = `${GITHUB_SEARCH_URL}?q=${query}&type=repositories&s=stars&o=desc`;
4289
- const resp = await fetch(url, {
4290
- signal: AbortSignal.timeout(1e4),
4291
- headers: SEARCH_HEADERS
4292
- });
4293
- if (!resp.ok) return [];
4294
- const html = await resp.text();
4295
- const repos = parseGitHubSearchHtml(html).slice(0, 5);
4296
- return repos.map((repo) => ({
4297
- name: repo.full_name.split("/")[1],
4298
- repoFullName: repo.full_name,
4299
- url: `https://github.com/${repo.full_name}`,
4300
- description: repo.description || "",
4301
- stars: repo.stargazers_count,
4302
- lastPush: repo.pushed_at,
4303
- vendor: repo.owner_login.toLowerCase() === orgName,
4304
- score: 0,
4305
- reason: "",
4306
- matchedDep: dep
4307
- }));
4308
- } catch {
4309
- return [];
4310
- }
4311
- }
4312
-
4313
- // src/mcp/prompts.ts
4314
- var SCORE_MCP_PROMPT = `You evaluate MCP (Model Context Protocol) server candidates for relevance to a software project.
4315
-
4316
- Score each candidate from 0-100 based on:
4317
- - **Relevance** (40%): How directly does it match the project's detected tool dependencies?
4318
- - **Capabilities** (30%): Does it provide meaningful read+write operations (not just read-only)?
4319
- - **Quality signals** (30%): Stars, recent activity, vendor/official status
4320
-
4321
- Return a JSON array where each element has:
4322
- - "index": the candidate's index number
4323
- - "score": relevance score 0-100
4324
- - "reason": one-liner explaining the score (max 80 chars)
4325
-
4326
- Be selective. Only score candidates that would genuinely help developers working on this project.
4327
- Return ONLY the JSON array.`;
4328
- var EXTRACT_CONFIG_PROMPT = `You extract MCP server configuration from a README file.
4329
-
4330
- Look for the MCP server's configuration block \u2014 typically a JSON snippet showing how to add it to claude_desktop_config.json, .mcp.json, or similar.
4331
-
4332
- Return a JSON object with:
4333
- - "command": the executable command (e.g., "npx", "uvx", "node", "docker")
4334
- - "args": array of arguments (e.g., ["-y", "@supabase/mcp-server"])
4335
- - "env": array of objects, each with:
4336
- - "key": environment variable name (e.g., "SUPABASE_ACCESS_TOKEN")
4337
- - "description": brief description of what this value is (e.g., "Personal access token from dashboard")
4338
- - "required": boolean
4339
-
4340
- If the README shows multiple configuration methods, prefer npx > uvx > node > docker.
4341
- If you cannot determine the configuration, return {"command": "", "args": [], "env": []}.
4342
- Return ONLY the JSON object.`;
4343
-
4344
- // src/mcp/validate.ts
4345
- var MIN_STARS = 100;
4346
- var MAX_AGE_DAYS = 180;
4347
- async function validateAndScore(candidates, toolDeps) {
4348
- const qualityFiltered = candidates.filter((c) => {
4349
- if (c.vendor) return true;
4350
- if (c.stars > 0 && c.stars < MIN_STARS) return false;
4351
- if (c.lastPush) {
4352
- const pushDate = new Date(c.lastPush);
4353
- const daysAgo = (Date.now() - pushDate.getTime()) / (1e3 * 60 * 60 * 24);
4354
- if (daysAgo > MAX_AGE_DAYS) return false;
4355
- }
4356
- return true;
4357
- });
4358
- if (qualityFiltered.length === 0) return [];
4359
- try {
4360
- return await scoreWithLLM(qualityFiltered, toolDeps);
4361
- } catch {
4362
- return qualityFiltered.slice(0, 5).map((c) => ({
4363
- ...c,
4364
- score: 50,
4365
- reason: c.description.slice(0, 80)
4366
- }));
4367
- }
4368
- }
4369
- async function scoreWithLLM(candidates, toolDeps) {
4370
- const candidateList = candidates.map((c, i) => {
4371
- const vendorTag = c.vendor ? " [VENDOR/OFFICIAL]" : "";
4372
- return `${i}. "${c.name}"${vendorTag} (${c.stars} stars) \u2014 ${c.description.slice(0, 100)}`;
4373
- }).join("\n");
4374
- const fastModel = getFastModel();
4375
- const scored = await llmJsonCall({
4376
- system: SCORE_MCP_PROMPT,
4377
- prompt: `TOOL DEPENDENCIES IN PROJECT:
4378
- ${toolDeps.join(", ")}
4379
-
4380
- MCP SERVER CANDIDATES:
4381
- ${candidateList}`,
4382
- maxTokens: 4e3,
4383
- ...fastModel ? { model: fastModel } : {}
4384
- });
4385
- if (!Array.isArray(scored)) return [];
4386
- return scored.filter((s) => s.score >= 60 && s.index >= 0 && s.index < candidates.length).sort((a, b) => b.score - a.score).slice(0, 5).map((s) => ({
4387
- ...candidates[s.index],
4388
- score: s.score,
4389
- reason: s.reason || candidates[s.index].description.slice(0, 80)
4390
- }));
4391
- }
4392
-
4393
- // src/mcp/config-extract.ts
4394
- async function fetchReadme(repoFullName) {
4395
- try {
4396
- const resp = await fetch(
4397
- `https://raw.githubusercontent.com/${repoFullName}/HEAD/README.md`,
4398
- { signal: AbortSignal.timeout(1e4) }
4399
- );
4400
- if (resp.ok) return await resp.text();
4401
- } catch {
4402
- }
4403
- try {
4404
- const resp = await fetch(
4405
- `https://raw.githubusercontent.com/${repoFullName}/main/README.md`,
4406
- { signal: AbortSignal.timeout(1e4) }
4407
- );
4408
- if (resp.ok) return await resp.text();
4409
- } catch {
4410
- }
4411
- return null;
4412
- }
4413
- async function extractMcpConfig(readme, serverName) {
4414
- try {
4415
- const truncated = readme.length > 15e3 ? readme.slice(0, 15e3) : readme;
4416
- const fastModel = getFastModel();
4417
- const result = await llmJsonCall({
4418
- system: EXTRACT_CONFIG_PROMPT,
4419
- prompt: `MCP Server: ${serverName}
4420
-
4421
- README:
4422
- ${truncated}`,
4423
- maxTokens: 2e3,
4424
- ...fastModel ? { model: fastModel } : {}
4425
- });
4426
- if (!result || !result.command) return null;
4427
- return {
4428
- command: result.command,
4429
- args: Array.isArray(result.args) ? result.args : [],
4430
- env: Array.isArray(result.env) ? result.env : []
4431
- };
4432
- } catch {
4433
- return null;
4434
- }
4435
- }
4436
-
4437
- // src/mcp/index.ts
4438
- async function discoverAndInstallMcps(targetAgent, fingerprint, dir) {
4439
- console.log(chalk5.hex("#6366f1").bold("\n MCP Server Discovery\n"));
4440
- const toolDeps = fingerprint.tools;
4441
- if (toolDeps.length === 0) {
4442
- console.log(chalk5.dim(" No external tools or services detected \u2014 skipping MCP discovery"));
4443
- return { installed: 0, names: [] };
4444
- }
4445
- const spinner = ora(`Searching MCP servers for ${toolDeps.length} detected tool${toolDeps.length === 1 ? "" : "s"}...`).start();
4446
- console.log(chalk5.dim(` Detected: ${toolDeps.join(", ")}`));
4447
- const existingMcps = getExistingMcpNames(fingerprint, targetAgent);
4448
- const filteredDeps = toolDeps.filter((d) => {
4449
- const lower = d.toLowerCase();
4450
- return !existingMcps.some((name) => name.includes(lower) || lower.includes(name));
4451
- });
4452
- if (filteredDeps.length === 0) {
4453
- spinner.succeed(chalk5.dim("All detected tools already have MCP servers configured"));
4454
- return { installed: 0, names: [] };
4455
- }
4456
- const candidates = await searchAllMcpSources(filteredDeps);
4457
- if (candidates.length === 0) {
4458
- spinner.succeed(chalk5.dim("No MCP servers found for detected tools"));
4459
- return { installed: 0, names: [] };
4460
- }
4461
- spinner.succeed(`Found ${candidates.length} candidate${candidates.length === 1 ? "" : "s"} for ${filteredDeps.join(", ")}`);
4462
- const scoreSpinner = ora("Scoring MCP candidates...").start();
4463
- const scored = await validateAndScore(candidates, filteredDeps);
4464
- if (scored.length === 0) {
4465
- scoreSpinner.succeed(chalk5.dim("No quality MCP servers passed validation"));
4466
- console.log(chalk5.dim(` Candidates checked: ${candidates.map((c) => c.name).join(", ")}`));
4467
- return { installed: 0, names: [] };
4468
- }
4469
- scoreSpinner.succeed(`${scored.length} quality MCP server${scored.length === 1 ? "" : "s"} found`);
4470
- console.log(chalk5.dim(` Scored: ${scored.map((c) => `${c.name} (${c.score})`).join(", ")}`));
4471
- const selected = await interactiveSelect(scored);
4472
- if (!selected || selected.length === 0) {
4473
- return { installed: 0, names: [] };
4474
- }
4475
- const mcpServers = {};
4476
- const installedNames = [];
4477
- for (const mcp of selected) {
4478
- console.log(chalk5.bold(`
4479
- Configuring ${mcp.name}...`));
4480
- const readme = await fetchReadme(mcp.repoFullName);
4481
- if (!readme) {
4482
- console.log(chalk5.yellow(` Could not fetch README for ${mcp.repoFullName} \u2014 skipping`));
4483
- console.log(chalk5.dim(` Manual setup: ${mcp.url}`));
4484
- continue;
4485
- }
4486
- const config = await extractMcpConfig(readme, mcp.name);
4487
- if (!config || !config.command) {
4488
- console.log(chalk5.yellow(` Could not extract config for ${mcp.name} \u2014 skipping`));
4489
- console.log(chalk5.dim(` Manual setup: ${mcp.url}`));
4490
- continue;
4491
- }
4492
- const env = {};
4493
- for (const envVar of config.env) {
4494
- if (!envVar.required) continue;
4495
- const value = await promptInput2(` ? ${envVar.key} (${envVar.description})`);
4496
- if (value) {
4497
- env[envVar.key] = value;
4498
- }
4499
- }
4500
- const serverConfig = {
4501
- command: config.command
4502
- };
4503
- if (config.args.length > 0) serverConfig.args = config.args;
4504
- if (Object.keys(env).length > 0) serverConfig.env = env;
4505
- mcpServers[mcp.name] = serverConfig;
4506
- installedNames.push(mcp.name);
4507
- console.log(` ${chalk5.green("\u2713")} ${mcp.name} configured`);
4508
- }
4509
- if (installedNames.length === 0) {
4510
- return { installed: 0, names: [] };
4511
- }
4512
- if (targetAgent.includes("claude") || targetAgent.includes("codex")) {
4513
- writeMcpJson(path16.join(dir, ".mcp.json"), mcpServers);
4514
- }
4515
- if (targetAgent.includes("cursor")) {
4516
- const cursorDir = path16.join(dir, ".cursor");
4517
- if (!fs20.existsSync(cursorDir)) fs20.mkdirSync(cursorDir, { recursive: true });
4518
- writeMcpJson(path16.join(cursorDir, "mcp.json"), mcpServers);
4519
- }
4520
- return { installed: installedNames.length, names: installedNames };
4521
- }
4522
- function writeMcpJson(filePath, mcpServers) {
4523
- let existing = {};
4524
- try {
4525
- if (fs20.existsSync(filePath)) {
4526
- const parsed = JSON.parse(fs20.readFileSync(filePath, "utf-8"));
4527
- if (parsed.mcpServers) existing = parsed.mcpServers;
4528
- }
4529
- } catch {
4530
- }
4531
- const merged = { ...existing, ...mcpServers };
4532
- fs20.writeFileSync(filePath, JSON.stringify({ mcpServers: merged }, null, 2) + "\n");
4533
- }
4534
- function getExistingMcpNames(fingerprint, targetAgent) {
4535
- const names = [];
4536
- if (targetAgent.includes("claude")) {
4537
- if (fingerprint.existingConfigs.claudeMcpServers) {
4538
- names.push(...Object.keys(fingerprint.existingConfigs.claudeMcpServers).map((k) => k.toLowerCase()));
4539
- }
4540
- }
4541
- if (targetAgent.includes("cursor")) {
4542
- if (fingerprint.existingConfigs.cursorMcpServers) {
4543
- names.push(...Object.keys(fingerprint.existingConfigs.cursorMcpServers).map((k) => k.toLowerCase()));
4544
- }
4545
- }
4546
- return names;
4547
- }
4548
- function promptInput2(question) {
4549
- const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
4550
- return new Promise((resolve2) => {
4551
- rl.question(chalk5.cyan(`${question}: `), (answer) => {
4552
- rl.close();
4553
- resolve2(answer.trim());
4554
- });
4555
- });
4556
- }
4557
- async function interactiveSelect(candidates) {
4558
- if (!process.stdin.isTTY) {
4559
- console.log(chalk5.bold("\n Available MCP servers:\n"));
4560
- for (const c of candidates) {
4561
- const vendorTag = c.vendor ? chalk5.blue(" (vendor)") : "";
4562
- console.log(` ${String(c.score).padStart(3)} ${c.name}${vendorTag} ${chalk5.dim(c.reason)}`);
4563
- }
4564
- console.log("");
4565
- return null;
4566
- }
4567
- const selected = /* @__PURE__ */ new Set();
4568
- let cursor = 0;
4569
- const { stdin, stdout } = process;
4570
- let lineCount = 0;
4571
- function render() {
4572
- const lines = [];
4573
- lines.push(chalk5.bold(" Select MCP servers to install:"));
4574
- lines.push("");
4575
- for (let i = 0; i < candidates.length; i++) {
4576
- const c = candidates[i];
4577
- const check = selected.has(i) ? chalk5.green("[x]") : "[ ]";
4578
- const ptr = i === cursor ? chalk5.cyan(">") : " ";
4579
- const scoreColor = c.score >= 90 ? chalk5.green : c.score >= 70 ? chalk5.yellow : chalk5.dim;
4580
- const vendorTag = c.vendor ? chalk5.blue(" (vendor)") : "";
4581
- lines.push(` ${ptr} ${check} ${scoreColor(String(c.score).padStart(3))} ${c.name}${vendorTag} ${chalk5.dim(c.reason.slice(0, 40))}`);
4582
- }
4583
- lines.push("");
4584
- lines.push(chalk5.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q skip"));
4585
- return lines.join("\n");
4586
- }
4587
- function draw(initial) {
4588
- if (!initial && lineCount > 0) {
4589
- stdout.write(`\x1B[${lineCount}A`);
4590
- }
4591
- stdout.write("\x1B[0J");
4592
- const output = render();
4593
- stdout.write(output + "\n");
4594
- lineCount = output.split("\n").length;
4595
- }
4596
- return new Promise((resolve2) => {
4597
- console.log("");
4598
- draw(true);
4599
- stdin.setRawMode(true);
4600
- stdin.resume();
4601
- stdin.setEncoding("utf8");
4602
- function cleanup() {
4603
- stdin.removeListener("data", onData);
4604
- stdin.setRawMode(false);
4605
- stdin.pause();
4606
- }
4607
- function onData(key) {
4608
- switch (key) {
4609
- case "\x1B[A":
4610
- cursor = (cursor - 1 + candidates.length) % candidates.length;
4611
- draw(false);
4612
- break;
4613
- case "\x1B[B":
4614
- cursor = (cursor + 1) % candidates.length;
4615
- draw(false);
4616
- break;
4617
- case " ":
4618
- selected.has(cursor) ? selected.delete(cursor) : selected.add(cursor);
4619
- draw(false);
4620
- break;
4621
- case "a":
4622
- candidates.forEach((_, i) => selected.add(i));
4623
- draw(false);
4624
- break;
4625
- case "n":
4626
- selected.clear();
4627
- draw(false);
4628
- break;
4629
- case "\r":
4630
- case "\n":
4631
- cleanup();
4632
- if (selected.size === 0) {
4633
- console.log(chalk5.dim("\n No MCP servers selected.\n"));
4634
- resolve2(null);
4635
- } else {
4636
- resolve2(Array.from(selected).sort().map((i) => candidates[i]));
4637
- }
4638
- break;
4639
- case "q":
4640
- case "\x1B":
4641
- case "":
4642
- cleanup();
4643
- console.log(chalk5.dim("\n Skipped MCP server installation.\n"));
4644
- resolve2(null);
4645
- break;
4646
- }
4647
- }
4648
- stdin.on("data", onData);
4649
- });
4650
- }
4651
-
4652
4345
  // src/commands/recommend.ts
4653
4346
  import chalk6 from "chalk";
4654
- import ora2 from "ora";
4655
- import select3 from "@inquirer/select";
4347
+ import ora from "ora";
4348
+ import select4 from "@inquirer/select";
4656
4349
  import { mkdirSync, readFileSync as readFileSync7, readdirSync as readdirSync5, existsSync as existsSync9, writeFileSync } from "fs";
4657
4350
  import { join as join8, dirname as dirname2 } from "path";
4658
4351
 
4659
4352
  // src/scanner/index.ts
4660
- import fs21 from "fs";
4661
- import path17 from "path";
4353
+ import fs20 from "fs";
4354
+ import path16 from "path";
4662
4355
  import crypto2 from "crypto";
4663
4356
  function scanLocalState(dir) {
4664
4357
  const items = [];
4665
- const claudeMdPath = path17.join(dir, "CLAUDE.md");
4666
- if (fs21.existsSync(claudeMdPath)) {
4358
+ const claudeMdPath = path16.join(dir, "CLAUDE.md");
4359
+ if (fs20.existsSync(claudeMdPath)) {
4667
4360
  items.push({
4668
4361
  type: "rule",
4669
4362
  platform: "claude",
@@ -4672,10 +4365,10 @@ function scanLocalState(dir) {
4672
4365
  path: claudeMdPath
4673
4366
  });
4674
4367
  }
4675
- const skillsDir = path17.join(dir, ".claude", "skills");
4676
- if (fs21.existsSync(skillsDir)) {
4677
- for (const file of fs21.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
4678
- const filePath = path17.join(skillsDir, file);
4368
+ const skillsDir = path16.join(dir, ".claude", "skills");
4369
+ if (fs20.existsSync(skillsDir)) {
4370
+ for (const file of fs20.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
4371
+ const filePath = path16.join(skillsDir, file);
4679
4372
  items.push({
4680
4373
  type: "skill",
4681
4374
  platform: "claude",
@@ -4685,10 +4378,10 @@ function scanLocalState(dir) {
4685
4378
  });
4686
4379
  }
4687
4380
  }
4688
- const mcpJsonPath = path17.join(dir, ".mcp.json");
4689
- if (fs21.existsSync(mcpJsonPath)) {
4381
+ const mcpJsonPath = path16.join(dir, ".mcp.json");
4382
+ if (fs20.existsSync(mcpJsonPath)) {
4690
4383
  try {
4691
- const mcpJson = JSON.parse(fs21.readFileSync(mcpJsonPath, "utf-8"));
4384
+ const mcpJson = JSON.parse(fs20.readFileSync(mcpJsonPath, "utf-8"));
4692
4385
  if (mcpJson.mcpServers) {
4693
4386
  for (const name of Object.keys(mcpJson.mcpServers)) {
4694
4387
  items.push({
@@ -4703,8 +4396,8 @@ function scanLocalState(dir) {
4703
4396
  } catch {
4704
4397
  }
4705
4398
  }
4706
- const agentsMdPath = path17.join(dir, "AGENTS.md");
4707
- if (fs21.existsSync(agentsMdPath)) {
4399
+ const agentsMdPath = path16.join(dir, "AGENTS.md");
4400
+ if (fs20.existsSync(agentsMdPath)) {
4708
4401
  items.push({
4709
4402
  type: "rule",
4710
4403
  platform: "codex",
@@ -4713,12 +4406,12 @@ function scanLocalState(dir) {
4713
4406
  path: agentsMdPath
4714
4407
  });
4715
4408
  }
4716
- const codexSkillsDir = path17.join(dir, ".agents", "skills");
4717
- if (fs21.existsSync(codexSkillsDir)) {
4409
+ const codexSkillsDir = path16.join(dir, ".agents", "skills");
4410
+ if (fs20.existsSync(codexSkillsDir)) {
4718
4411
  try {
4719
- for (const name of fs21.readdirSync(codexSkillsDir)) {
4720
- const skillFile = path17.join(codexSkillsDir, name, "SKILL.md");
4721
- if (fs21.existsSync(skillFile)) {
4412
+ for (const name of fs20.readdirSync(codexSkillsDir)) {
4413
+ const skillFile = path16.join(codexSkillsDir, name, "SKILL.md");
4414
+ if (fs20.existsSync(skillFile)) {
4722
4415
  items.push({
4723
4416
  type: "skill",
4724
4417
  platform: "codex",
@@ -4731,8 +4424,8 @@ function scanLocalState(dir) {
4731
4424
  } catch {
4732
4425
  }
4733
4426
  }
4734
- const cursorrulesPath = path17.join(dir, ".cursorrules");
4735
- if (fs21.existsSync(cursorrulesPath)) {
4427
+ const cursorrulesPath = path16.join(dir, ".cursorrules");
4428
+ if (fs20.existsSync(cursorrulesPath)) {
4736
4429
  items.push({
4737
4430
  type: "rule",
4738
4431
  platform: "cursor",
@@ -4741,10 +4434,10 @@ function scanLocalState(dir) {
4741
4434
  path: cursorrulesPath
4742
4435
  });
4743
4436
  }
4744
- const cursorRulesDir = path17.join(dir, ".cursor", "rules");
4745
- if (fs21.existsSync(cursorRulesDir)) {
4746
- for (const file of fs21.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
4747
- const filePath = path17.join(cursorRulesDir, file);
4437
+ const cursorRulesDir = path16.join(dir, ".cursor", "rules");
4438
+ if (fs20.existsSync(cursorRulesDir)) {
4439
+ for (const file of fs20.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
4440
+ const filePath = path16.join(cursorRulesDir, file);
4748
4441
  items.push({
4749
4442
  type: "rule",
4750
4443
  platform: "cursor",
@@ -4754,12 +4447,12 @@ function scanLocalState(dir) {
4754
4447
  });
4755
4448
  }
4756
4449
  }
4757
- const cursorSkillsDir = path17.join(dir, ".cursor", "skills");
4758
- if (fs21.existsSync(cursorSkillsDir)) {
4450
+ const cursorSkillsDir = path16.join(dir, ".cursor", "skills");
4451
+ if (fs20.existsSync(cursorSkillsDir)) {
4759
4452
  try {
4760
- for (const name of fs21.readdirSync(cursorSkillsDir)) {
4761
- const skillFile = path17.join(cursorSkillsDir, name, "SKILL.md");
4762
- if (fs21.existsSync(skillFile)) {
4453
+ for (const name of fs20.readdirSync(cursorSkillsDir)) {
4454
+ const skillFile = path16.join(cursorSkillsDir, name, "SKILL.md");
4455
+ if (fs20.existsSync(skillFile)) {
4763
4456
  items.push({
4764
4457
  type: "skill",
4765
4458
  platform: "cursor",
@@ -4772,10 +4465,10 @@ function scanLocalState(dir) {
4772
4465
  } catch {
4773
4466
  }
4774
4467
  }
4775
- const cursorMcpPath = path17.join(dir, ".cursor", "mcp.json");
4776
- if (fs21.existsSync(cursorMcpPath)) {
4468
+ const cursorMcpPath = path16.join(dir, ".cursor", "mcp.json");
4469
+ if (fs20.existsSync(cursorMcpPath)) {
4777
4470
  try {
4778
- const mcpJson = JSON.parse(fs21.readFileSync(cursorMcpPath, "utf-8"));
4471
+ const mcpJson = JSON.parse(fs20.readFileSync(cursorMcpPath, "utf-8"));
4779
4472
  if (mcpJson.mcpServers) {
4780
4473
  for (const name of Object.keys(mcpJson.mcpServers)) {
4781
4474
  items.push({
@@ -4793,7 +4486,7 @@ function scanLocalState(dir) {
4793
4486
  return items;
4794
4487
  }
4795
4488
  function hashFile(filePath) {
4796
- const text = fs21.readFileSync(filePath, "utf-8");
4489
+ const text = fs20.readFileSync(filePath, "utf-8");
4797
4490
  return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
4798
4491
  }
4799
4492
  function hashJson(obj) {
@@ -4801,6 +4494,7 @@ function hashJson(obj) {
4801
4494
  }
4802
4495
 
4803
4496
  // src/commands/recommend.ts
4497
+ init_config();
4804
4498
  function detectLocalPlatforms() {
4805
4499
  const items = scanLocalState(process.cwd());
4806
4500
  const platforms = /* @__PURE__ */ new Set();
@@ -4955,7 +4649,7 @@ async function searchAllProviders(technologies, platform) {
4955
4649
  }
4956
4650
  return combined;
4957
4651
  }
4958
- async function scoreWithLLM2(candidates, projectContext, technologies) {
4652
+ async function scoreWithLLM(candidates, projectContext, technologies) {
4959
4653
  const candidateList = candidates.map((c, i) => `${i}. "${c.name}" \u2014 ${c.reason || "no description"}`).join("\n");
4960
4654
  const fastModel = getFastModel();
4961
4655
  const scored = await llmJsonCall({
@@ -5100,7 +4794,7 @@ function extractTopDeps() {
5100
4794
  }
5101
4795
  }
5102
4796
  async function recommendCommand() {
5103
- const proceed = await select3({
4797
+ const proceed = await select4({
5104
4798
  message: "Search public repos for relevant skills to add to this project?",
5105
4799
  choices: [
5106
4800
  { name: "Yes, find skills for my project", value: true },
@@ -5127,7 +4821,7 @@ async function searchAndInstallSkills() {
5127
4821
  throw new Error("__exit__");
5128
4822
  }
5129
4823
  const primaryPlatform = platforms.includes("claude") ? "claude" : platforms[0];
5130
- const searchSpinner = ora2("Searching skill registries...").start();
4824
+ const searchSpinner = ora("Searching skill registries...").start();
5131
4825
  const allCandidates = await searchAllProviders(technologies, primaryPlatform);
5132
4826
  if (!allCandidates.length) {
5133
4827
  searchSpinner.succeed("No skills found matching your tech stack.");
@@ -5145,10 +4839,10 @@ async function searchAndInstallSkills() {
5145
4839
  let results;
5146
4840
  const config = loadConfig();
5147
4841
  if (config) {
5148
- const scoreSpinner = ora2("Scoring relevance for your project...").start();
4842
+ const scoreSpinner = ora("Scoring relevance for your project...").start();
5149
4843
  try {
5150
4844
  const projectContext = buildProjectContext(fingerprint);
5151
- results = await scoreWithLLM2(newCandidates, projectContext, technologies);
4845
+ results = await scoreWithLLM(newCandidates, projectContext, technologies);
5152
4846
  if (results.length === 0) {
5153
4847
  scoreSpinner.succeed("No highly relevant skills found for your specific project.");
5154
4848
  return;
@@ -5161,7 +4855,7 @@ async function searchAndInstallSkills() {
5161
4855
  } else {
5162
4856
  results = newCandidates.slice(0, 20);
5163
4857
  }
5164
- const fetchSpinner = ora2("Verifying skill availability...").start();
4858
+ const fetchSpinner = ora("Verifying skill availability...").start();
5165
4859
  const contentMap = /* @__PURE__ */ new Map();
5166
4860
  await Promise.all(results.map(async (rec) => {
5167
4861
  const content = await fetchSkillContent(rec);
@@ -5176,12 +4870,12 @@ async function searchAndInstallSkills() {
5176
4870
  fetchSpinner.succeed(
5177
4871
  `${available.length} installable skill${available.length > 1 ? "s" : ""}` + (unavailableCount > 0 ? chalk6.dim(` (${unavailableCount} unavailable)`) : "")
5178
4872
  );
5179
- const selected = await interactiveSelect2(available);
4873
+ const selected = await interactiveSelect(available);
5180
4874
  if (selected?.length) {
5181
4875
  await installSkills(selected, platforms, contentMap);
5182
4876
  }
5183
4877
  }
5184
- async function interactiveSelect2(recs) {
4878
+ async function interactiveSelect(recs) {
5185
4879
  if (!process.stdin.isTTY) {
5186
4880
  printSkills(recs);
5187
4881
  return null;
@@ -5326,7 +5020,7 @@ async function fetchSkillContent(rec) {
5326
5020
  return null;
5327
5021
  }
5328
5022
  async function installSkills(recs, platforms, contentMap) {
5329
- const spinner = ora2(`Installing ${recs.length} skill${recs.length > 1 ? "s" : ""}...`).start();
5023
+ const spinner = ora(`Installing ${recs.length} skill${recs.length > 1 ? "s" : ""}...`).start();
5330
5024
  const installed = [];
5331
5025
  for (const rec of recs) {
5332
5026
  const content = contentMap.get(rec.slug);
@@ -5419,9 +5113,10 @@ async function initCommand(options) {
5419
5113
  const fastModel = getFastModel();
5420
5114
  const modelLine = fastModel ? ` Provider: ${config.provider} | Model: ${displayModel} | Scan: ${fastModel}` : ` Provider: ${config.provider} | Model: ${displayModel}`;
5421
5115
  console.log(chalk7.dim(modelLine + "\n"));
5116
+ await validateModel({ fast: true });
5422
5117
  console.log(title.bold(" Step 2/6 \u2014 Discover your project\n"));
5423
5118
  console.log(chalk7.dim(" Learning about your languages, dependencies, structure, and existing configs.\n"));
5424
- const spinner = ora3("Analyzing project...").start();
5119
+ const spinner = ora2("Analyzing project...").start();
5425
5120
  const fingerprint = await collectFingerprint(process.cwd());
5426
5121
  spinner.succeed("Project analyzed");
5427
5122
  console.log(chalk7.dim(` Languages: ${fingerprint.languages.join(", ") || "none detected"}`));
@@ -5465,7 +5160,7 @@ async function initCommand(options) {
5465
5160
  }
5466
5161
  const isEmpty = fingerprint.fileTree.length < 3;
5467
5162
  if (isEmpty) {
5468
- fingerprint.description = await promptInput3("What will you build in this project?");
5163
+ fingerprint.description = await promptInput2("What will you build in this project?");
5469
5164
  }
5470
5165
  let failingChecks;
5471
5166
  let passingChecks;
@@ -5493,7 +5188,7 @@ async function initCommand(options) {
5493
5188
  }
5494
5189
  console.log(chalk7.dim(" This can take a couple of minutes depending on your model and provider.\n"));
5495
5190
  const genStartTime = Date.now();
5496
- const genSpinner = ora3("Generating setup...").start();
5191
+ const genSpinner = ora2("Generating setup...").start();
5497
5192
  const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
5498
5193
  genMessages.start();
5499
5194
  let generatedSetup = null;
@@ -5593,7 +5288,7 @@ async function initCommand(options) {
5593
5288
  console.log(JSON.stringify(generatedSetup, null, 2));
5594
5289
  return;
5595
5290
  }
5596
- const writeSpinner = ora3("Writing config files...").start();
5291
+ const writeSpinner = ora2("Writing config files...").start();
5597
5292
  try {
5598
5293
  const result = writeSetup(generatedSetup);
5599
5294
  writeSpinner.succeed("Config files written");
@@ -5616,25 +5311,6 @@ async function initCommand(options) {
5616
5311
  console.error(chalk7.red(err instanceof Error ? err.message : "Unknown error"));
5617
5312
  throw new Error("__exit__");
5618
5313
  }
5619
- console.log(title.bold("\n Step 5/6 \u2014 Enhance with MCP servers\n"));
5620
- console.log(chalk7.dim(" MCP servers connect your AI agents to external tools and services"));
5621
- console.log(chalk7.dim(" like databases, APIs, and platforms your project depends on.\n"));
5622
- if (fingerprint.tools.length > 0) {
5623
- try {
5624
- const mcpResult = await discoverAndInstallMcps(targetAgent, fingerprint, process.cwd());
5625
- if (mcpResult.installed > 0) {
5626
- console.log(chalk7.bold(`
5627
- ${mcpResult.installed} MCP server${mcpResult.installed > 1 ? "s" : ""} configured`));
5628
- for (const name of mcpResult.names) {
5629
- console.log(` ${chalk7.green("\u2713")} ${name}`);
5630
- }
5631
- }
5632
- } catch (err) {
5633
- console.log(chalk7.dim(" MCP discovery skipped: " + (err instanceof Error ? err.message : "unknown error")));
5634
- }
5635
- } else {
5636
- console.log(chalk7.dim(" No external tools or services detected \u2014 skipping MCP discovery.\n"));
5637
- }
5638
5314
  ensurePermissions();
5639
5315
  const sha = getCurrentHeadSha();
5640
5316
  writeState({
@@ -5693,7 +5369,7 @@ async function initCommand(options) {
5693
5369
  displayScoreDelta(baselineScore, afterScore);
5694
5370
  console.log(title.bold("\n Step 6/6 \u2014 Community skills\n"));
5695
5371
  console.log(chalk7.dim(" Search public skill registries for skills that match your tech stack.\n"));
5696
- const wantsSkills = await select4({
5372
+ const wantsSkills = await select5({
5697
5373
  message: "Search public repos for relevant skills to add to this project?",
5698
5374
  choices: [
5699
5375
  { name: "Yes, find skills for my project", value: true },
@@ -5722,7 +5398,7 @@ async function initCommand(options) {
5722
5398
  }
5723
5399
  async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
5724
5400
  while (true) {
5725
- const message = await promptInput3("\nWhat would you like to change?");
5401
+ const message = await promptInput2("\nWhat would you like to change?");
5726
5402
  if (!message || message.toLowerCase() === "done" || message.toLowerCase() === "accept") {
5727
5403
  return currentSetup;
5728
5404
  }
@@ -5736,7 +5412,7 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
5736
5412
  console.log(chalk7.dim(' Type "done" to accept the current setup.\n'));
5737
5413
  continue;
5738
5414
  }
5739
- const refineSpinner = ora3("Refining setup...").start();
5415
+ const refineSpinner = ora2("Refining setup...").start();
5740
5416
  const refineMessages = new SpinnerMessages(refineSpinner, REFINE_MESSAGES);
5741
5417
  refineMessages.start();
5742
5418
  const refined = await refineSetup(
@@ -5763,7 +5439,7 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
5763
5439
  }
5764
5440
  function summarizeSetup(action, setup) {
5765
5441
  const descriptions = setup.fileDescriptions;
5766
- const files = descriptions ? Object.entries(descriptions).map(([path24, desc]) => ` ${path24}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
5442
+ const files = descriptions ? Object.entries(descriptions).map(([path23, desc]) => ` ${path23}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
5767
5443
  return `${action}. Files:
5768
5444
  ${files}`;
5769
5445
  }
@@ -5814,8 +5490,8 @@ ${JSON.stringify(checkList, null, 2)}`,
5814
5490
  return [];
5815
5491
  }
5816
5492
  }
5817
- function promptInput3(question) {
5818
- const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
5493
+ function promptInput2(question) {
5494
+ const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
5819
5495
  return new Promise((resolve2) => {
5820
5496
  rl.question(chalk7.cyan(`${question} `), (answer) => {
5821
5497
  rl.close();
@@ -5849,13 +5525,13 @@ async function promptHookType(targetAgent) {
5849
5525
  choices.push({ name: "Both (Claude Code + pre-commit)", value: "both" });
5850
5526
  }
5851
5527
  choices.push({ name: "Skip for now", value: "skip" });
5852
- return select4({
5528
+ return select5({
5853
5529
  message: "How would you like to auto-refresh your docs?",
5854
5530
  choices
5855
5531
  });
5856
5532
  }
5857
5533
  async function promptReviewAction() {
5858
- return select4({
5534
+ return select5({
5859
5535
  message: "What would you like to do?",
5860
5536
  choices: [
5861
5537
  { name: "Accept and apply", value: "accept" },
@@ -5876,7 +5552,7 @@ function printSetupSummary(setup) {
5876
5552
  };
5877
5553
  if (claude) {
5878
5554
  if (claude.claudeMd) {
5879
- const icon = fs22.existsSync("CLAUDE.md") ? chalk7.yellow("~") : chalk7.green("+");
5555
+ const icon = fs21.existsSync("CLAUDE.md") ? chalk7.yellow("~") : chalk7.green("+");
5880
5556
  const desc = getDescription("CLAUDE.md");
5881
5557
  console.log(` ${icon} ${chalk7.bold("CLAUDE.md")}`);
5882
5558
  if (desc) console.log(chalk7.dim(` ${desc}`));
@@ -5886,7 +5562,7 @@ function printSetupSummary(setup) {
5886
5562
  if (Array.isArray(skills) && skills.length > 0) {
5887
5563
  for (const skill of skills) {
5888
5564
  const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
5889
- const icon = fs22.existsSync(skillPath) ? chalk7.yellow("~") : chalk7.green("+");
5565
+ const icon = fs21.existsSync(skillPath) ? chalk7.yellow("~") : chalk7.green("+");
5890
5566
  const desc = getDescription(skillPath);
5891
5567
  console.log(` ${icon} ${chalk7.bold(skillPath)}`);
5892
5568
  console.log(chalk7.dim(` ${desc || skill.description || skill.name}`));
@@ -5897,7 +5573,7 @@ function printSetupSummary(setup) {
5897
5573
  const codex = setup.codex;
5898
5574
  if (codex) {
5899
5575
  if (codex.agentsMd) {
5900
- const icon = fs22.existsSync("AGENTS.md") ? chalk7.yellow("~") : chalk7.green("+");
5576
+ const icon = fs21.existsSync("AGENTS.md") ? chalk7.yellow("~") : chalk7.green("+");
5901
5577
  const desc = getDescription("AGENTS.md");
5902
5578
  console.log(` ${icon} ${chalk7.bold("AGENTS.md")}`);
5903
5579
  if (desc) console.log(chalk7.dim(` ${desc}`));
@@ -5907,7 +5583,7 @@ function printSetupSummary(setup) {
5907
5583
  if (Array.isArray(codexSkills) && codexSkills.length > 0) {
5908
5584
  for (const skill of codexSkills) {
5909
5585
  const skillPath = `.agents/skills/${skill.name}/SKILL.md`;
5910
- const icon = fs22.existsSync(skillPath) ? chalk7.yellow("~") : chalk7.green("+");
5586
+ const icon = fs21.existsSync(skillPath) ? chalk7.yellow("~") : chalk7.green("+");
5911
5587
  const desc = getDescription(skillPath);
5912
5588
  console.log(` ${icon} ${chalk7.bold(skillPath)}`);
5913
5589
  console.log(chalk7.dim(` ${desc || skill.description || skill.name}`));
@@ -5917,7 +5593,7 @@ function printSetupSummary(setup) {
5917
5593
  }
5918
5594
  if (cursor) {
5919
5595
  if (cursor.cursorrules) {
5920
- const icon = fs22.existsSync(".cursorrules") ? chalk7.yellow("~") : chalk7.green("+");
5596
+ const icon = fs21.existsSync(".cursorrules") ? chalk7.yellow("~") : chalk7.green("+");
5921
5597
  const desc = getDescription(".cursorrules");
5922
5598
  console.log(` ${icon} ${chalk7.bold(".cursorrules")}`);
5923
5599
  if (desc) console.log(chalk7.dim(` ${desc}`));
@@ -5927,7 +5603,7 @@ function printSetupSummary(setup) {
5927
5603
  if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
5928
5604
  for (const skill of cursorSkills) {
5929
5605
  const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
5930
- const icon = fs22.existsSync(skillPath) ? chalk7.yellow("~") : chalk7.green("+");
5606
+ const icon = fs21.existsSync(skillPath) ? chalk7.yellow("~") : chalk7.green("+");
5931
5607
  const desc = getDescription(skillPath);
5932
5608
  console.log(` ${icon} ${chalk7.bold(skillPath)}`);
5933
5609
  console.log(chalk7.dim(` ${desc || skill.description || skill.name}`));
@@ -5938,7 +5614,7 @@ function printSetupSummary(setup) {
5938
5614
  if (Array.isArray(rules) && rules.length > 0) {
5939
5615
  for (const rule of rules) {
5940
5616
  const rulePath = `.cursor/rules/${rule.filename}`;
5941
- const icon = fs22.existsSync(rulePath) ? chalk7.yellow("~") : chalk7.green("+");
5617
+ const icon = fs21.existsSync(rulePath) ? chalk7.yellow("~") : chalk7.green("+");
5942
5618
  const desc = getDescription(rulePath);
5943
5619
  console.log(` ${icon} ${chalk7.bold(rulePath)}`);
5944
5620
  if (desc) {
@@ -5951,7 +5627,7 @@ function printSetupSummary(setup) {
5951
5627
  }
5952
5628
  }
5953
5629
  }
5954
- if (!codex && !fs22.existsSync("AGENTS.md")) {
5630
+ if (!codex && !fs21.existsSync("AGENTS.md")) {
5955
5631
  console.log(` ${chalk7.green("+")} ${chalk7.bold("AGENTS.md")}`);
5956
5632
  console.log(chalk7.dim(" Cross-agent coordination file"));
5957
5633
  console.log("");
@@ -5970,8 +5646,8 @@ function ensurePermissions() {
5970
5646
  const settingsPath = ".claude/settings.json";
5971
5647
  let settings = {};
5972
5648
  try {
5973
- if (fs22.existsSync(settingsPath)) {
5974
- settings = JSON.parse(fs22.readFileSync(settingsPath, "utf-8"));
5649
+ if (fs21.existsSync(settingsPath)) {
5650
+ settings = JSON.parse(fs21.readFileSync(settingsPath, "utf-8"));
5975
5651
  }
5976
5652
  } catch {
5977
5653
  }
@@ -5985,15 +5661,15 @@ function ensurePermissions() {
5985
5661
  "Bash(git *)"
5986
5662
  ];
5987
5663
  settings.permissions = permissions;
5988
- if (!fs22.existsSync(".claude")) fs22.mkdirSync(".claude", { recursive: true });
5989
- fs22.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5664
+ if (!fs21.existsSync(".claude")) fs21.mkdirSync(".claude", { recursive: true });
5665
+ fs21.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5990
5666
  }
5991
5667
 
5992
5668
  // src/commands/undo.ts
5993
5669
  import chalk8 from "chalk";
5994
- import ora4 from "ora";
5670
+ import ora3 from "ora";
5995
5671
  function undoCommand() {
5996
- const spinner = ora4("Reverting setup...").start();
5672
+ const spinner = ora3("Reverting setup...").start();
5997
5673
  try {
5998
5674
  const { restored, removed } = undoSetup();
5999
5675
  if (restored.length === 0 && removed.length === 0) {
@@ -6022,7 +5698,8 @@ function undoCommand() {
6022
5698
 
6023
5699
  // src/commands/status.ts
6024
5700
  import chalk9 from "chalk";
6025
- import fs23 from "fs";
5701
+ import fs22 from "fs";
5702
+ init_config();
6026
5703
  async function statusCommand(options) {
6027
5704
  const config = loadConfig();
6028
5705
  const manifest = readManifest();
@@ -6048,7 +5725,7 @@ async function statusCommand(options) {
6048
5725
  }
6049
5726
  console.log(` Files managed: ${chalk9.cyan(manifest.entries.length.toString())}`);
6050
5727
  for (const entry of manifest.entries) {
6051
- const exists = fs23.existsSync(entry.path);
5728
+ const exists = fs22.existsSync(entry.path);
6052
5729
  const icon = exists ? chalk9.green("\u2713") : chalk9.red("\u2717");
6053
5730
  console.log(` ${icon} ${entry.path} (${entry.action})`);
6054
5731
  }
@@ -6057,8 +5734,9 @@ async function statusCommand(options) {
6057
5734
 
6058
5735
  // src/commands/regenerate.ts
6059
5736
  import chalk10 from "chalk";
6060
- import ora5 from "ora";
6061
- import select5 from "@inquirer/select";
5737
+ import ora4 from "ora";
5738
+ import select6 from "@inquirer/select";
5739
+ init_config();
6062
5740
  async function regenerateCommand(options) {
6063
5741
  const config = loadConfig();
6064
5742
  if (!config) {
@@ -6071,7 +5749,8 @@ async function regenerateCommand(options) {
6071
5749
  throw new Error("__exit__");
6072
5750
  }
6073
5751
  const targetAgent = readState()?.targetAgent ?? ["claude", "cursor"];
6074
- const spinner = ora5("Analyzing project...").start();
5752
+ await validateModel({ fast: true });
5753
+ const spinner = ora4("Analyzing project...").start();
6075
5754
  const fingerprint = await collectFingerprint(process.cwd());
6076
5755
  spinner.succeed("Project analyzed");
6077
5756
  const baselineScore = computeLocalScore(process.cwd(), targetAgent);
@@ -6080,7 +5759,7 @@ async function regenerateCommand(options) {
6080
5759
  console.log(chalk10.green(" Your setup is already at 100/100 \u2014 nothing to regenerate.\n"));
6081
5760
  return;
6082
5761
  }
6083
- const genSpinner = ora5("Regenerating setup...").start();
5762
+ const genSpinner = ora4("Regenerating setup...").start();
6084
5763
  const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
6085
5764
  genMessages.start();
6086
5765
  let generatedSetup = null;
@@ -6139,7 +5818,7 @@ async function regenerateCommand(options) {
6139
5818
  const reviewMethod = await promptReviewMethod();
6140
5819
  await openReview(reviewMethod, staged.stagedFiles);
6141
5820
  }
6142
- const action = await select5({
5821
+ const action = await select6({
6143
5822
  message: "Apply regenerated setup?",
6144
5823
  choices: [
6145
5824
  { name: "Accept and apply", value: "accept" },
@@ -6151,7 +5830,7 @@ async function regenerateCommand(options) {
6151
5830
  console.log(chalk10.dim("Regeneration cancelled. No files were modified."));
6152
5831
  return;
6153
5832
  }
6154
- const writeSpinner = ora5("Writing config files...").start();
5833
+ const writeSpinner = ora4("Writing config files...").start();
6155
5834
  try {
6156
5835
  const result = writeSetup(generatedSetup);
6157
5836
  writeSpinner.succeed("Config files written");
@@ -6225,10 +5904,10 @@ async function scoreCommand(options) {
6225
5904
  }
6226
5905
 
6227
5906
  // src/commands/refresh.ts
6228
- import fs25 from "fs";
6229
- import path19 from "path";
5907
+ import fs24 from "fs";
5908
+ import path18 from "path";
6230
5909
  import chalk12 from "chalk";
6231
- import ora6 from "ora";
5910
+ import ora5 from "ora";
6232
5911
 
6233
5912
  // src/lib/git-diff.ts
6234
5913
  import { execSync as execSync8 } from "child_process";
@@ -6303,37 +5982,37 @@ function collectDiff(lastSha) {
6303
5982
  }
6304
5983
 
6305
5984
  // src/writers/refresh.ts
6306
- import fs24 from "fs";
6307
- import path18 from "path";
5985
+ import fs23 from "fs";
5986
+ import path17 from "path";
6308
5987
  function writeRefreshDocs(docs) {
6309
5988
  const written = [];
6310
5989
  if (docs.claudeMd) {
6311
- fs24.writeFileSync("CLAUDE.md", docs.claudeMd);
5990
+ fs23.writeFileSync("CLAUDE.md", docs.claudeMd);
6312
5991
  written.push("CLAUDE.md");
6313
5992
  }
6314
5993
  if (docs.readmeMd) {
6315
- fs24.writeFileSync("README.md", docs.readmeMd);
5994
+ fs23.writeFileSync("README.md", docs.readmeMd);
6316
5995
  written.push("README.md");
6317
5996
  }
6318
5997
  if (docs.cursorrules) {
6319
- fs24.writeFileSync(".cursorrules", docs.cursorrules);
5998
+ fs23.writeFileSync(".cursorrules", docs.cursorrules);
6320
5999
  written.push(".cursorrules");
6321
6000
  }
6322
6001
  if (docs.cursorRules) {
6323
- const rulesDir = path18.join(".cursor", "rules");
6324
- if (!fs24.existsSync(rulesDir)) fs24.mkdirSync(rulesDir, { recursive: true });
6002
+ const rulesDir = path17.join(".cursor", "rules");
6003
+ if (!fs23.existsSync(rulesDir)) fs23.mkdirSync(rulesDir, { recursive: true });
6325
6004
  for (const rule of docs.cursorRules) {
6326
- const filePath = path18.join(rulesDir, rule.filename);
6327
- fs24.writeFileSync(filePath, rule.content);
6005
+ const filePath = path17.join(rulesDir, rule.filename);
6006
+ fs23.writeFileSync(filePath, rule.content);
6328
6007
  written.push(filePath);
6329
6008
  }
6330
6009
  }
6331
6010
  if (docs.claudeSkills) {
6332
- const skillsDir = path18.join(".claude", "skills");
6333
- if (!fs24.existsSync(skillsDir)) fs24.mkdirSync(skillsDir, { recursive: true });
6011
+ const skillsDir = path17.join(".claude", "skills");
6012
+ if (!fs23.existsSync(skillsDir)) fs23.mkdirSync(skillsDir, { recursive: true });
6334
6013
  for (const skill of docs.claudeSkills) {
6335
- const filePath = path18.join(skillsDir, skill.filename);
6336
- fs24.writeFileSync(filePath, skill.content);
6014
+ const filePath = path17.join(skillsDir, skill.filename);
6015
+ fs23.writeFileSync(filePath, skill.content);
6337
6016
  written.push(filePath);
6338
6017
  }
6339
6018
  }
@@ -6341,6 +6020,7 @@ function writeRefreshDocs(docs) {
6341
6020
  }
6342
6021
 
6343
6022
  // src/ai/refresh.ts
6023
+ init_config();
6344
6024
  async function refreshDocs(diff, existingDocs, projectContext) {
6345
6025
  const prompt = buildRefreshPrompt(diff, existingDocs, projectContext);
6346
6026
  const fastModel = getFastModel();
@@ -6404,17 +6084,18 @@ Changed files: ${diff.changedFiles.join(", ")}`);
6404
6084
  }
6405
6085
 
6406
6086
  // src/commands/refresh.ts
6087
+ init_config();
6407
6088
  function log(quiet, ...args) {
6408
6089
  if (!quiet) console.log(...args);
6409
6090
  }
6410
6091
  function discoverGitRepos(parentDir) {
6411
6092
  const repos = [];
6412
6093
  try {
6413
- const entries = fs25.readdirSync(parentDir, { withFileTypes: true });
6094
+ const entries = fs24.readdirSync(parentDir, { withFileTypes: true });
6414
6095
  for (const entry of entries) {
6415
6096
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
6416
- const childPath = path19.join(parentDir, entry.name);
6417
- if (fs25.existsSync(path19.join(childPath, ".git"))) {
6097
+ const childPath = path18.join(parentDir, entry.name);
6098
+ if (fs24.existsSync(path18.join(childPath, ".git"))) {
6418
6099
  repos.push(childPath);
6419
6100
  }
6420
6101
  }
@@ -6436,7 +6117,7 @@ async function refreshSingleRepo(repoDir, options) {
6436
6117
  log(quiet, chalk12.dim(`${prefix}No changes since last refresh.`));
6437
6118
  return;
6438
6119
  }
6439
- const spinner = quiet ? null : ora6(`${prefix}Analyzing changes...`).start();
6120
+ const spinner = quiet ? null : ora5(`${prefix}Analyzing changes...`).start();
6440
6121
  const existingDocs = readExistingConfigs(repoDir);
6441
6122
  const fingerprint = await collectFingerprint(repoDir);
6442
6123
  const projectContext = {
@@ -6495,6 +6176,7 @@ async function refreshCommand(options) {
6495
6176
  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."));
6496
6177
  throw new Error("__exit__");
6497
6178
  }
6179
+ await validateModel({ fast: true });
6498
6180
  if (isGitRepo()) {
6499
6181
  await refreshSingleRepo(process.cwd(), options);
6500
6182
  return;
@@ -6509,7 +6191,7 @@ async function refreshCommand(options) {
6509
6191
  `));
6510
6192
  const originalDir = process.cwd();
6511
6193
  for (const repo of repos) {
6512
- const repoName = path19.basename(repo);
6194
+ const repoName = path18.basename(repo);
6513
6195
  try {
6514
6196
  process.chdir(repo);
6515
6197
  await refreshSingleRepo(repo, { ...options, label: repoName });
@@ -6689,6 +6371,7 @@ async function hooksCommand(options) {
6689
6371
  }
6690
6372
 
6691
6373
  // src/commands/config.ts
6374
+ init_config();
6692
6375
  import chalk14 from "chalk";
6693
6376
  async function configCommand() {
6694
6377
  const existing = loadConfig();
@@ -6761,8 +6444,8 @@ function readStdin() {
6761
6444
 
6762
6445
  // src/learner/storage.ts
6763
6446
  init_constants();
6764
- import fs26 from "fs";
6765
- import path20 from "path";
6447
+ import fs25 from "fs";
6448
+ import path19 from "path";
6766
6449
  var MAX_RESPONSE_LENGTH = 2e3;
6767
6450
  var DEFAULT_STATE = {
6768
6451
  sessionId: null,
@@ -6770,15 +6453,15 @@ var DEFAULT_STATE = {
6770
6453
  lastAnalysisTimestamp: null
6771
6454
  };
6772
6455
  function ensureLearningDir() {
6773
- if (!fs26.existsSync(LEARNING_DIR)) {
6774
- fs26.mkdirSync(LEARNING_DIR, { recursive: true });
6456
+ if (!fs25.existsSync(LEARNING_DIR)) {
6457
+ fs25.mkdirSync(LEARNING_DIR, { recursive: true });
6775
6458
  }
6776
6459
  }
6777
6460
  function sessionFilePath() {
6778
- return path20.join(LEARNING_DIR, LEARNING_SESSION_FILE);
6461
+ return path19.join(LEARNING_DIR, LEARNING_SESSION_FILE);
6779
6462
  }
6780
6463
  function stateFilePath() {
6781
- return path20.join(LEARNING_DIR, LEARNING_STATE_FILE);
6464
+ return path19.join(LEARNING_DIR, LEARNING_STATE_FILE);
6782
6465
  }
6783
6466
  function truncateResponse(response) {
6784
6467
  const str = JSON.stringify(response);
@@ -6789,50 +6472,50 @@ function appendEvent(event) {
6789
6472
  ensureLearningDir();
6790
6473
  const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
6791
6474
  const filePath = sessionFilePath();
6792
- fs26.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
6475
+ fs25.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
6793
6476
  const count = getEventCount();
6794
6477
  if (count > LEARNING_MAX_EVENTS) {
6795
- const lines = fs26.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
6478
+ const lines = fs25.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
6796
6479
  const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
6797
- fs26.writeFileSync(filePath, kept.join("\n") + "\n");
6480
+ fs25.writeFileSync(filePath, kept.join("\n") + "\n");
6798
6481
  }
6799
6482
  }
6800
6483
  function readAllEvents() {
6801
6484
  const filePath = sessionFilePath();
6802
- if (!fs26.existsSync(filePath)) return [];
6803
- const lines = fs26.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
6485
+ if (!fs25.existsSync(filePath)) return [];
6486
+ const lines = fs25.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
6804
6487
  return lines.map((line) => JSON.parse(line));
6805
6488
  }
6806
6489
  function getEventCount() {
6807
6490
  const filePath = sessionFilePath();
6808
- if (!fs26.existsSync(filePath)) return 0;
6809
- const content = fs26.readFileSync(filePath, "utf-8");
6491
+ if (!fs25.existsSync(filePath)) return 0;
6492
+ const content = fs25.readFileSync(filePath, "utf-8");
6810
6493
  return content.split("\n").filter(Boolean).length;
6811
6494
  }
6812
6495
  function clearSession() {
6813
6496
  const filePath = sessionFilePath();
6814
- if (fs26.existsSync(filePath)) fs26.unlinkSync(filePath);
6497
+ if (fs25.existsSync(filePath)) fs25.unlinkSync(filePath);
6815
6498
  }
6816
6499
  function readState2() {
6817
6500
  const filePath = stateFilePath();
6818
- if (!fs26.existsSync(filePath)) return { ...DEFAULT_STATE };
6501
+ if (!fs25.existsSync(filePath)) return { ...DEFAULT_STATE };
6819
6502
  try {
6820
- return JSON.parse(fs26.readFileSync(filePath, "utf-8"));
6503
+ return JSON.parse(fs25.readFileSync(filePath, "utf-8"));
6821
6504
  } catch {
6822
6505
  return { ...DEFAULT_STATE };
6823
6506
  }
6824
6507
  }
6825
6508
  function writeState2(state) {
6826
6509
  ensureLearningDir();
6827
- fs26.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
6510
+ fs25.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
6828
6511
  }
6829
6512
  function resetState() {
6830
6513
  writeState2({ ...DEFAULT_STATE });
6831
6514
  }
6832
6515
 
6833
6516
  // src/learner/writer.ts
6834
- import fs27 from "fs";
6835
- import path21 from "path";
6517
+ import fs26 from "fs";
6518
+ import path20 from "path";
6836
6519
  var LEARNED_START = "<!-- caliber:learned -->";
6837
6520
  var LEARNED_END = "<!-- /caliber:learned -->";
6838
6521
  function writeLearnedContent(update) {
@@ -6852,8 +6535,8 @@ function writeLearnedContent(update) {
6852
6535
  function writeLearnedSection(content) {
6853
6536
  const claudeMdPath = "CLAUDE.md";
6854
6537
  let existing = "";
6855
- if (fs27.existsSync(claudeMdPath)) {
6856
- existing = fs27.readFileSync(claudeMdPath, "utf-8");
6538
+ if (fs26.existsSync(claudeMdPath)) {
6539
+ existing = fs26.readFileSync(claudeMdPath, "utf-8");
6857
6540
  }
6858
6541
  const section = `${LEARNED_START}
6859
6542
  ${content}
@@ -6867,15 +6550,15 @@ ${LEARNED_END}`;
6867
6550
  const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
6868
6551
  updated = existing + separator + "\n" + section + "\n";
6869
6552
  }
6870
- fs27.writeFileSync(claudeMdPath, updated);
6553
+ fs26.writeFileSync(claudeMdPath, updated);
6871
6554
  }
6872
6555
  function writeLearnedSkill(skill) {
6873
- const skillDir = path21.join(".claude", "skills", skill.name);
6874
- if (!fs27.existsSync(skillDir)) fs27.mkdirSync(skillDir, { recursive: true });
6875
- const skillPath = path21.join(skillDir, "SKILL.md");
6876
- if (!skill.isNew && fs27.existsSync(skillPath)) {
6877
- const existing = fs27.readFileSync(skillPath, "utf-8");
6878
- fs27.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
6556
+ const skillDir = path20.join(".claude", "skills", skill.name);
6557
+ if (!fs26.existsSync(skillDir)) fs26.mkdirSync(skillDir, { recursive: true });
6558
+ const skillPath = path20.join(skillDir, "SKILL.md");
6559
+ if (!skill.isNew && fs26.existsSync(skillPath)) {
6560
+ const existing = fs26.readFileSync(skillPath, "utf-8");
6561
+ fs26.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
6879
6562
  } else {
6880
6563
  const frontmatter = [
6881
6564
  "---",
@@ -6884,14 +6567,14 @@ function writeLearnedSkill(skill) {
6884
6567
  "---",
6885
6568
  ""
6886
6569
  ].join("\n");
6887
- fs27.writeFileSync(skillPath, frontmatter + skill.content);
6570
+ fs26.writeFileSync(skillPath, frontmatter + skill.content);
6888
6571
  }
6889
6572
  return skillPath;
6890
6573
  }
6891
6574
  function readLearnedSection() {
6892
6575
  const claudeMdPath = "CLAUDE.md";
6893
- if (!fs27.existsSync(claudeMdPath)) return null;
6894
- const content = fs27.readFileSync(claudeMdPath, "utf-8");
6576
+ if (!fs26.existsSync(claudeMdPath)) return null;
6577
+ const content = fs26.readFileSync(claudeMdPath, "utf-8");
6895
6578
  const startIdx = content.indexOf(LEARNED_START);
6896
6579
  const endIdx = content.indexOf(LEARNED_END);
6897
6580
  if (startIdx === -1 || endIdx === -1) return null;
@@ -6899,6 +6582,7 @@ function readLearnedSection() {
6899
6582
  }
6900
6583
 
6901
6584
  // src/ai/learn.ts
6585
+ init_config();
6902
6586
  var MAX_PROMPT_TOKENS = 1e5;
6903
6587
  function formatEventsForPrompt(events) {
6904
6588
  return events.map((e, i) => {
@@ -6972,6 +6656,7 @@ ${eventsText}`;
6972
6656
  }
6973
6657
 
6974
6658
  // src/commands/learn.ts
6659
+ init_config();
6975
6660
  async function learnObserveCommand(options) {
6976
6661
  try {
6977
6662
  const raw = await readStdin();
@@ -7009,6 +6694,7 @@ async function learnFinalizeCommand() {
7009
6694
  resetState();
7010
6695
  return;
7011
6696
  }
6697
+ await validateModel();
7012
6698
  const existingConfigs = readExistingConfigs(process.cwd());
7013
6699
  const existingLearnedSection = readLearnedSection();
7014
6700
  const existingSkills = existingConfigs.claudeSkills || [];
@@ -7077,9 +6763,9 @@ Learned items in CLAUDE.md: ${chalk15.cyan(String(lineCount))}`);
7077
6763
  }
7078
6764
 
7079
6765
  // src/cli.ts
7080
- var __dirname = path22.dirname(fileURLToPath(import.meta.url));
6766
+ var __dirname = path21.dirname(fileURLToPath(import.meta.url));
7081
6767
  var pkg = JSON.parse(
7082
- fs28.readFileSync(path22.resolve(__dirname, "..", "package.json"), "utf-8")
6768
+ fs27.readFileSync(path21.resolve(__dirname, "..", "package.json"), "utf-8")
7083
6769
  );
7084
6770
  var program = new Command();
7085
6771
  var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
@@ -7112,22 +6798,22 @@ learn.command("remove").description("Remove learning hooks from .claude/settings
7112
6798
  learn.command("status").description("Show learning system status").action(learnStatusCommand);
7113
6799
 
7114
6800
  // src/utils/version-check.ts
7115
- import fs29 from "fs";
7116
- import path23 from "path";
6801
+ import fs28 from "fs";
6802
+ import path22 from "path";
7117
6803
  import { fileURLToPath as fileURLToPath2 } from "url";
7118
6804
  import { execSync as execSync9 } from "child_process";
7119
6805
  import chalk16 from "chalk";
7120
- import ora7 from "ora";
6806
+ import ora6 from "ora";
7121
6807
  import confirm from "@inquirer/confirm";
7122
- var __dirname_vc = path23.dirname(fileURLToPath2(import.meta.url));
6808
+ var __dirname_vc = path22.dirname(fileURLToPath2(import.meta.url));
7123
6809
  var pkg2 = JSON.parse(
7124
- fs29.readFileSync(path23.resolve(__dirname_vc, "..", "package.json"), "utf-8")
6810
+ fs28.readFileSync(path22.resolve(__dirname_vc, "..", "package.json"), "utf-8")
7125
6811
  );
7126
6812
  function getInstalledVersion() {
7127
6813
  try {
7128
6814
  const globalRoot = execSync9("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
7129
- const pkgPath = path23.join(globalRoot, "@rely-ai", "caliber", "package.json");
7130
- return JSON.parse(fs29.readFileSync(pkgPath, "utf-8")).version;
6815
+ const pkgPath = path22.join(globalRoot, "@rely-ai", "caliber", "package.json");
6816
+ return JSON.parse(fs28.readFileSync(pkgPath, "utf-8")).version;
7131
6817
  } catch {
7132
6818
  return null;
7133
6819
  }
@@ -7168,7 +6854,7 @@ Update available: ${current} -> ${latest}`)
7168
6854
  console.log();
7169
6855
  return;
7170
6856
  }
7171
- const spinner = ora7("Updating caliber...").start();
6857
+ const spinner = ora6("Updating caliber...").start();
7172
6858
  try {
7173
6859
  execSync9(`npm install -g @rely-ai/caliber@${latest}`, {
7174
6860
  stdio: "pipe",