@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.
- package/dist/bin.js +519 -833
- 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
|
|
55
|
-
import
|
|
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
|
|
61
|
-
import
|
|
62
|
-
import
|
|
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
|
|
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/
|
|
511
|
-
|
|
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
|
|
2364
|
+
import chalk2 from "chalk";
|
|
2180
2365
|
import fs14 from "fs";
|
|
2181
|
-
import
|
|
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
|
|
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
|
|
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(
|
|
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 ?
|
|
2285
|
-
const stats = f.isNew ?
|
|
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(
|
|
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 ?
|
|
2306
|
-
const icon = f.isNew ?
|
|
2307
|
-
const stats = f.isNew ?
|
|
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(
|
|
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 ? ` ${
|
|
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(
|
|
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(
|
|
2511
|
+
lines.push(chalk2.green(" " + line));
|
|
2327
2512
|
} else if (line.startsWith("-")) {
|
|
2328
|
-
lines.push(
|
|
2513
|
+
lines.push(chalk2.red(" " + line));
|
|
2329
2514
|
} else if (line.startsWith("@@")) {
|
|
2330
|
-
lines.push(
|
|
2515
|
+
lines.push(chalk2.cyan(" " + line));
|
|
2331
2516
|
} else {
|
|
2332
|
-
lines.push(
|
|
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(
|
|
2523
|
+
lines.push(chalk2.dim(` \u2500\u2500 ${Math.min(pct, 100)}% \u2500\u2500`));
|
|
2339
2524
|
}
|
|
2340
2525
|
lines.push("");
|
|
2341
|
-
lines.push(
|
|
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
|
|
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 =
|
|
2982
|
+
this.spinner.suffixText = chalk3.dim(`(${this.formatElapsed()})`);
|
|
2798
2983
|
this.elapsedTimer = setInterval(() => {
|
|
2799
|
-
this.spinner.suffixText =
|
|
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
|
-
|
|
3022
|
+
init_config();
|
|
3023
|
+
import chalk4 from "chalk";
|
|
2835
3024
|
import readline2 from "readline";
|
|
2836
|
-
import
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
3208
|
+
function readFileOrNull(path23) {
|
|
3020
3209
|
try {
|
|
3021
|
-
return readFileSync(
|
|
3210
|
+
return readFileSync(path23, "utf-8");
|
|
3022
3211
|
} catch {
|
|
3023
3212
|
return null;
|
|
3024
3213
|
}
|
|
3025
3214
|
}
|
|
3026
|
-
function readJsonOrNull(
|
|
3027
|
-
const content = readFileOrNull(
|
|
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(
|
|
3584
|
+
function readFileOrNull2(path23) {
|
|
3396
3585
|
try {
|
|
3397
|
-
return readFileSync3(
|
|
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(
|
|
3739
|
+
function readFileOrNull3(path23) {
|
|
3551
3740
|
try {
|
|
3552
|
-
return readFileSync4(
|
|
3741
|
+
return readFileSync4(path23, "utf-8");
|
|
3553
3742
|
} catch {
|
|
3554
3743
|
return null;
|
|
3555
3744
|
}
|
|
3556
3745
|
}
|
|
3557
|
-
function readJsonOrNull2(
|
|
3558
|
-
const content = readFileOrNull3(
|
|
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(
|
|
3930
|
+
function readFileOrNull4(path23) {
|
|
3742
3931
|
try {
|
|
3743
|
-
return readFileSync5(
|
|
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(
|
|
4046
|
+
function readFileOrNull5(path23) {
|
|
3858
4047
|
try {
|
|
3859
|
-
return readFileSync6(
|
|
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
|
|
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
|
|
4240
|
+
return chalk5.green;
|
|
4052
4241
|
case "B":
|
|
4053
|
-
return
|
|
4242
|
+
return chalk5.greenBright;
|
|
4054
4243
|
case "C":
|
|
4055
|
-
return
|
|
4244
|
+
return chalk5.yellow;
|
|
4056
4245
|
case "D":
|
|
4057
|
-
return
|
|
4246
|
+
return chalk5.hex("#f97316");
|
|
4058
4247
|
case "F":
|
|
4059
|
-
return
|
|
4248
|
+
return chalk5.red;
|
|
4060
4249
|
default:
|
|
4061
|
-
return
|
|
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 =
|
|
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 ?
|
|
4072
|
-
const points = check.passed ?
|
|
4073
|
-
const name = check.passed ?
|
|
4074
|
-
const detail = check.detail ?
|
|
4075
|
-
const suggestion = !check.passed && check.suggestion ?
|
|
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(
|
|
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(` ${
|
|
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(
|
|
4276
|
+
console.log(chalk5.dim(` Target: ${agentLabel}`));
|
|
4088
4277
|
console.log("");
|
|
4089
|
-
console.log(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
4119
|
-
Run ${
|
|
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 ?
|
|
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(
|
|
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}`)} ${
|
|
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)} ${
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
4655
|
-
import
|
|
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
|
|
4661
|
-
import
|
|
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 =
|
|
4666
|
-
if (
|
|
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 =
|
|
4676
|
-
if (
|
|
4677
|
-
for (const file of
|
|
4678
|
-
const filePath =
|
|
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 =
|
|
4689
|
-
if (
|
|
4381
|
+
const mcpJsonPath = path16.join(dir, ".mcp.json");
|
|
4382
|
+
if (fs20.existsSync(mcpJsonPath)) {
|
|
4690
4383
|
try {
|
|
4691
|
-
const mcpJson = JSON.parse(
|
|
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 =
|
|
4707
|
-
if (
|
|
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 =
|
|
4717
|
-
if (
|
|
4409
|
+
const codexSkillsDir = path16.join(dir, ".agents", "skills");
|
|
4410
|
+
if (fs20.existsSync(codexSkillsDir)) {
|
|
4718
4411
|
try {
|
|
4719
|
-
for (const name of
|
|
4720
|
-
const skillFile =
|
|
4721
|
-
if (
|
|
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 =
|
|
4735
|
-
if (
|
|
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 =
|
|
4745
|
-
if (
|
|
4746
|
-
for (const file of
|
|
4747
|
-
const filePath =
|
|
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 =
|
|
4758
|
-
if (
|
|
4450
|
+
const cursorSkillsDir = path16.join(dir, ".cursor", "skills");
|
|
4451
|
+
if (fs20.existsSync(cursorSkillsDir)) {
|
|
4759
4452
|
try {
|
|
4760
|
-
for (const name of
|
|
4761
|
-
const skillFile =
|
|
4762
|
-
if (
|
|
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 =
|
|
4776
|
-
if (
|
|
4468
|
+
const cursorMcpPath = path16.join(dir, ".cursor", "mcp.json");
|
|
4469
|
+
if (fs20.existsSync(cursorMcpPath)) {
|
|
4777
4470
|
try {
|
|
4778
|
-
const mcpJson = JSON.parse(
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
4842
|
+
const scoreSpinner = ora("Scoring relevance for your project...").start();
|
|
5149
4843
|
try {
|
|
5150
4844
|
const projectContext = buildProjectContext(fingerprint);
|
|
5151
|
-
results = await
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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(([
|
|
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
|
|
5818
|
-
const rl =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 && !
|
|
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 (
|
|
5974
|
-
settings = JSON.parse(
|
|
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 (!
|
|
5989
|
-
|
|
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
|
|
5670
|
+
import ora3 from "ora";
|
|
5995
5671
|
function undoCommand() {
|
|
5996
|
-
const spinner =
|
|
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
|
|
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 =
|
|
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
|
|
6061
|
-
import
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
6229
|
-
import
|
|
5907
|
+
import fs24 from "fs";
|
|
5908
|
+
import path18 from "path";
|
|
6230
5909
|
import chalk12 from "chalk";
|
|
6231
|
-
import
|
|
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
|
|
6307
|
-
import
|
|
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
|
-
|
|
5990
|
+
fs23.writeFileSync("CLAUDE.md", docs.claudeMd);
|
|
6312
5991
|
written.push("CLAUDE.md");
|
|
6313
5992
|
}
|
|
6314
5993
|
if (docs.readmeMd) {
|
|
6315
|
-
|
|
5994
|
+
fs23.writeFileSync("README.md", docs.readmeMd);
|
|
6316
5995
|
written.push("README.md");
|
|
6317
5996
|
}
|
|
6318
5997
|
if (docs.cursorrules) {
|
|
6319
|
-
|
|
5998
|
+
fs23.writeFileSync(".cursorrules", docs.cursorrules);
|
|
6320
5999
|
written.push(".cursorrules");
|
|
6321
6000
|
}
|
|
6322
6001
|
if (docs.cursorRules) {
|
|
6323
|
-
const rulesDir =
|
|
6324
|
-
if (!
|
|
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 =
|
|
6327
|
-
|
|
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 =
|
|
6333
|
-
if (!
|
|
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 =
|
|
6336
|
-
|
|
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 =
|
|
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 =
|
|
6417
|
-
if (
|
|
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 :
|
|
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 =
|
|
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
|
|
6765
|
-
import
|
|
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 (!
|
|
6774
|
-
|
|
6456
|
+
if (!fs25.existsSync(LEARNING_DIR)) {
|
|
6457
|
+
fs25.mkdirSync(LEARNING_DIR, { recursive: true });
|
|
6775
6458
|
}
|
|
6776
6459
|
}
|
|
6777
6460
|
function sessionFilePath() {
|
|
6778
|
-
return
|
|
6461
|
+
return path19.join(LEARNING_DIR, LEARNING_SESSION_FILE);
|
|
6779
6462
|
}
|
|
6780
6463
|
function stateFilePath() {
|
|
6781
|
-
return
|
|
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
|
-
|
|
6475
|
+
fs25.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
6793
6476
|
const count = getEventCount();
|
|
6794
6477
|
if (count > LEARNING_MAX_EVENTS) {
|
|
6795
|
-
const lines =
|
|
6478
|
+
const lines = fs25.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
6796
6479
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
6797
|
-
|
|
6480
|
+
fs25.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
6798
6481
|
}
|
|
6799
6482
|
}
|
|
6800
6483
|
function readAllEvents() {
|
|
6801
6484
|
const filePath = sessionFilePath();
|
|
6802
|
-
if (!
|
|
6803
|
-
const lines =
|
|
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 (!
|
|
6809
|
-
const content =
|
|
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 (
|
|
6497
|
+
if (fs25.existsSync(filePath)) fs25.unlinkSync(filePath);
|
|
6815
6498
|
}
|
|
6816
6499
|
function readState2() {
|
|
6817
6500
|
const filePath = stateFilePath();
|
|
6818
|
-
if (!
|
|
6501
|
+
if (!fs25.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
6819
6502
|
try {
|
|
6820
|
-
return JSON.parse(
|
|
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
|
-
|
|
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
|
|
6835
|
-
import
|
|
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 (
|
|
6856
|
-
existing =
|
|
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
|
-
|
|
6553
|
+
fs26.writeFileSync(claudeMdPath, updated);
|
|
6871
6554
|
}
|
|
6872
6555
|
function writeLearnedSkill(skill) {
|
|
6873
|
-
const skillDir =
|
|
6874
|
-
if (!
|
|
6875
|
-
const skillPath =
|
|
6876
|
-
if (!skill.isNew &&
|
|
6877
|
-
const existing =
|
|
6878
|
-
|
|
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
|
-
|
|
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 (!
|
|
6894
|
-
const content =
|
|
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 =
|
|
6766
|
+
var __dirname = path21.dirname(fileURLToPath(import.meta.url));
|
|
7081
6767
|
var pkg = JSON.parse(
|
|
7082
|
-
|
|
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
|
|
7116
|
-
import
|
|
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
|
|
6806
|
+
import ora6 from "ora";
|
|
7121
6807
|
import confirm from "@inquirer/confirm";
|
|
7122
|
-
var __dirname_vc =
|
|
6808
|
+
var __dirname_vc = path22.dirname(fileURLToPath2(import.meta.url));
|
|
7123
6809
|
var pkg2 = JSON.parse(
|
|
7124
|
-
|
|
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 =
|
|
7130
|
-
return JSON.parse(
|
|
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 =
|
|
6857
|
+
const spinner = ora6("Updating caliber...").start();
|
|
7172
6858
|
try {
|
|
7173
6859
|
execSync9(`npm install -g @rely-ai/caliber@${latest}`, {
|
|
7174
6860
|
stdio: "pipe",
|