@rely-ai/caliber 1.5.6 → 1.6.1
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 +398 -895
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -51,17 +51,17 @@ var init_constants = __esm({
|
|
|
51
51
|
|
|
52
52
|
// src/cli.ts
|
|
53
53
|
import { Command } from "commander";
|
|
54
|
-
import
|
|
55
|
-
import
|
|
54
|
+
import fs27 from "fs";
|
|
55
|
+
import path21 from "path";
|
|
56
56
|
import { fileURLToPath } from "url";
|
|
57
57
|
|
|
58
58
|
// src/commands/onboard.ts
|
|
59
|
-
import
|
|
60
|
-
import
|
|
61
|
-
import
|
|
59
|
+
import chalk6 from "chalk";
|
|
60
|
+
import ora2 from "ora";
|
|
61
|
+
import readline3 from "readline";
|
|
62
62
|
import select4 from "@inquirer/select";
|
|
63
63
|
import checkbox from "@inquirer/checkbox";
|
|
64
|
-
import
|
|
64
|
+
import fs21 from "fs";
|
|
65
65
|
|
|
66
66
|
// src/fingerprint/index.ts
|
|
67
67
|
import fs6 from "fs";
|
|
@@ -520,6 +520,11 @@ var DEFAULT_MODELS = {
|
|
|
520
520
|
cursor: "default",
|
|
521
521
|
"claude-cli": "default"
|
|
522
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
|
+
};
|
|
523
528
|
function loadConfig() {
|
|
524
529
|
const envConfig = resolveFromEnv();
|
|
525
530
|
if (envConfig) return envConfig;
|
|
@@ -591,7 +596,12 @@ function getConfigFilePath() {
|
|
|
591
596
|
return CONFIG_FILE;
|
|
592
597
|
}
|
|
593
598
|
function getFastModel() {
|
|
594
|
-
|
|
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;
|
|
595
605
|
}
|
|
596
606
|
|
|
597
607
|
// src/llm/anthropic.ts
|
|
@@ -3006,15 +3016,15 @@ function computeGrade(score) {
|
|
|
3006
3016
|
// src/scoring/checks/coverage.ts
|
|
3007
3017
|
import { readFileSync, readdirSync } from "fs";
|
|
3008
3018
|
import { join } from "path";
|
|
3009
|
-
function readFileOrNull(
|
|
3019
|
+
function readFileOrNull(path23) {
|
|
3010
3020
|
try {
|
|
3011
|
-
return readFileSync(
|
|
3021
|
+
return readFileSync(path23, "utf-8");
|
|
3012
3022
|
} catch {
|
|
3013
3023
|
return null;
|
|
3014
3024
|
}
|
|
3015
3025
|
}
|
|
3016
|
-
function readJsonOrNull(
|
|
3017
|
-
const content = readFileOrNull(
|
|
3026
|
+
function readJsonOrNull(path23) {
|
|
3027
|
+
const content = readFileOrNull(path23);
|
|
3018
3028
|
if (!content) return null;
|
|
3019
3029
|
try {
|
|
3020
3030
|
return JSON.parse(content);
|
|
@@ -3382,9 +3392,9 @@ function checkExistence(dir) {
|
|
|
3382
3392
|
// src/scoring/checks/quality.ts
|
|
3383
3393
|
import { readFileSync as readFileSync3 } from "fs";
|
|
3384
3394
|
import { join as join3 } from "path";
|
|
3385
|
-
function readFileOrNull2(
|
|
3395
|
+
function readFileOrNull2(path23) {
|
|
3386
3396
|
try {
|
|
3387
|
-
return readFileSync3(
|
|
3397
|
+
return readFileSync3(path23, "utf-8");
|
|
3388
3398
|
} catch {
|
|
3389
3399
|
return null;
|
|
3390
3400
|
}
|
|
@@ -3537,15 +3547,15 @@ function checkQuality(dir) {
|
|
|
3537
3547
|
// src/scoring/checks/accuracy.ts
|
|
3538
3548
|
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync } from "fs";
|
|
3539
3549
|
import { join as join4 } from "path";
|
|
3540
|
-
function readFileOrNull3(
|
|
3550
|
+
function readFileOrNull3(path23) {
|
|
3541
3551
|
try {
|
|
3542
|
-
return readFileSync4(
|
|
3552
|
+
return readFileSync4(path23, "utf-8");
|
|
3543
3553
|
} catch {
|
|
3544
3554
|
return null;
|
|
3545
3555
|
}
|
|
3546
3556
|
}
|
|
3547
|
-
function readJsonOrNull2(
|
|
3548
|
-
const content = readFileOrNull3(
|
|
3557
|
+
function readJsonOrNull2(path23) {
|
|
3558
|
+
const content = readFileOrNull3(path23);
|
|
3549
3559
|
if (!content) return null;
|
|
3550
3560
|
try {
|
|
3551
3561
|
return JSON.parse(content);
|
|
@@ -3728,9 +3738,9 @@ function checkAccuracy(dir) {
|
|
|
3728
3738
|
// src/scoring/checks/freshness.ts
|
|
3729
3739
|
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
3730
3740
|
import { join as join5 } from "path";
|
|
3731
|
-
function readFileOrNull4(
|
|
3741
|
+
function readFileOrNull4(path23) {
|
|
3732
3742
|
try {
|
|
3733
|
-
return readFileSync5(
|
|
3743
|
+
return readFileSync5(path23, "utf-8");
|
|
3734
3744
|
} catch {
|
|
3735
3745
|
return null;
|
|
3736
3746
|
}
|
|
@@ -3844,9 +3854,9 @@ function checkFreshness(dir) {
|
|
|
3844
3854
|
import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "fs";
|
|
3845
3855
|
import { execSync as execSync7 } from "child_process";
|
|
3846
3856
|
import { join as join6 } from "path";
|
|
3847
|
-
function readFileOrNull5(
|
|
3857
|
+
function readFileOrNull5(path23) {
|
|
3848
3858
|
try {
|
|
3849
|
-
return readFileSync6(
|
|
3859
|
+
return readFileSync6(path23, "utf-8");
|
|
3850
3860
|
} catch {
|
|
3851
3861
|
return null;
|
|
3852
3862
|
}
|
|
@@ -4143,513 +4153,21 @@ function displayScoreDelta(before, after) {
|
|
|
4143
4153
|
}
|
|
4144
4154
|
}
|
|
4145
4155
|
|
|
4146
|
-
// src/
|
|
4156
|
+
// src/commands/recommend.ts
|
|
4147
4157
|
import chalk5 from "chalk";
|
|
4148
4158
|
import ora from "ora";
|
|
4149
|
-
import readline3 from "readline";
|
|
4150
|
-
import fs20 from "fs";
|
|
4151
|
-
import path16 from "path";
|
|
4152
|
-
|
|
4153
|
-
// src/mcp/search.ts
|
|
4154
|
-
var AWESOME_MCP_URL = "https://raw.githubusercontent.com/punkpeye/awesome-mcp-servers/main/README.md";
|
|
4155
|
-
var GITHUB_SEARCH_URL = "https://github.com/search";
|
|
4156
|
-
var SEARCH_HEADERS = {
|
|
4157
|
-
"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",
|
|
4158
|
-
"Accept": "text/html"
|
|
4159
|
-
};
|
|
4160
|
-
function parseGitHubSearchHtml(html) {
|
|
4161
|
-
try {
|
|
4162
|
-
const scriptMatch = html.match(/<script type="application\/json" data-target="react-app\.embeddedData">([\s\S]*?)<\/script>/);
|
|
4163
|
-
if (!scriptMatch) {
|
|
4164
|
-
return [];
|
|
4165
|
-
}
|
|
4166
|
-
const data = JSON.parse(scriptMatch[1]);
|
|
4167
|
-
const results = data?.payload?.results;
|
|
4168
|
-
if (!Array.isArray(results)) {
|
|
4169
|
-
return [];
|
|
4170
|
-
}
|
|
4171
|
-
return results.map((r) => {
|
|
4172
|
-
const repo = r.repo?.repository;
|
|
4173
|
-
const ownerLogin = repo?.owner_login || "";
|
|
4174
|
-
const name = repo?.name || "";
|
|
4175
|
-
const description = typeof r.hl_trunc_description === "string" ? r.hl_trunc_description.replace(/<\/?em>/g, "") : null;
|
|
4176
|
-
return {
|
|
4177
|
-
full_name: `${ownerLogin}/${name}`,
|
|
4178
|
-
description,
|
|
4179
|
-
stargazers_count: typeof r.followers === "number" ? r.followers : 0,
|
|
4180
|
-
pushed_at: repo?.updated_at || "",
|
|
4181
|
-
owner_login: ownerLogin
|
|
4182
|
-
};
|
|
4183
|
-
});
|
|
4184
|
-
} catch {
|
|
4185
|
-
return [];
|
|
4186
|
-
}
|
|
4187
|
-
}
|
|
4188
|
-
async function searchAllMcpSources(toolDeps) {
|
|
4189
|
-
if (toolDeps.length === 0) return [];
|
|
4190
|
-
const searches = [
|
|
4191
|
-
searchAwesomeMcpLists(toolDeps),
|
|
4192
|
-
...toolDeps.map((dep) => searchGitHub(dep)),
|
|
4193
|
-
...toolDeps.map((dep) => searchVendorOrg(dep))
|
|
4194
|
-
];
|
|
4195
|
-
const results = await Promise.all(searches);
|
|
4196
|
-
const seen = /* @__PURE__ */ new Map();
|
|
4197
|
-
for (const batch of results) {
|
|
4198
|
-
for (const candidate of batch) {
|
|
4199
|
-
const key = candidate.repoFullName.toLowerCase();
|
|
4200
|
-
const existing = seen.get(key);
|
|
4201
|
-
if (!existing || candidate.vendor || candidate.stars > existing.stars) {
|
|
4202
|
-
seen.set(key, candidate);
|
|
4203
|
-
}
|
|
4204
|
-
}
|
|
4205
|
-
}
|
|
4206
|
-
return Array.from(seen.values());
|
|
4207
|
-
}
|
|
4208
|
-
async function searchAwesomeMcpLists(toolDeps) {
|
|
4209
|
-
try {
|
|
4210
|
-
const resp = await fetch(AWESOME_MCP_URL, {
|
|
4211
|
-
signal: AbortSignal.timeout(1e4)
|
|
4212
|
-
});
|
|
4213
|
-
if (!resp.ok) return [];
|
|
4214
|
-
const markdown = await resp.text();
|
|
4215
|
-
const candidates = [];
|
|
4216
|
-
const depLower = toolDeps.map((d) => d.toLowerCase().replace(/^@[^/]+\//, ""));
|
|
4217
|
-
const itemPattern = /^[-*]\s+\[([^\]]+)\]\(([^)]+)\)\s*[-–—:]\s*(.*)/gm;
|
|
4218
|
-
let match;
|
|
4219
|
-
while ((match = itemPattern.exec(markdown)) !== null) {
|
|
4220
|
-
const [, name, url, description] = match;
|
|
4221
|
-
if (!url.includes("github.com")) continue;
|
|
4222
|
-
const text = `${name} ${description}`.toLowerCase();
|
|
4223
|
-
const matchedDep = depLower.find((d) => text.includes(d));
|
|
4224
|
-
if (!matchedDep) continue;
|
|
4225
|
-
const repoMatch = url.match(/github\.com\/([^/]+\/[^/]+)/);
|
|
4226
|
-
if (!repoMatch) continue;
|
|
4227
|
-
candidates.push({
|
|
4228
|
-
name: name.trim(),
|
|
4229
|
-
repoFullName: repoMatch[1],
|
|
4230
|
-
url: url.trim(),
|
|
4231
|
-
description: description.trim().slice(0, 200),
|
|
4232
|
-
stars: 0,
|
|
4233
|
-
lastPush: "",
|
|
4234
|
-
vendor: false,
|
|
4235
|
-
score: 0,
|
|
4236
|
-
reason: "",
|
|
4237
|
-
matchedDep: toolDeps.find((d) => d.toLowerCase().replace(/^@[^/]+\//, "") === matchedDep) || matchedDep
|
|
4238
|
-
});
|
|
4239
|
-
}
|
|
4240
|
-
return candidates;
|
|
4241
|
-
} catch {
|
|
4242
|
-
return [];
|
|
4243
|
-
}
|
|
4244
|
-
}
|
|
4245
|
-
async function searchGitHub(dep) {
|
|
4246
|
-
const depName = dep.replace(/^@[^/]+\//, "");
|
|
4247
|
-
try {
|
|
4248
|
-
const query = encodeURIComponent(`${depName} mcp server`);
|
|
4249
|
-
const url = `${GITHUB_SEARCH_URL}?q=${query}&type=repositories&s=stars&o=desc`;
|
|
4250
|
-
const resp = await fetch(url, {
|
|
4251
|
-
signal: AbortSignal.timeout(1e4),
|
|
4252
|
-
headers: SEARCH_HEADERS
|
|
4253
|
-
});
|
|
4254
|
-
if (!resp.ok) return [];
|
|
4255
|
-
const html = await resp.text();
|
|
4256
|
-
const repos = parseGitHubSearchHtml(html).slice(0, 5);
|
|
4257
|
-
return repos.map((repo) => ({
|
|
4258
|
-
name: repo.full_name.split("/")[1],
|
|
4259
|
-
repoFullName: repo.full_name,
|
|
4260
|
-
url: `https://github.com/${repo.full_name}`,
|
|
4261
|
-
description: repo.description || "",
|
|
4262
|
-
stars: repo.stargazers_count,
|
|
4263
|
-
lastPush: repo.pushed_at,
|
|
4264
|
-
vendor: false,
|
|
4265
|
-
score: 0,
|
|
4266
|
-
reason: "",
|
|
4267
|
-
matchedDep: dep
|
|
4268
|
-
}));
|
|
4269
|
-
} catch {
|
|
4270
|
-
return [];
|
|
4271
|
-
}
|
|
4272
|
-
}
|
|
4273
|
-
async function searchVendorOrg(dep) {
|
|
4274
|
-
const depName = dep.replace(/^@([^/]+)\/.*/, "$1").replace(/^@/, "");
|
|
4275
|
-
const orgName = depName.toLowerCase();
|
|
4276
|
-
try {
|
|
4277
|
-
const query = encodeURIComponent(`mcp org:${orgName}`);
|
|
4278
|
-
const url = `${GITHUB_SEARCH_URL}?q=${query}&type=repositories&s=stars&o=desc`;
|
|
4279
|
-
const resp = await fetch(url, {
|
|
4280
|
-
signal: AbortSignal.timeout(1e4),
|
|
4281
|
-
headers: SEARCH_HEADERS
|
|
4282
|
-
});
|
|
4283
|
-
if (!resp.ok) return [];
|
|
4284
|
-
const html = await resp.text();
|
|
4285
|
-
const repos = parseGitHubSearchHtml(html).slice(0, 5);
|
|
4286
|
-
return repos.map((repo) => ({
|
|
4287
|
-
name: repo.full_name.split("/")[1],
|
|
4288
|
-
repoFullName: repo.full_name,
|
|
4289
|
-
url: `https://github.com/${repo.full_name}`,
|
|
4290
|
-
description: repo.description || "",
|
|
4291
|
-
stars: repo.stargazers_count,
|
|
4292
|
-
lastPush: repo.pushed_at,
|
|
4293
|
-
vendor: repo.owner_login.toLowerCase() === orgName,
|
|
4294
|
-
score: 0,
|
|
4295
|
-
reason: "",
|
|
4296
|
-
matchedDep: dep
|
|
4297
|
-
}));
|
|
4298
|
-
} catch {
|
|
4299
|
-
return [];
|
|
4300
|
-
}
|
|
4301
|
-
}
|
|
4302
|
-
|
|
4303
|
-
// src/mcp/prompts.ts
|
|
4304
|
-
var SCORE_MCP_PROMPT = `You evaluate MCP (Model Context Protocol) server candidates for relevance to a software project.
|
|
4305
|
-
|
|
4306
|
-
Score each candidate from 0-100 based on:
|
|
4307
|
-
- **Relevance** (40%): How directly does it match the project's detected tool dependencies?
|
|
4308
|
-
- **Capabilities** (30%): Does it provide meaningful read+write operations (not just read-only)?
|
|
4309
|
-
- **Quality signals** (30%): Stars, recent activity, vendor/official status
|
|
4310
|
-
|
|
4311
|
-
Return a JSON array where each element has:
|
|
4312
|
-
- "index": the candidate's index number
|
|
4313
|
-
- "score": relevance score 0-100
|
|
4314
|
-
- "reason": one-liner explaining the score (max 80 chars)
|
|
4315
|
-
|
|
4316
|
-
Be selective. Only score candidates that would genuinely help developers working on this project.
|
|
4317
|
-
Return ONLY the JSON array.`;
|
|
4318
|
-
var EXTRACT_CONFIG_PROMPT = `You extract MCP server configuration from a README file.
|
|
4319
|
-
|
|
4320
|
-
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.
|
|
4321
|
-
|
|
4322
|
-
Return a JSON object with:
|
|
4323
|
-
- "command": the executable command (e.g., "npx", "uvx", "node", "docker")
|
|
4324
|
-
- "args": array of arguments (e.g., ["-y", "@supabase/mcp-server"])
|
|
4325
|
-
- "env": array of objects, each with:
|
|
4326
|
-
- "key": environment variable name (e.g., "SUPABASE_ACCESS_TOKEN")
|
|
4327
|
-
- "description": brief description of what this value is (e.g., "Personal access token from dashboard")
|
|
4328
|
-
- "required": boolean
|
|
4329
|
-
|
|
4330
|
-
If the README shows multiple configuration methods, prefer npx > uvx > node > docker.
|
|
4331
|
-
If you cannot determine the configuration, return {"command": "", "args": [], "env": []}.
|
|
4332
|
-
Return ONLY the JSON object.`;
|
|
4333
|
-
|
|
4334
|
-
// src/mcp/validate.ts
|
|
4335
|
-
var MIN_STARS = 100;
|
|
4336
|
-
var MAX_AGE_DAYS = 180;
|
|
4337
|
-
async function validateAndScore(candidates, toolDeps) {
|
|
4338
|
-
const qualityFiltered = candidates.filter((c) => {
|
|
4339
|
-
if (c.vendor) return true;
|
|
4340
|
-
if (c.stars > 0 && c.stars < MIN_STARS) return false;
|
|
4341
|
-
if (c.lastPush) {
|
|
4342
|
-
const pushDate = new Date(c.lastPush);
|
|
4343
|
-
const daysAgo = (Date.now() - pushDate.getTime()) / (1e3 * 60 * 60 * 24);
|
|
4344
|
-
if (daysAgo > MAX_AGE_DAYS) return false;
|
|
4345
|
-
}
|
|
4346
|
-
return true;
|
|
4347
|
-
});
|
|
4348
|
-
if (qualityFiltered.length === 0) return [];
|
|
4349
|
-
try {
|
|
4350
|
-
return await scoreWithLLM(qualityFiltered, toolDeps);
|
|
4351
|
-
} catch {
|
|
4352
|
-
return qualityFiltered.slice(0, 5).map((c) => ({
|
|
4353
|
-
...c,
|
|
4354
|
-
score: 50,
|
|
4355
|
-
reason: c.description.slice(0, 80)
|
|
4356
|
-
}));
|
|
4357
|
-
}
|
|
4358
|
-
}
|
|
4359
|
-
async function scoreWithLLM(candidates, toolDeps) {
|
|
4360
|
-
const candidateList = candidates.map((c, i) => {
|
|
4361
|
-
const vendorTag = c.vendor ? " [VENDOR/OFFICIAL]" : "";
|
|
4362
|
-
return `${i}. "${c.name}"${vendorTag} (${c.stars} stars) \u2014 ${c.description.slice(0, 100)}`;
|
|
4363
|
-
}).join("\n");
|
|
4364
|
-
const scored = await llmJsonCall({
|
|
4365
|
-
system: SCORE_MCP_PROMPT,
|
|
4366
|
-
prompt: `TOOL DEPENDENCIES IN PROJECT:
|
|
4367
|
-
${toolDeps.join(", ")}
|
|
4368
|
-
|
|
4369
|
-
MCP SERVER CANDIDATES:
|
|
4370
|
-
${candidateList}`,
|
|
4371
|
-
maxTokens: 4e3
|
|
4372
|
-
});
|
|
4373
|
-
if (!Array.isArray(scored)) return [];
|
|
4374
|
-
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) => ({
|
|
4375
|
-
...candidates[s.index],
|
|
4376
|
-
score: s.score,
|
|
4377
|
-
reason: s.reason || candidates[s.index].description.slice(0, 80)
|
|
4378
|
-
}));
|
|
4379
|
-
}
|
|
4380
|
-
|
|
4381
|
-
// src/mcp/config-extract.ts
|
|
4382
|
-
async function fetchReadme(repoFullName) {
|
|
4383
|
-
try {
|
|
4384
|
-
const resp = await fetch(
|
|
4385
|
-
`https://raw.githubusercontent.com/${repoFullName}/HEAD/README.md`,
|
|
4386
|
-
{ signal: AbortSignal.timeout(1e4) }
|
|
4387
|
-
);
|
|
4388
|
-
if (resp.ok) return await resp.text();
|
|
4389
|
-
} catch {
|
|
4390
|
-
}
|
|
4391
|
-
try {
|
|
4392
|
-
const resp = await fetch(
|
|
4393
|
-
`https://raw.githubusercontent.com/${repoFullName}/main/README.md`,
|
|
4394
|
-
{ signal: AbortSignal.timeout(1e4) }
|
|
4395
|
-
);
|
|
4396
|
-
if (resp.ok) return await resp.text();
|
|
4397
|
-
} catch {
|
|
4398
|
-
}
|
|
4399
|
-
return null;
|
|
4400
|
-
}
|
|
4401
|
-
async function extractMcpConfig(readme, serverName) {
|
|
4402
|
-
try {
|
|
4403
|
-
const truncated = readme.length > 15e3 ? readme.slice(0, 15e3) : readme;
|
|
4404
|
-
const result = await llmJsonCall({
|
|
4405
|
-
system: EXTRACT_CONFIG_PROMPT,
|
|
4406
|
-
prompt: `MCP Server: ${serverName}
|
|
4407
|
-
|
|
4408
|
-
README:
|
|
4409
|
-
${truncated}`,
|
|
4410
|
-
maxTokens: 2e3
|
|
4411
|
-
});
|
|
4412
|
-
if (!result || !result.command) return null;
|
|
4413
|
-
return {
|
|
4414
|
-
command: result.command,
|
|
4415
|
-
args: Array.isArray(result.args) ? result.args : [],
|
|
4416
|
-
env: Array.isArray(result.env) ? result.env : []
|
|
4417
|
-
};
|
|
4418
|
-
} catch {
|
|
4419
|
-
return null;
|
|
4420
|
-
}
|
|
4421
|
-
}
|
|
4422
|
-
|
|
4423
|
-
// src/mcp/index.ts
|
|
4424
|
-
async function discoverAndInstallMcps(targetAgent, fingerprint, dir) {
|
|
4425
|
-
console.log(chalk5.hex("#6366f1").bold("\n MCP Server Discovery\n"));
|
|
4426
|
-
const toolDeps = fingerprint.tools;
|
|
4427
|
-
if (toolDeps.length === 0) {
|
|
4428
|
-
console.log(chalk5.dim(" No external tools or services detected \u2014 skipping MCP discovery"));
|
|
4429
|
-
return { installed: 0, names: [] };
|
|
4430
|
-
}
|
|
4431
|
-
const spinner = ora(`Searching MCP servers for ${toolDeps.length} detected tool${toolDeps.length === 1 ? "" : "s"}...`).start();
|
|
4432
|
-
console.log(chalk5.dim(` Detected: ${toolDeps.join(", ")}`));
|
|
4433
|
-
const existingMcps = getExistingMcpNames(fingerprint, targetAgent);
|
|
4434
|
-
const filteredDeps = toolDeps.filter((d) => {
|
|
4435
|
-
const lower = d.toLowerCase();
|
|
4436
|
-
return !existingMcps.some((name) => name.includes(lower) || lower.includes(name));
|
|
4437
|
-
});
|
|
4438
|
-
if (filteredDeps.length === 0) {
|
|
4439
|
-
spinner.succeed(chalk5.dim("All detected tools already have MCP servers configured"));
|
|
4440
|
-
return { installed: 0, names: [] };
|
|
4441
|
-
}
|
|
4442
|
-
const candidates = await searchAllMcpSources(filteredDeps);
|
|
4443
|
-
if (candidates.length === 0) {
|
|
4444
|
-
spinner.succeed(chalk5.dim("No MCP servers found for detected tools"));
|
|
4445
|
-
return { installed: 0, names: [] };
|
|
4446
|
-
}
|
|
4447
|
-
spinner.succeed(`Found ${candidates.length} candidate${candidates.length === 1 ? "" : "s"} for ${filteredDeps.join(", ")}`);
|
|
4448
|
-
const scoreSpinner = ora("Scoring MCP candidates...").start();
|
|
4449
|
-
const scored = await validateAndScore(candidates, filteredDeps);
|
|
4450
|
-
if (scored.length === 0) {
|
|
4451
|
-
scoreSpinner.succeed(chalk5.dim("No quality MCP servers passed validation"));
|
|
4452
|
-
console.log(chalk5.dim(` Candidates checked: ${candidates.map((c) => c.name).join(", ")}`));
|
|
4453
|
-
return { installed: 0, names: [] };
|
|
4454
|
-
}
|
|
4455
|
-
scoreSpinner.succeed(`${scored.length} quality MCP server${scored.length === 1 ? "" : "s"} found`);
|
|
4456
|
-
console.log(chalk5.dim(` Scored: ${scored.map((c) => `${c.name} (${c.score})`).join(", ")}`));
|
|
4457
|
-
const selected = await interactiveSelect(scored);
|
|
4458
|
-
if (!selected || selected.length === 0) {
|
|
4459
|
-
return { installed: 0, names: [] };
|
|
4460
|
-
}
|
|
4461
|
-
const mcpServers = {};
|
|
4462
|
-
const installedNames = [];
|
|
4463
|
-
for (const mcp of selected) {
|
|
4464
|
-
console.log(chalk5.bold(`
|
|
4465
|
-
Configuring ${mcp.name}...`));
|
|
4466
|
-
const readme = await fetchReadme(mcp.repoFullName);
|
|
4467
|
-
if (!readme) {
|
|
4468
|
-
console.log(chalk5.yellow(` Could not fetch README for ${mcp.repoFullName} \u2014 skipping`));
|
|
4469
|
-
console.log(chalk5.dim(` Manual setup: ${mcp.url}`));
|
|
4470
|
-
continue;
|
|
4471
|
-
}
|
|
4472
|
-
const config = await extractMcpConfig(readme, mcp.name);
|
|
4473
|
-
if (!config || !config.command) {
|
|
4474
|
-
console.log(chalk5.yellow(` Could not extract config for ${mcp.name} \u2014 skipping`));
|
|
4475
|
-
console.log(chalk5.dim(` Manual setup: ${mcp.url}`));
|
|
4476
|
-
continue;
|
|
4477
|
-
}
|
|
4478
|
-
const env = {};
|
|
4479
|
-
for (const envVar of config.env) {
|
|
4480
|
-
if (!envVar.required) continue;
|
|
4481
|
-
const value = await promptInput2(` ? ${envVar.key} (${envVar.description})`);
|
|
4482
|
-
if (value) {
|
|
4483
|
-
env[envVar.key] = value;
|
|
4484
|
-
}
|
|
4485
|
-
}
|
|
4486
|
-
const serverConfig = {
|
|
4487
|
-
command: config.command
|
|
4488
|
-
};
|
|
4489
|
-
if (config.args.length > 0) serverConfig.args = config.args;
|
|
4490
|
-
if (Object.keys(env).length > 0) serverConfig.env = env;
|
|
4491
|
-
mcpServers[mcp.name] = serverConfig;
|
|
4492
|
-
installedNames.push(mcp.name);
|
|
4493
|
-
console.log(` ${chalk5.green("\u2713")} ${mcp.name} configured`);
|
|
4494
|
-
}
|
|
4495
|
-
if (installedNames.length === 0) {
|
|
4496
|
-
return { installed: 0, names: [] };
|
|
4497
|
-
}
|
|
4498
|
-
if (targetAgent.includes("claude") || targetAgent.includes("codex")) {
|
|
4499
|
-
writeMcpJson(path16.join(dir, ".mcp.json"), mcpServers);
|
|
4500
|
-
}
|
|
4501
|
-
if (targetAgent.includes("cursor")) {
|
|
4502
|
-
const cursorDir = path16.join(dir, ".cursor");
|
|
4503
|
-
if (!fs20.existsSync(cursorDir)) fs20.mkdirSync(cursorDir, { recursive: true });
|
|
4504
|
-
writeMcpJson(path16.join(cursorDir, "mcp.json"), mcpServers);
|
|
4505
|
-
}
|
|
4506
|
-
return { installed: installedNames.length, names: installedNames };
|
|
4507
|
-
}
|
|
4508
|
-
function writeMcpJson(filePath, mcpServers) {
|
|
4509
|
-
let existing = {};
|
|
4510
|
-
try {
|
|
4511
|
-
if (fs20.existsSync(filePath)) {
|
|
4512
|
-
const parsed = JSON.parse(fs20.readFileSync(filePath, "utf-8"));
|
|
4513
|
-
if (parsed.mcpServers) existing = parsed.mcpServers;
|
|
4514
|
-
}
|
|
4515
|
-
} catch {
|
|
4516
|
-
}
|
|
4517
|
-
const merged = { ...existing, ...mcpServers };
|
|
4518
|
-
fs20.writeFileSync(filePath, JSON.stringify({ mcpServers: merged }, null, 2) + "\n");
|
|
4519
|
-
}
|
|
4520
|
-
function getExistingMcpNames(fingerprint, targetAgent) {
|
|
4521
|
-
const names = [];
|
|
4522
|
-
if (targetAgent.includes("claude")) {
|
|
4523
|
-
if (fingerprint.existingConfigs.claudeMcpServers) {
|
|
4524
|
-
names.push(...Object.keys(fingerprint.existingConfigs.claudeMcpServers).map((k) => k.toLowerCase()));
|
|
4525
|
-
}
|
|
4526
|
-
}
|
|
4527
|
-
if (targetAgent.includes("cursor")) {
|
|
4528
|
-
if (fingerprint.existingConfigs.cursorMcpServers) {
|
|
4529
|
-
names.push(...Object.keys(fingerprint.existingConfigs.cursorMcpServers).map((k) => k.toLowerCase()));
|
|
4530
|
-
}
|
|
4531
|
-
}
|
|
4532
|
-
return names;
|
|
4533
|
-
}
|
|
4534
|
-
function promptInput2(question) {
|
|
4535
|
-
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
4536
|
-
return new Promise((resolve2) => {
|
|
4537
|
-
rl.question(chalk5.cyan(`${question}: `), (answer) => {
|
|
4538
|
-
rl.close();
|
|
4539
|
-
resolve2(answer.trim());
|
|
4540
|
-
});
|
|
4541
|
-
});
|
|
4542
|
-
}
|
|
4543
|
-
async function interactiveSelect(candidates) {
|
|
4544
|
-
if (!process.stdin.isTTY) {
|
|
4545
|
-
console.log(chalk5.bold("\n Available MCP servers:\n"));
|
|
4546
|
-
for (const c of candidates) {
|
|
4547
|
-
const vendorTag = c.vendor ? chalk5.blue(" (vendor)") : "";
|
|
4548
|
-
console.log(` ${String(c.score).padStart(3)} ${c.name}${vendorTag} ${chalk5.dim(c.reason)}`);
|
|
4549
|
-
}
|
|
4550
|
-
console.log("");
|
|
4551
|
-
return null;
|
|
4552
|
-
}
|
|
4553
|
-
const selected = /* @__PURE__ */ new Set();
|
|
4554
|
-
let cursor = 0;
|
|
4555
|
-
const { stdin, stdout } = process;
|
|
4556
|
-
let lineCount = 0;
|
|
4557
|
-
function render() {
|
|
4558
|
-
const lines = [];
|
|
4559
|
-
lines.push(chalk5.bold(" Select MCP servers to install:"));
|
|
4560
|
-
lines.push("");
|
|
4561
|
-
for (let i = 0; i < candidates.length; i++) {
|
|
4562
|
-
const c = candidates[i];
|
|
4563
|
-
const check = selected.has(i) ? chalk5.green("[x]") : "[ ]";
|
|
4564
|
-
const ptr = i === cursor ? chalk5.cyan(">") : " ";
|
|
4565
|
-
const scoreColor = c.score >= 90 ? chalk5.green : c.score >= 70 ? chalk5.yellow : chalk5.dim;
|
|
4566
|
-
const vendorTag = c.vendor ? chalk5.blue(" (vendor)") : "";
|
|
4567
|
-
lines.push(` ${ptr} ${check} ${scoreColor(String(c.score).padStart(3))} ${c.name}${vendorTag} ${chalk5.dim(c.reason.slice(0, 40))}`);
|
|
4568
|
-
}
|
|
4569
|
-
lines.push("");
|
|
4570
|
-
lines.push(chalk5.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q skip"));
|
|
4571
|
-
return lines.join("\n");
|
|
4572
|
-
}
|
|
4573
|
-
function draw(initial) {
|
|
4574
|
-
if (!initial && lineCount > 0) {
|
|
4575
|
-
stdout.write(`\x1B[${lineCount}A`);
|
|
4576
|
-
}
|
|
4577
|
-
stdout.write("\x1B[0J");
|
|
4578
|
-
const output = render();
|
|
4579
|
-
stdout.write(output + "\n");
|
|
4580
|
-
lineCount = output.split("\n").length;
|
|
4581
|
-
}
|
|
4582
|
-
return new Promise((resolve2) => {
|
|
4583
|
-
console.log("");
|
|
4584
|
-
draw(true);
|
|
4585
|
-
stdin.setRawMode(true);
|
|
4586
|
-
stdin.resume();
|
|
4587
|
-
stdin.setEncoding("utf8");
|
|
4588
|
-
function cleanup() {
|
|
4589
|
-
stdin.removeListener("data", onData);
|
|
4590
|
-
stdin.setRawMode(false);
|
|
4591
|
-
stdin.pause();
|
|
4592
|
-
}
|
|
4593
|
-
function onData(key) {
|
|
4594
|
-
switch (key) {
|
|
4595
|
-
case "\x1B[A":
|
|
4596
|
-
cursor = (cursor - 1 + candidates.length) % candidates.length;
|
|
4597
|
-
draw(false);
|
|
4598
|
-
break;
|
|
4599
|
-
case "\x1B[B":
|
|
4600
|
-
cursor = (cursor + 1) % candidates.length;
|
|
4601
|
-
draw(false);
|
|
4602
|
-
break;
|
|
4603
|
-
case " ":
|
|
4604
|
-
selected.has(cursor) ? selected.delete(cursor) : selected.add(cursor);
|
|
4605
|
-
draw(false);
|
|
4606
|
-
break;
|
|
4607
|
-
case "a":
|
|
4608
|
-
candidates.forEach((_, i) => selected.add(i));
|
|
4609
|
-
draw(false);
|
|
4610
|
-
break;
|
|
4611
|
-
case "n":
|
|
4612
|
-
selected.clear();
|
|
4613
|
-
draw(false);
|
|
4614
|
-
break;
|
|
4615
|
-
case "\r":
|
|
4616
|
-
case "\n":
|
|
4617
|
-
cleanup();
|
|
4618
|
-
if (selected.size === 0) {
|
|
4619
|
-
console.log(chalk5.dim("\n No MCP servers selected.\n"));
|
|
4620
|
-
resolve2(null);
|
|
4621
|
-
} else {
|
|
4622
|
-
resolve2(Array.from(selected).sort().map((i) => candidates[i]));
|
|
4623
|
-
}
|
|
4624
|
-
break;
|
|
4625
|
-
case "q":
|
|
4626
|
-
case "\x1B":
|
|
4627
|
-
case "":
|
|
4628
|
-
cleanup();
|
|
4629
|
-
console.log(chalk5.dim("\n Skipped MCP server installation.\n"));
|
|
4630
|
-
resolve2(null);
|
|
4631
|
-
break;
|
|
4632
|
-
}
|
|
4633
|
-
}
|
|
4634
|
-
stdin.on("data", onData);
|
|
4635
|
-
});
|
|
4636
|
-
}
|
|
4637
|
-
|
|
4638
|
-
// src/commands/recommend.ts
|
|
4639
|
-
import chalk6 from "chalk";
|
|
4640
|
-
import ora2 from "ora";
|
|
4641
4159
|
import select3 from "@inquirer/select";
|
|
4642
4160
|
import { mkdirSync, readFileSync as readFileSync7, readdirSync as readdirSync5, existsSync as existsSync9, writeFileSync } from "fs";
|
|
4643
4161
|
import { join as join8, dirname as dirname2 } from "path";
|
|
4644
4162
|
|
|
4645
4163
|
// src/scanner/index.ts
|
|
4646
|
-
import
|
|
4647
|
-
import
|
|
4164
|
+
import fs20 from "fs";
|
|
4165
|
+
import path16 from "path";
|
|
4648
4166
|
import crypto2 from "crypto";
|
|
4649
4167
|
function scanLocalState(dir) {
|
|
4650
4168
|
const items = [];
|
|
4651
|
-
const claudeMdPath =
|
|
4652
|
-
if (
|
|
4169
|
+
const claudeMdPath = path16.join(dir, "CLAUDE.md");
|
|
4170
|
+
if (fs20.existsSync(claudeMdPath)) {
|
|
4653
4171
|
items.push({
|
|
4654
4172
|
type: "rule",
|
|
4655
4173
|
platform: "claude",
|
|
@@ -4658,10 +4176,10 @@ function scanLocalState(dir) {
|
|
|
4658
4176
|
path: claudeMdPath
|
|
4659
4177
|
});
|
|
4660
4178
|
}
|
|
4661
|
-
const skillsDir =
|
|
4662
|
-
if (
|
|
4663
|
-
for (const file of
|
|
4664
|
-
const filePath =
|
|
4179
|
+
const skillsDir = path16.join(dir, ".claude", "skills");
|
|
4180
|
+
if (fs20.existsSync(skillsDir)) {
|
|
4181
|
+
for (const file of fs20.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
4182
|
+
const filePath = path16.join(skillsDir, file);
|
|
4665
4183
|
items.push({
|
|
4666
4184
|
type: "skill",
|
|
4667
4185
|
platform: "claude",
|
|
@@ -4671,10 +4189,10 @@ function scanLocalState(dir) {
|
|
|
4671
4189
|
});
|
|
4672
4190
|
}
|
|
4673
4191
|
}
|
|
4674
|
-
const mcpJsonPath =
|
|
4675
|
-
if (
|
|
4192
|
+
const mcpJsonPath = path16.join(dir, ".mcp.json");
|
|
4193
|
+
if (fs20.existsSync(mcpJsonPath)) {
|
|
4676
4194
|
try {
|
|
4677
|
-
const mcpJson = JSON.parse(
|
|
4195
|
+
const mcpJson = JSON.parse(fs20.readFileSync(mcpJsonPath, "utf-8"));
|
|
4678
4196
|
if (mcpJson.mcpServers) {
|
|
4679
4197
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
4680
4198
|
items.push({
|
|
@@ -4689,8 +4207,8 @@ function scanLocalState(dir) {
|
|
|
4689
4207
|
} catch {
|
|
4690
4208
|
}
|
|
4691
4209
|
}
|
|
4692
|
-
const agentsMdPath =
|
|
4693
|
-
if (
|
|
4210
|
+
const agentsMdPath = path16.join(dir, "AGENTS.md");
|
|
4211
|
+
if (fs20.existsSync(agentsMdPath)) {
|
|
4694
4212
|
items.push({
|
|
4695
4213
|
type: "rule",
|
|
4696
4214
|
platform: "codex",
|
|
@@ -4699,12 +4217,12 @@ function scanLocalState(dir) {
|
|
|
4699
4217
|
path: agentsMdPath
|
|
4700
4218
|
});
|
|
4701
4219
|
}
|
|
4702
|
-
const codexSkillsDir =
|
|
4703
|
-
if (
|
|
4220
|
+
const codexSkillsDir = path16.join(dir, ".agents", "skills");
|
|
4221
|
+
if (fs20.existsSync(codexSkillsDir)) {
|
|
4704
4222
|
try {
|
|
4705
|
-
for (const name of
|
|
4706
|
-
const skillFile =
|
|
4707
|
-
if (
|
|
4223
|
+
for (const name of fs20.readdirSync(codexSkillsDir)) {
|
|
4224
|
+
const skillFile = path16.join(codexSkillsDir, name, "SKILL.md");
|
|
4225
|
+
if (fs20.existsSync(skillFile)) {
|
|
4708
4226
|
items.push({
|
|
4709
4227
|
type: "skill",
|
|
4710
4228
|
platform: "codex",
|
|
@@ -4717,8 +4235,8 @@ function scanLocalState(dir) {
|
|
|
4717
4235
|
} catch {
|
|
4718
4236
|
}
|
|
4719
4237
|
}
|
|
4720
|
-
const cursorrulesPath =
|
|
4721
|
-
if (
|
|
4238
|
+
const cursorrulesPath = path16.join(dir, ".cursorrules");
|
|
4239
|
+
if (fs20.existsSync(cursorrulesPath)) {
|
|
4722
4240
|
items.push({
|
|
4723
4241
|
type: "rule",
|
|
4724
4242
|
platform: "cursor",
|
|
@@ -4727,10 +4245,10 @@ function scanLocalState(dir) {
|
|
|
4727
4245
|
path: cursorrulesPath
|
|
4728
4246
|
});
|
|
4729
4247
|
}
|
|
4730
|
-
const cursorRulesDir =
|
|
4731
|
-
if (
|
|
4732
|
-
for (const file of
|
|
4733
|
-
const filePath =
|
|
4248
|
+
const cursorRulesDir = path16.join(dir, ".cursor", "rules");
|
|
4249
|
+
if (fs20.existsSync(cursorRulesDir)) {
|
|
4250
|
+
for (const file of fs20.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
4251
|
+
const filePath = path16.join(cursorRulesDir, file);
|
|
4734
4252
|
items.push({
|
|
4735
4253
|
type: "rule",
|
|
4736
4254
|
platform: "cursor",
|
|
@@ -4740,12 +4258,12 @@ function scanLocalState(dir) {
|
|
|
4740
4258
|
});
|
|
4741
4259
|
}
|
|
4742
4260
|
}
|
|
4743
|
-
const cursorSkillsDir =
|
|
4744
|
-
if (
|
|
4261
|
+
const cursorSkillsDir = path16.join(dir, ".cursor", "skills");
|
|
4262
|
+
if (fs20.existsSync(cursorSkillsDir)) {
|
|
4745
4263
|
try {
|
|
4746
|
-
for (const name of
|
|
4747
|
-
const skillFile =
|
|
4748
|
-
if (
|
|
4264
|
+
for (const name of fs20.readdirSync(cursorSkillsDir)) {
|
|
4265
|
+
const skillFile = path16.join(cursorSkillsDir, name, "SKILL.md");
|
|
4266
|
+
if (fs20.existsSync(skillFile)) {
|
|
4749
4267
|
items.push({
|
|
4750
4268
|
type: "skill",
|
|
4751
4269
|
platform: "cursor",
|
|
@@ -4758,10 +4276,10 @@ function scanLocalState(dir) {
|
|
|
4758
4276
|
} catch {
|
|
4759
4277
|
}
|
|
4760
4278
|
}
|
|
4761
|
-
const cursorMcpPath =
|
|
4762
|
-
if (
|
|
4279
|
+
const cursorMcpPath = path16.join(dir, ".cursor", "mcp.json");
|
|
4280
|
+
if (fs20.existsSync(cursorMcpPath)) {
|
|
4763
4281
|
try {
|
|
4764
|
-
const mcpJson = JSON.parse(
|
|
4282
|
+
const mcpJson = JSON.parse(fs20.readFileSync(cursorMcpPath, "utf-8"));
|
|
4765
4283
|
if (mcpJson.mcpServers) {
|
|
4766
4284
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
4767
4285
|
items.push({
|
|
@@ -4779,7 +4297,7 @@ function scanLocalState(dir) {
|
|
|
4779
4297
|
return items;
|
|
4780
4298
|
}
|
|
4781
4299
|
function hashFile(filePath) {
|
|
4782
|
-
const text =
|
|
4300
|
+
const text = fs20.readFileSync(filePath, "utf-8");
|
|
4783
4301
|
return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
|
|
4784
4302
|
}
|
|
4785
4303
|
function hashJson(obj) {
|
|
@@ -4941,8 +4459,9 @@ async function searchAllProviders(technologies, platform) {
|
|
|
4941
4459
|
}
|
|
4942
4460
|
return combined;
|
|
4943
4461
|
}
|
|
4944
|
-
async function
|
|
4462
|
+
async function scoreWithLLM(candidates, projectContext, technologies) {
|
|
4945
4463
|
const candidateList = candidates.map((c, i) => `${i}. "${c.name}" \u2014 ${c.reason || "no description"}`).join("\n");
|
|
4464
|
+
const fastModel = getFastModel();
|
|
4946
4465
|
const scored = await llmJsonCall({
|
|
4947
4466
|
system: `You evaluate whether AI agent skills and tools are relevant to a specific software project.
|
|
4948
4467
|
Given a project context and a list of candidates, score each one's relevance from 0-100 and provide a brief reason (max 80 chars).
|
|
@@ -4970,7 +4489,8 @@ ${technologies.join(", ")}
|
|
|
4970
4489
|
|
|
4971
4490
|
CANDIDATES:
|
|
4972
4491
|
${candidateList}`,
|
|
4973
|
-
maxTokens: 8e3
|
|
4492
|
+
maxTokens: 8e3,
|
|
4493
|
+
...fastModel ? { model: fastModel } : {}
|
|
4974
4494
|
});
|
|
4975
4495
|
if (!Array.isArray(scored)) return [];
|
|
4976
4496
|
return scored.filter((s) => s.score >= 60 && s.index >= 0 && s.index < candidates.length).sort((a, b) => b.score - a.score).slice(0, 20).map((s) => ({
|
|
@@ -5092,7 +4612,7 @@ async function recommendCommand() {
|
|
|
5092
4612
|
]
|
|
5093
4613
|
});
|
|
5094
4614
|
if (!proceed) {
|
|
5095
|
-
console.log(
|
|
4615
|
+
console.log(chalk5.dim(" Cancelled.\n"));
|
|
5096
4616
|
return;
|
|
5097
4617
|
}
|
|
5098
4618
|
await searchAndInstallSkills();
|
|
@@ -5107,11 +4627,11 @@ async function searchAndInstallSkills() {
|
|
|
5107
4627
|
...extractTopDeps()
|
|
5108
4628
|
].filter(Boolean))];
|
|
5109
4629
|
if (technologies.length === 0) {
|
|
5110
|
-
console.log(
|
|
4630
|
+
console.log(chalk5.yellow("Could not detect any languages or dependencies. Try running from a project root."));
|
|
5111
4631
|
throw new Error("__exit__");
|
|
5112
4632
|
}
|
|
5113
4633
|
const primaryPlatform = platforms.includes("claude") ? "claude" : platforms[0];
|
|
5114
|
-
const searchSpinner =
|
|
4634
|
+
const searchSpinner = ora("Searching skill registries...").start();
|
|
5115
4635
|
const allCandidates = await searchAllProviders(technologies, primaryPlatform);
|
|
5116
4636
|
if (!allCandidates.length) {
|
|
5117
4637
|
searchSpinner.succeed("No skills found matching your tech stack.");
|
|
@@ -5124,15 +4644,15 @@ async function searchAndInstallSkills() {
|
|
|
5124
4644
|
return;
|
|
5125
4645
|
}
|
|
5126
4646
|
searchSpinner.succeed(
|
|
5127
|
-
`Found ${allCandidates.length} skills` + (filteredCount > 0 ?
|
|
4647
|
+
`Found ${allCandidates.length} skills` + (filteredCount > 0 ? chalk5.dim(` (${filteredCount} already installed)`) : "")
|
|
5128
4648
|
);
|
|
5129
4649
|
let results;
|
|
5130
4650
|
const config = loadConfig();
|
|
5131
4651
|
if (config) {
|
|
5132
|
-
const scoreSpinner =
|
|
4652
|
+
const scoreSpinner = ora("Scoring relevance for your project...").start();
|
|
5133
4653
|
try {
|
|
5134
4654
|
const projectContext = buildProjectContext(fingerprint);
|
|
5135
|
-
results = await
|
|
4655
|
+
results = await scoreWithLLM(newCandidates, projectContext, technologies);
|
|
5136
4656
|
if (results.length === 0) {
|
|
5137
4657
|
scoreSpinner.succeed("No highly relevant skills found for your specific project.");
|
|
5138
4658
|
return;
|
|
@@ -5145,7 +4665,7 @@ async function searchAndInstallSkills() {
|
|
|
5145
4665
|
} else {
|
|
5146
4666
|
results = newCandidates.slice(0, 20);
|
|
5147
4667
|
}
|
|
5148
|
-
const fetchSpinner =
|
|
4668
|
+
const fetchSpinner = ora("Verifying skill availability...").start();
|
|
5149
4669
|
const contentMap = /* @__PURE__ */ new Map();
|
|
5150
4670
|
await Promise.all(results.map(async (rec) => {
|
|
5151
4671
|
const content = await fetchSkillContent(rec);
|
|
@@ -5158,14 +4678,14 @@ async function searchAndInstallSkills() {
|
|
|
5158
4678
|
}
|
|
5159
4679
|
const unavailableCount = results.length - available.length;
|
|
5160
4680
|
fetchSpinner.succeed(
|
|
5161
|
-
`${available.length} installable skill${available.length > 1 ? "s" : ""}` + (unavailableCount > 0 ?
|
|
4681
|
+
`${available.length} installable skill${available.length > 1 ? "s" : ""}` + (unavailableCount > 0 ? chalk5.dim(` (${unavailableCount} unavailable)`) : "")
|
|
5162
4682
|
);
|
|
5163
|
-
const selected = await
|
|
4683
|
+
const selected = await interactiveSelect(available);
|
|
5164
4684
|
if (selected?.length) {
|
|
5165
4685
|
await installSkills(selected, platforms, contentMap);
|
|
5166
4686
|
}
|
|
5167
4687
|
}
|
|
5168
|
-
async function
|
|
4688
|
+
async function interactiveSelect(recs) {
|
|
5169
4689
|
if (!process.stdin.isTTY) {
|
|
5170
4690
|
printSkills(recs);
|
|
5171
4691
|
return null;
|
|
@@ -5181,30 +4701,30 @@ async function interactiveSelect2(recs) {
|
|
|
5181
4701
|
const nameWidth = Math.max(...recs.map((r) => r.name.length), 4) + 2;
|
|
5182
4702
|
const prefixWidth = 8;
|
|
5183
4703
|
const scoreWidth = 6;
|
|
5184
|
-
lines.push(
|
|
4704
|
+
lines.push(chalk5.bold(" Skills"));
|
|
5185
4705
|
lines.push("");
|
|
5186
4706
|
if (hasScores) {
|
|
5187
|
-
const header = " ".repeat(prefixWidth) +
|
|
4707
|
+
const header = " ".repeat(prefixWidth) + chalk5.dim("Score".padEnd(scoreWidth)) + chalk5.dim("Name".padEnd(nameWidth)) + chalk5.dim("Why");
|
|
5188
4708
|
lines.push(header);
|
|
5189
4709
|
} else {
|
|
5190
|
-
const header = " ".repeat(prefixWidth) +
|
|
4710
|
+
const header = " ".repeat(prefixWidth) + chalk5.dim("Name".padEnd(nameWidth)) + chalk5.dim("Technology".padEnd(18)) + chalk5.dim("Source");
|
|
5191
4711
|
lines.push(header);
|
|
5192
4712
|
}
|
|
5193
|
-
lines.push(
|
|
4713
|
+
lines.push(chalk5.dim(" " + "\u2500".repeat(Math.min(cols - 4, 90))));
|
|
5194
4714
|
for (let i = 0; i < recs.length; i++) {
|
|
5195
4715
|
const rec = recs[i];
|
|
5196
|
-
const check = selected.has(i) ?
|
|
5197
|
-
const ptr = i === cursor ?
|
|
4716
|
+
const check = selected.has(i) ? chalk5.green("[x]") : "[ ]";
|
|
4717
|
+
const ptr = i === cursor ? chalk5.cyan(">") : " ";
|
|
5198
4718
|
if (hasScores) {
|
|
5199
|
-
const scoreColor = rec.score >= 90 ?
|
|
4719
|
+
const scoreColor = rec.score >= 90 ? chalk5.green : rec.score >= 70 ? chalk5.yellow : chalk5.dim;
|
|
5200
4720
|
const reasonMax = Math.max(cols - prefixWidth - scoreWidth - nameWidth - 2, 20);
|
|
5201
|
-
lines.push(` ${ptr} ${check} ${scoreColor(String(rec.score).padStart(3))} ${rec.name.padEnd(nameWidth)}${
|
|
4721
|
+
lines.push(` ${ptr} ${check} ${scoreColor(String(rec.score).padStart(3))} ${rec.name.padEnd(nameWidth)}${chalk5.dim(rec.reason.slice(0, reasonMax))}`);
|
|
5202
4722
|
} else {
|
|
5203
|
-
lines.push(` ${ptr} ${check} ${rec.name.padEnd(nameWidth)}${rec.detected_technology.padEnd(16)} ${
|
|
4723
|
+
lines.push(` ${ptr} ${check} ${rec.name.padEnd(nameWidth)}${rec.detected_technology.padEnd(16)} ${chalk5.dim(rec.source_url || "")}`);
|
|
5204
4724
|
}
|
|
5205
4725
|
}
|
|
5206
4726
|
lines.push("");
|
|
5207
|
-
lines.push(
|
|
4727
|
+
lines.push(chalk5.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q cancel"));
|
|
5208
4728
|
return lines.join("\n");
|
|
5209
4729
|
}
|
|
5210
4730
|
function draw(initial) {
|
|
@@ -5253,7 +4773,7 @@ async function interactiveSelect2(recs) {
|
|
|
5253
4773
|
case "\n":
|
|
5254
4774
|
cleanup();
|
|
5255
4775
|
if (selected.size === 0) {
|
|
5256
|
-
console.log(
|
|
4776
|
+
console.log(chalk5.dim("\n No skills selected.\n"));
|
|
5257
4777
|
resolve2(null);
|
|
5258
4778
|
} else {
|
|
5259
4779
|
resolve2(Array.from(selected).sort().map((i) => recs[i]));
|
|
@@ -5263,7 +4783,7 @@ async function interactiveSelect2(recs) {
|
|
|
5263
4783
|
case "\x1B":
|
|
5264
4784
|
case "":
|
|
5265
4785
|
cleanup();
|
|
5266
|
-
console.log(
|
|
4786
|
+
console.log(chalk5.dim("\n Cancelled.\n"));
|
|
5267
4787
|
resolve2(null);
|
|
5268
4788
|
break;
|
|
5269
4789
|
}
|
|
@@ -5310,7 +4830,7 @@ async function fetchSkillContent(rec) {
|
|
|
5310
4830
|
return null;
|
|
5311
4831
|
}
|
|
5312
4832
|
async function installSkills(recs, platforms, contentMap) {
|
|
5313
|
-
const spinner =
|
|
4833
|
+
const spinner = ora(`Installing ${recs.length} skill${recs.length > 1 ? "s" : ""}...`).start();
|
|
5314
4834
|
const installed = [];
|
|
5315
4835
|
for (const rec of recs) {
|
|
5316
4836
|
const content = contentMap.get(rec.slug);
|
|
@@ -5326,7 +4846,7 @@ async function installSkills(recs, platforms, contentMap) {
|
|
|
5326
4846
|
if (installed.length > 0) {
|
|
5327
4847
|
spinner.succeed(`Installed ${installed.length} file${installed.length > 1 ? "s" : ""}`);
|
|
5328
4848
|
for (const p of installed) {
|
|
5329
|
-
console.log(
|
|
4849
|
+
console.log(chalk5.green(` \u2713 ${p}`));
|
|
5330
4850
|
}
|
|
5331
4851
|
} else {
|
|
5332
4852
|
spinner.fail("No skills were installed");
|
|
@@ -5339,19 +4859,19 @@ function printSkills(recs) {
|
|
|
5339
4859
|
const nameWidth = Math.max(...recs.map((r) => r.name.length), 4) + 2;
|
|
5340
4860
|
const scoreWidth = 6;
|
|
5341
4861
|
const prefixWidth = 2;
|
|
5342
|
-
console.log(
|
|
4862
|
+
console.log(chalk5.bold("\n Skills\n"));
|
|
5343
4863
|
if (hasScores) {
|
|
5344
|
-
console.log(" ".repeat(prefixWidth) +
|
|
4864
|
+
console.log(" ".repeat(prefixWidth) + chalk5.dim("Score".padEnd(scoreWidth)) + chalk5.dim("Name".padEnd(nameWidth)) + chalk5.dim("Why"));
|
|
5345
4865
|
} else {
|
|
5346
|
-
console.log(" ".repeat(prefixWidth) +
|
|
4866
|
+
console.log(" ".repeat(prefixWidth) + chalk5.dim("Name".padEnd(nameWidth)) + chalk5.dim("Technology".padEnd(18)) + chalk5.dim("Source"));
|
|
5347
4867
|
}
|
|
5348
|
-
console.log(
|
|
4868
|
+
console.log(chalk5.dim(" " + "\u2500".repeat(Math.min(cols - 4, 90))));
|
|
5349
4869
|
for (const rec of recs) {
|
|
5350
4870
|
if (hasScores) {
|
|
5351
4871
|
const reasonMax = Math.max(cols - prefixWidth - scoreWidth - nameWidth - 2, 20);
|
|
5352
|
-
console.log(` ${String(rec.score).padStart(3)} ${rec.name.padEnd(nameWidth)}${
|
|
4872
|
+
console.log(` ${String(rec.score).padStart(3)} ${rec.name.padEnd(nameWidth)}${chalk5.dim(rec.reason.slice(0, reasonMax))}`);
|
|
5353
4873
|
} else {
|
|
5354
|
-
console.log(` ${rec.name.padEnd(nameWidth)}${rec.detected_technology.padEnd(16)} ${
|
|
4874
|
+
console.log(` ${rec.name.padEnd(nameWidth)}${rec.detected_technology.padEnd(16)} ${chalk5.dim(rec.source_url || "")}`);
|
|
5355
4875
|
}
|
|
5356
4876
|
}
|
|
5357
4877
|
console.log("");
|
|
@@ -5359,8 +4879,8 @@ function printSkills(recs) {
|
|
|
5359
4879
|
|
|
5360
4880
|
// src/commands/onboard.ts
|
|
5361
4881
|
async function initCommand(options) {
|
|
5362
|
-
const brand =
|
|
5363
|
-
const title =
|
|
4882
|
+
const brand = chalk6.hex("#EB9D83");
|
|
4883
|
+
const title = chalk6.hex("#83D1EB");
|
|
5364
4884
|
console.log(brand.bold(`
|
|
5365
4885
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
5366
4886
|
\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
@@ -5369,21 +4889,21 @@ async function initCommand(options) {
|
|
|
5369
4889
|
\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
5370
4890
|
\u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
|
|
5371
4891
|
`));
|
|
5372
|
-
console.log(
|
|
4892
|
+
console.log(chalk6.dim(" Onboard your project for AI-assisted development\n"));
|
|
5373
4893
|
console.log(title.bold(" Welcome to Caliber\n"));
|
|
5374
|
-
console.log(
|
|
5375
|
-
console.log(
|
|
4894
|
+
console.log(chalk6.dim(" Caliber analyzes your codebase and creates tailored config files"));
|
|
4895
|
+
console.log(chalk6.dim(" so your AI coding agents understand your project from day one.\n"));
|
|
5376
4896
|
console.log(title.bold(" How onboarding works:\n"));
|
|
5377
|
-
console.log(
|
|
5378
|
-
console.log(
|
|
5379
|
-
console.log(
|
|
5380
|
-
console.log(
|
|
5381
|
-
console.log(
|
|
5382
|
-
console.log(
|
|
4897
|
+
console.log(chalk6.dim(" 1. Connect Set up your LLM provider"));
|
|
4898
|
+
console.log(chalk6.dim(" 2. Discover Analyze your code, dependencies, and structure"));
|
|
4899
|
+
console.log(chalk6.dim(" 3. Generate Create config files tailored to your project"));
|
|
4900
|
+
console.log(chalk6.dim(" 4. Review Preview, refine, and apply the changes"));
|
|
4901
|
+
console.log(chalk6.dim(" 5. Enhance Discover MCP servers for your tools"));
|
|
4902
|
+
console.log(chalk6.dim(" 6. Skills Browse community skills for your stack\n"));
|
|
5383
4903
|
console.log(title.bold(" Step 1/6 \u2014 Connect your LLM\n"));
|
|
5384
4904
|
let config = loadConfig();
|
|
5385
4905
|
if (!config) {
|
|
5386
|
-
console.log(
|
|
4906
|
+
console.log(chalk6.dim(" No LLM provider set yet. Choose how to run Caliber:\n"));
|
|
5387
4907
|
try {
|
|
5388
4908
|
await runInteractiveProviderSetup({
|
|
5389
4909
|
selectMessage: "How do you want to use Caliber? (choose LLM provider)"
|
|
@@ -5394,22 +4914,22 @@ async function initCommand(options) {
|
|
|
5394
4914
|
}
|
|
5395
4915
|
config = loadConfig();
|
|
5396
4916
|
if (!config) {
|
|
5397
|
-
console.log(
|
|
4917
|
+
console.log(chalk6.red(" Setup was cancelled or failed.\n"));
|
|
5398
4918
|
throw new Error("__exit__");
|
|
5399
4919
|
}
|
|
5400
|
-
console.log(
|
|
4920
|
+
console.log(chalk6.green(" \u2713 Provider saved. Let's continue.\n"));
|
|
5401
4921
|
}
|
|
5402
4922
|
const displayModel = config.model === "default" && config.provider === "claude-cli" ? process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)" : config.model;
|
|
5403
4923
|
const fastModel = getFastModel();
|
|
5404
4924
|
const modelLine = fastModel ? ` Provider: ${config.provider} | Model: ${displayModel} | Scan: ${fastModel}` : ` Provider: ${config.provider} | Model: ${displayModel}`;
|
|
5405
|
-
console.log(
|
|
4925
|
+
console.log(chalk6.dim(modelLine + "\n"));
|
|
5406
4926
|
console.log(title.bold(" Step 2/6 \u2014 Discover your project\n"));
|
|
5407
|
-
console.log(
|
|
5408
|
-
const spinner =
|
|
4927
|
+
console.log(chalk6.dim(" Learning about your languages, dependencies, structure, and existing configs.\n"));
|
|
4928
|
+
const spinner = ora2("Analyzing project...").start();
|
|
5409
4929
|
const fingerprint = await collectFingerprint(process.cwd());
|
|
5410
4930
|
spinner.succeed("Project analyzed");
|
|
5411
|
-
console.log(
|
|
5412
|
-
console.log(
|
|
4931
|
+
console.log(chalk6.dim(` Languages: ${fingerprint.languages.join(", ") || "none detected"}`));
|
|
4932
|
+
console.log(chalk6.dim(` Files: ${fingerprint.fileTree.length} found
|
|
5413
4933
|
`));
|
|
5414
4934
|
const targetAgent = options.agent || await promptAgent();
|
|
5415
4935
|
const preScore = computeLocalScore(process.cwd(), targetAgent);
|
|
@@ -5428,28 +4948,28 @@ async function initCommand(options) {
|
|
|
5428
4948
|
const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length || fingerprint.existingConfigs.agentsMd);
|
|
5429
4949
|
const NON_LLM_CHECKS = /* @__PURE__ */ new Set(["hooks_configured", "agents_md_exists", "permissions_configured", "mcp_servers"]);
|
|
5430
4950
|
if (hasExistingConfig && baselineScore.score === 100) {
|
|
5431
|
-
console.log(
|
|
5432
|
-
console.log(
|
|
4951
|
+
console.log(chalk6.bold.green(" Your setup is already optimal \u2014 nothing to change.\n"));
|
|
4952
|
+
console.log(chalk6.dim(" Run ") + chalk6.hex("#83D1EB")("caliber onboard --force") + chalk6.dim(" to regenerate anyway.\n"));
|
|
5433
4953
|
if (!options.force) return;
|
|
5434
4954
|
}
|
|
5435
4955
|
const allFailingChecks = baselineScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
|
|
5436
4956
|
const llmFixableChecks = allFailingChecks.filter((c) => !NON_LLM_CHECKS.has(c.id));
|
|
5437
4957
|
if (hasExistingConfig && llmFixableChecks.length === 0 && allFailingChecks.length > 0 && !options.force) {
|
|
5438
|
-
console.log(
|
|
5439
|
-
console.log(
|
|
4958
|
+
console.log(chalk6.bold.green("\n Your config is fully optimized for LLM generation.\n"));
|
|
4959
|
+
console.log(chalk6.dim(" Remaining items need CLI actions:\n"));
|
|
5440
4960
|
for (const check of allFailingChecks) {
|
|
5441
|
-
console.log(
|
|
4961
|
+
console.log(chalk6.dim(` \u2022 ${check.name}`));
|
|
5442
4962
|
if (check.suggestion) {
|
|
5443
|
-
console.log(` ${
|
|
4963
|
+
console.log(` ${chalk6.hex("#83D1EB")(check.suggestion)}`);
|
|
5444
4964
|
}
|
|
5445
4965
|
}
|
|
5446
4966
|
console.log("");
|
|
5447
|
-
console.log(
|
|
4967
|
+
console.log(chalk6.dim(" Run ") + chalk6.hex("#83D1EB")("caliber onboard --force") + chalk6.dim(" to regenerate anyway.\n"));
|
|
5448
4968
|
return;
|
|
5449
4969
|
}
|
|
5450
4970
|
const isEmpty = fingerprint.fileTree.length < 3;
|
|
5451
4971
|
if (isEmpty) {
|
|
5452
|
-
fingerprint.description = await
|
|
4972
|
+
fingerprint.description = await promptInput2("What will you build in this project?");
|
|
5453
4973
|
}
|
|
5454
4974
|
let failingChecks;
|
|
5455
4975
|
let passingChecks;
|
|
@@ -5460,24 +4980,24 @@ async function initCommand(options) {
|
|
|
5460
4980
|
currentScore = baselineScore.score;
|
|
5461
4981
|
if (failingChecks.length > 0) {
|
|
5462
4982
|
console.log(title.bold(" Step 3/6 \u2014 Fine-tuning\n"));
|
|
5463
|
-
console.log(
|
|
4983
|
+
console.log(chalk6.dim(` Your setup scores ${baselineScore.score}/100 \u2014 fixing ${failingChecks.length} remaining issue${failingChecks.length === 1 ? "" : "s"}:
|
|
5464
4984
|
`));
|
|
5465
4985
|
for (const check of failingChecks) {
|
|
5466
|
-
console.log(
|
|
4986
|
+
console.log(chalk6.dim(` \u2022 ${check.name}`));
|
|
5467
4987
|
}
|
|
5468
4988
|
console.log("");
|
|
5469
4989
|
}
|
|
5470
4990
|
} else if (hasExistingConfig) {
|
|
5471
4991
|
console.log(title.bold(" Step 3/6 \u2014 Improve your setup\n"));
|
|
5472
|
-
console.log(
|
|
5473
|
-
console.log(
|
|
4992
|
+
console.log(chalk6.dim(" Reviewing your existing configs against your codebase"));
|
|
4993
|
+
console.log(chalk6.dim(" and preparing improvements.\n"));
|
|
5474
4994
|
} else {
|
|
5475
4995
|
console.log(title.bold(" Step 3/6 \u2014 Build your agent setup\n"));
|
|
5476
|
-
console.log(
|
|
4996
|
+
console.log(chalk6.dim(" Creating config files tailored to your project.\n"));
|
|
5477
4997
|
}
|
|
5478
|
-
console.log(
|
|
4998
|
+
console.log(chalk6.dim(" This can take a couple of minutes depending on your model and provider.\n"));
|
|
5479
4999
|
const genStartTime = Date.now();
|
|
5480
|
-
const genSpinner =
|
|
5000
|
+
const genSpinner = ora2("Generating setup...").start();
|
|
5481
5001
|
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
|
|
5482
5002
|
genMessages.start();
|
|
5483
5003
|
let generatedSetup = null;
|
|
@@ -5517,8 +5037,8 @@ async function initCommand(options) {
|
|
|
5517
5037
|
if (!generatedSetup) {
|
|
5518
5038
|
genSpinner.fail("Failed to generate setup.");
|
|
5519
5039
|
if (rawOutput) {
|
|
5520
|
-
console.log(
|
|
5521
|
-
console.log(
|
|
5040
|
+
console.log(chalk6.dim("\nRaw LLM output (JSON parse failed):"));
|
|
5041
|
+
console.log(chalk6.dim(rawOutput.slice(0, 500)));
|
|
5522
5042
|
}
|
|
5523
5043
|
throw new Error("__exit__");
|
|
5524
5044
|
}
|
|
@@ -5526,7 +5046,7 @@ async function initCommand(options) {
|
|
|
5526
5046
|
const mins = Math.floor(elapsedMs / 6e4);
|
|
5527
5047
|
const secs = Math.floor(elapsedMs % 6e4 / 1e3);
|
|
5528
5048
|
const timeStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
|
|
5529
|
-
genSpinner.succeed(`Setup generated ${
|
|
5049
|
+
genSpinner.succeed(`Setup generated ${chalk6.dim(`in ${timeStr}`)}`);
|
|
5530
5050
|
printSetupSummary(generatedSetup);
|
|
5531
5051
|
const sessionHistory = [];
|
|
5532
5052
|
sessionHistory.push({
|
|
@@ -5537,11 +5057,11 @@ async function initCommand(options) {
|
|
|
5537
5057
|
const setupFiles = collectSetupFiles(generatedSetup);
|
|
5538
5058
|
const staged = stageFiles(setupFiles, process.cwd());
|
|
5539
5059
|
const totalChanges = staged.newFiles + staged.modifiedFiles;
|
|
5540
|
-
console.log(
|
|
5060
|
+
console.log(chalk6.dim(` ${chalk6.green(`${staged.newFiles} new`)} / ${chalk6.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}
|
|
5541
5061
|
`));
|
|
5542
5062
|
let action;
|
|
5543
5063
|
if (totalChanges === 0) {
|
|
5544
|
-
console.log(
|
|
5064
|
+
console.log(chalk6.dim(" No changes needed \u2014 your configs are already up to date.\n"));
|
|
5545
5065
|
cleanupStaging();
|
|
5546
5066
|
action = "accept";
|
|
5547
5067
|
} else {
|
|
@@ -5556,12 +5076,12 @@ async function initCommand(options) {
|
|
|
5556
5076
|
generatedSetup = await refineLoop(generatedSetup, targetAgent, sessionHistory);
|
|
5557
5077
|
if (!generatedSetup) {
|
|
5558
5078
|
cleanupStaging();
|
|
5559
|
-
console.log(
|
|
5079
|
+
console.log(chalk6.dim("Refinement cancelled. No files were modified."));
|
|
5560
5080
|
return;
|
|
5561
5081
|
}
|
|
5562
5082
|
const updatedFiles = collectSetupFiles(generatedSetup);
|
|
5563
5083
|
const restaged = stageFiles(updatedFiles, process.cwd());
|
|
5564
|
-
console.log(
|
|
5084
|
+
console.log(chalk6.dim(` ${chalk6.green(`${restaged.newFiles} new`)} / ${chalk6.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
|
|
5565
5085
|
`));
|
|
5566
5086
|
printSetupSummary(generatedSetup);
|
|
5567
5087
|
await openReview("terminal", restaged.stagedFiles);
|
|
@@ -5569,56 +5089,37 @@ async function initCommand(options) {
|
|
|
5569
5089
|
}
|
|
5570
5090
|
cleanupStaging();
|
|
5571
5091
|
if (action === "decline") {
|
|
5572
|
-
console.log(
|
|
5092
|
+
console.log(chalk6.dim("Setup declined. No files were modified."));
|
|
5573
5093
|
return;
|
|
5574
5094
|
}
|
|
5575
5095
|
if (options.dryRun) {
|
|
5576
|
-
console.log(
|
|
5096
|
+
console.log(chalk6.yellow("\n[Dry run] Would write the following files:"));
|
|
5577
5097
|
console.log(JSON.stringify(generatedSetup, null, 2));
|
|
5578
5098
|
return;
|
|
5579
5099
|
}
|
|
5580
|
-
const writeSpinner =
|
|
5100
|
+
const writeSpinner = ora2("Writing config files...").start();
|
|
5581
5101
|
try {
|
|
5582
5102
|
const result = writeSetup(generatedSetup);
|
|
5583
5103
|
writeSpinner.succeed("Config files written");
|
|
5584
|
-
console.log(
|
|
5104
|
+
console.log(chalk6.bold("\nFiles created/updated:"));
|
|
5585
5105
|
for (const file of result.written) {
|
|
5586
|
-
console.log(` ${
|
|
5106
|
+
console.log(` ${chalk6.green("\u2713")} ${file}`);
|
|
5587
5107
|
}
|
|
5588
5108
|
if (result.deleted.length > 0) {
|
|
5589
|
-
console.log(
|
|
5109
|
+
console.log(chalk6.bold("\nFiles removed:"));
|
|
5590
5110
|
for (const file of result.deleted) {
|
|
5591
|
-
console.log(` ${
|
|
5111
|
+
console.log(` ${chalk6.red("\u2717")} ${file}`);
|
|
5592
5112
|
}
|
|
5593
5113
|
}
|
|
5594
5114
|
if (result.backupDir) {
|
|
5595
|
-
console.log(
|
|
5115
|
+
console.log(chalk6.dim(`
|
|
5596
5116
|
Backups saved to ${result.backupDir}`));
|
|
5597
5117
|
}
|
|
5598
5118
|
} catch (err) {
|
|
5599
5119
|
writeSpinner.fail("Failed to write files");
|
|
5600
|
-
console.error(
|
|
5120
|
+
console.error(chalk6.red(err instanceof Error ? err.message : "Unknown error"));
|
|
5601
5121
|
throw new Error("__exit__");
|
|
5602
5122
|
}
|
|
5603
|
-
console.log(title.bold("\n Step 5/6 \u2014 Enhance with MCP servers\n"));
|
|
5604
|
-
console.log(chalk7.dim(" MCP servers connect your AI agents to external tools and services"));
|
|
5605
|
-
console.log(chalk7.dim(" like databases, APIs, and platforms your project depends on.\n"));
|
|
5606
|
-
if (fingerprint.tools.length > 0) {
|
|
5607
|
-
try {
|
|
5608
|
-
const mcpResult = await discoverAndInstallMcps(targetAgent, fingerprint, process.cwd());
|
|
5609
|
-
if (mcpResult.installed > 0) {
|
|
5610
|
-
console.log(chalk7.bold(`
|
|
5611
|
-
${mcpResult.installed} MCP server${mcpResult.installed > 1 ? "s" : ""} configured`));
|
|
5612
|
-
for (const name of mcpResult.names) {
|
|
5613
|
-
console.log(` ${chalk7.green("\u2713")} ${name}`);
|
|
5614
|
-
}
|
|
5615
|
-
}
|
|
5616
|
-
} catch (err) {
|
|
5617
|
-
console.log(chalk7.dim(" MCP discovery skipped: " + (err instanceof Error ? err.message : "unknown error")));
|
|
5618
|
-
}
|
|
5619
|
-
} else {
|
|
5620
|
-
console.log(chalk7.dim(" No external tools or services detected \u2014 skipping MCP discovery.\n"));
|
|
5621
|
-
}
|
|
5622
5123
|
ensurePermissions();
|
|
5623
5124
|
const sha = getCurrentHeadSha();
|
|
5624
5125
|
writeState({
|
|
@@ -5628,55 +5129,55 @@ async function initCommand(options) {
|
|
|
5628
5129
|
});
|
|
5629
5130
|
console.log("");
|
|
5630
5131
|
console.log(title.bold(" Keep your configs fresh\n"));
|
|
5631
|
-
console.log(
|
|
5132
|
+
console.log(chalk6.dim(" Caliber can automatically update your agent configs when your code changes.\n"));
|
|
5632
5133
|
const hookChoice = await promptHookType(targetAgent);
|
|
5633
5134
|
if (hookChoice === "claude" || hookChoice === "both") {
|
|
5634
5135
|
const hookResult = installHook();
|
|
5635
5136
|
if (hookResult.installed) {
|
|
5636
|
-
console.log(` ${
|
|
5637
|
-
console.log(
|
|
5137
|
+
console.log(` ${chalk6.green("\u2713")} Claude Code hook installed \u2014 docs update on session end`);
|
|
5138
|
+
console.log(chalk6.dim(" Run ") + chalk6.hex("#83D1EB")("caliber hooks --remove") + chalk6.dim(" to disable"));
|
|
5638
5139
|
} else if (hookResult.alreadyInstalled) {
|
|
5639
|
-
console.log(
|
|
5140
|
+
console.log(chalk6.dim(" Claude Code hook already installed"));
|
|
5640
5141
|
}
|
|
5641
5142
|
const learnResult = installLearningHooks();
|
|
5642
5143
|
if (learnResult.installed) {
|
|
5643
|
-
console.log(` ${
|
|
5644
|
-
console.log(
|
|
5144
|
+
console.log(` ${chalk6.green("\u2713")} Learning hooks installed \u2014 session insights captured automatically`);
|
|
5145
|
+
console.log(chalk6.dim(" Run ") + chalk6.hex("#83D1EB")("caliber learn remove") + chalk6.dim(" to disable"));
|
|
5645
5146
|
} else if (learnResult.alreadyInstalled) {
|
|
5646
|
-
console.log(
|
|
5147
|
+
console.log(chalk6.dim(" Learning hooks already installed"));
|
|
5647
5148
|
}
|
|
5648
5149
|
}
|
|
5649
5150
|
if (hookChoice === "precommit" || hookChoice === "both") {
|
|
5650
5151
|
const precommitResult = installPreCommitHook();
|
|
5651
5152
|
if (precommitResult.installed) {
|
|
5652
|
-
console.log(` ${
|
|
5653
|
-
console.log(
|
|
5153
|
+
console.log(` ${chalk6.green("\u2713")} Pre-commit hook installed \u2014 docs refresh before each commit`);
|
|
5154
|
+
console.log(chalk6.dim(" Run ") + chalk6.hex("#83D1EB")("caliber hooks --remove") + chalk6.dim(" to disable"));
|
|
5654
5155
|
} else if (precommitResult.alreadyInstalled) {
|
|
5655
|
-
console.log(
|
|
5156
|
+
console.log(chalk6.dim(" Pre-commit hook already installed"));
|
|
5656
5157
|
} else {
|
|
5657
|
-
console.log(
|
|
5158
|
+
console.log(chalk6.yellow(" Could not install pre-commit hook (not a git repository?)"));
|
|
5658
5159
|
}
|
|
5659
5160
|
}
|
|
5660
5161
|
if (hookChoice === "skip") {
|
|
5661
|
-
console.log(
|
|
5162
|
+
console.log(chalk6.dim(" Skipped auto-refresh hooks. Run ") + chalk6.hex("#83D1EB")("caliber hooks --install") + chalk6.dim(" later to enable."));
|
|
5662
5163
|
}
|
|
5663
5164
|
const afterScore = computeLocalScore(process.cwd(), targetAgent);
|
|
5664
5165
|
if (afterScore.score < baselineScore.score) {
|
|
5665
5166
|
console.log("");
|
|
5666
|
-
console.log(
|
|
5167
|
+
console.log(chalk6.yellow(` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`));
|
|
5667
5168
|
try {
|
|
5668
5169
|
const { restored, removed } = undoSetup();
|
|
5669
5170
|
if (restored.length > 0 || removed.length > 0) {
|
|
5670
|
-
console.log(
|
|
5171
|
+
console.log(chalk6.dim(` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`));
|
|
5671
5172
|
}
|
|
5672
5173
|
} catch {
|
|
5673
5174
|
}
|
|
5674
|
-
console.log(
|
|
5175
|
+
console.log(chalk6.dim(" Run ") + chalk6.hex("#83D1EB")("caliber onboard --force") + chalk6.dim(" to override.\n"));
|
|
5675
5176
|
return;
|
|
5676
5177
|
}
|
|
5677
5178
|
displayScoreDelta(baselineScore, afterScore);
|
|
5678
5179
|
console.log(title.bold("\n Step 6/6 \u2014 Community skills\n"));
|
|
5679
|
-
console.log(
|
|
5180
|
+
console.log(chalk6.dim(" Search public skill registries for skills that match your tech stack.\n"));
|
|
5680
5181
|
const wantsSkills = await select4({
|
|
5681
5182
|
message: "Search public repos for relevant skills to add to this project?",
|
|
5682
5183
|
choices: [
|
|
@@ -5689,16 +5190,16 @@ async function initCommand(options) {
|
|
|
5689
5190
|
await searchAndInstallSkills();
|
|
5690
5191
|
} catch (err) {
|
|
5691
5192
|
if (err.message !== "__exit__") {
|
|
5692
|
-
console.log(
|
|
5193
|
+
console.log(chalk6.dim(" Skills search failed: " + (err.message || "unknown error")));
|
|
5693
5194
|
}
|
|
5694
|
-
console.log(
|
|
5195
|
+
console.log(chalk6.dim(" Run ") + chalk6.hex("#83D1EB")("caliber skills") + chalk6.dim(" later to try again.\n"));
|
|
5695
5196
|
}
|
|
5696
5197
|
} else {
|
|
5697
|
-
console.log(
|
|
5198
|
+
console.log(chalk6.dim(" Skipped. Run ") + chalk6.hex("#83D1EB")("caliber skills") + chalk6.dim(" later to browse.\n"));
|
|
5698
5199
|
}
|
|
5699
|
-
console.log(
|
|
5700
|
-
console.log(
|
|
5701
|
-
console.log(
|
|
5200
|
+
console.log(chalk6.bold.green(" Onboarding complete! Your project is ready for AI-assisted development."));
|
|
5201
|
+
console.log(chalk6.dim(" Run ") + chalk6.hex("#83D1EB")("caliber undo") + chalk6.dim(" to revert changes.\n"));
|
|
5202
|
+
console.log(chalk6.bold(" Next steps:\n"));
|
|
5702
5203
|
console.log(` ${title("caliber score")} See your full config breakdown`);
|
|
5703
5204
|
console.log(` ${title("caliber skills")} Discover community skills for your stack`);
|
|
5704
5205
|
console.log(` ${title("caliber undo")} Revert all changes from this run`);
|
|
@@ -5706,7 +5207,7 @@ async function initCommand(options) {
|
|
|
5706
5207
|
}
|
|
5707
5208
|
async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
5708
5209
|
while (true) {
|
|
5709
|
-
const message = await
|
|
5210
|
+
const message = await promptInput2("\nWhat would you like to change?");
|
|
5710
5211
|
if (!message || message.toLowerCase() === "done" || message.toLowerCase() === "accept") {
|
|
5711
5212
|
return currentSetup;
|
|
5712
5213
|
}
|
|
@@ -5715,12 +5216,12 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
|
5715
5216
|
}
|
|
5716
5217
|
const isValid = await classifyRefineIntent(message);
|
|
5717
5218
|
if (!isValid) {
|
|
5718
|
-
console.log(
|
|
5719
|
-
console.log(
|
|
5720
|
-
console.log(
|
|
5219
|
+
console.log(chalk6.dim(" This doesn't look like a config change request."));
|
|
5220
|
+
console.log(chalk6.dim(" Describe what to add, remove, or modify in your configs."));
|
|
5221
|
+
console.log(chalk6.dim(' Type "done" to accept the current setup.\n'));
|
|
5721
5222
|
continue;
|
|
5722
5223
|
}
|
|
5723
|
-
const refineSpinner =
|
|
5224
|
+
const refineSpinner = ora2("Refining setup...").start();
|
|
5724
5225
|
const refineMessages = new SpinnerMessages(refineSpinner, REFINE_MESSAGES);
|
|
5725
5226
|
refineMessages.start();
|
|
5726
5227
|
const refined = await refineSetup(
|
|
@@ -5738,16 +5239,16 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
|
5738
5239
|
});
|
|
5739
5240
|
refineSpinner.succeed("Setup updated");
|
|
5740
5241
|
printSetupSummary(refined);
|
|
5741
|
-
console.log(
|
|
5242
|
+
console.log(chalk6.dim('Type "done" to accept, or describe more changes.'));
|
|
5742
5243
|
} else {
|
|
5743
5244
|
refineSpinner.fail("Refinement failed \u2014 could not parse AI response.");
|
|
5744
|
-
console.log(
|
|
5245
|
+
console.log(chalk6.dim('Try rephrasing your request, or type "done" to keep the current setup.'));
|
|
5745
5246
|
}
|
|
5746
5247
|
}
|
|
5747
5248
|
}
|
|
5748
5249
|
function summarizeSetup(action, setup) {
|
|
5749
5250
|
const descriptions = setup.fileDescriptions;
|
|
5750
|
-
const files = descriptions ? Object.entries(descriptions).map(([
|
|
5251
|
+
const files = descriptions ? Object.entries(descriptions).map(([path23, desc]) => ` ${path23}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
|
|
5751
5252
|
return `${action}. Files:
|
|
5752
5253
|
${files}`;
|
|
5753
5254
|
}
|
|
@@ -5798,10 +5299,10 @@ ${JSON.stringify(checkList, null, 2)}`,
|
|
|
5798
5299
|
return [];
|
|
5799
5300
|
}
|
|
5800
5301
|
}
|
|
5801
|
-
function
|
|
5802
|
-
const rl =
|
|
5302
|
+
function promptInput2(question) {
|
|
5303
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
5803
5304
|
return new Promise((resolve2) => {
|
|
5804
|
-
rl.question(
|
|
5305
|
+
rl.question(chalk6.cyan(`${question} `), (answer) => {
|
|
5805
5306
|
rl.close();
|
|
5806
5307
|
resolve2(answer.trim());
|
|
5807
5308
|
});
|
|
@@ -5854,26 +5355,26 @@ function printSetupSummary(setup) {
|
|
|
5854
5355
|
const fileDescriptions = setup.fileDescriptions;
|
|
5855
5356
|
const deletions = setup.deletions;
|
|
5856
5357
|
console.log("");
|
|
5857
|
-
console.log(
|
|
5358
|
+
console.log(chalk6.bold(" Proposed changes:\n"));
|
|
5858
5359
|
const getDescription = (filePath) => {
|
|
5859
5360
|
return fileDescriptions?.[filePath];
|
|
5860
5361
|
};
|
|
5861
5362
|
if (claude) {
|
|
5862
5363
|
if (claude.claudeMd) {
|
|
5863
|
-
const icon =
|
|
5364
|
+
const icon = fs21.existsSync("CLAUDE.md") ? chalk6.yellow("~") : chalk6.green("+");
|
|
5864
5365
|
const desc = getDescription("CLAUDE.md");
|
|
5865
|
-
console.log(` ${icon} ${
|
|
5866
|
-
if (desc) console.log(
|
|
5366
|
+
console.log(` ${icon} ${chalk6.bold("CLAUDE.md")}`);
|
|
5367
|
+
if (desc) console.log(chalk6.dim(` ${desc}`));
|
|
5867
5368
|
console.log("");
|
|
5868
5369
|
}
|
|
5869
5370
|
const skills = claude.skills;
|
|
5870
5371
|
if (Array.isArray(skills) && skills.length > 0) {
|
|
5871
5372
|
for (const skill of skills) {
|
|
5872
5373
|
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
5873
|
-
const icon =
|
|
5374
|
+
const icon = fs21.existsSync(skillPath) ? chalk6.yellow("~") : chalk6.green("+");
|
|
5874
5375
|
const desc = getDescription(skillPath);
|
|
5875
|
-
console.log(` ${icon} ${
|
|
5876
|
-
console.log(
|
|
5376
|
+
console.log(` ${icon} ${chalk6.bold(skillPath)}`);
|
|
5377
|
+
console.log(chalk6.dim(` ${desc || skill.description || skill.name}`));
|
|
5877
5378
|
console.log("");
|
|
5878
5379
|
}
|
|
5879
5380
|
}
|
|
@@ -5881,40 +5382,40 @@ function printSetupSummary(setup) {
|
|
|
5881
5382
|
const codex = setup.codex;
|
|
5882
5383
|
if (codex) {
|
|
5883
5384
|
if (codex.agentsMd) {
|
|
5884
|
-
const icon =
|
|
5385
|
+
const icon = fs21.existsSync("AGENTS.md") ? chalk6.yellow("~") : chalk6.green("+");
|
|
5885
5386
|
const desc = getDescription("AGENTS.md");
|
|
5886
|
-
console.log(` ${icon} ${
|
|
5887
|
-
if (desc) console.log(
|
|
5387
|
+
console.log(` ${icon} ${chalk6.bold("AGENTS.md")}`);
|
|
5388
|
+
if (desc) console.log(chalk6.dim(` ${desc}`));
|
|
5888
5389
|
console.log("");
|
|
5889
5390
|
}
|
|
5890
5391
|
const codexSkills = codex.skills;
|
|
5891
5392
|
if (Array.isArray(codexSkills) && codexSkills.length > 0) {
|
|
5892
5393
|
for (const skill of codexSkills) {
|
|
5893
5394
|
const skillPath = `.agents/skills/${skill.name}/SKILL.md`;
|
|
5894
|
-
const icon =
|
|
5395
|
+
const icon = fs21.existsSync(skillPath) ? chalk6.yellow("~") : chalk6.green("+");
|
|
5895
5396
|
const desc = getDescription(skillPath);
|
|
5896
|
-
console.log(` ${icon} ${
|
|
5897
|
-
console.log(
|
|
5397
|
+
console.log(` ${icon} ${chalk6.bold(skillPath)}`);
|
|
5398
|
+
console.log(chalk6.dim(` ${desc || skill.description || skill.name}`));
|
|
5898
5399
|
console.log("");
|
|
5899
5400
|
}
|
|
5900
5401
|
}
|
|
5901
5402
|
}
|
|
5902
5403
|
if (cursor) {
|
|
5903
5404
|
if (cursor.cursorrules) {
|
|
5904
|
-
const icon =
|
|
5405
|
+
const icon = fs21.existsSync(".cursorrules") ? chalk6.yellow("~") : chalk6.green("+");
|
|
5905
5406
|
const desc = getDescription(".cursorrules");
|
|
5906
|
-
console.log(` ${icon} ${
|
|
5907
|
-
if (desc) console.log(
|
|
5407
|
+
console.log(` ${icon} ${chalk6.bold(".cursorrules")}`);
|
|
5408
|
+
if (desc) console.log(chalk6.dim(` ${desc}`));
|
|
5908
5409
|
console.log("");
|
|
5909
5410
|
}
|
|
5910
5411
|
const cursorSkills = cursor.skills;
|
|
5911
5412
|
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
5912
5413
|
for (const skill of cursorSkills) {
|
|
5913
5414
|
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
5914
|
-
const icon =
|
|
5415
|
+
const icon = fs21.existsSync(skillPath) ? chalk6.yellow("~") : chalk6.green("+");
|
|
5915
5416
|
const desc = getDescription(skillPath);
|
|
5916
|
-
console.log(` ${icon} ${
|
|
5917
|
-
console.log(
|
|
5417
|
+
console.log(` ${icon} ${chalk6.bold(skillPath)}`);
|
|
5418
|
+
console.log(chalk6.dim(` ${desc || skill.description || skill.name}`));
|
|
5918
5419
|
console.log("");
|
|
5919
5420
|
}
|
|
5920
5421
|
}
|
|
@@ -5922,40 +5423,40 @@ function printSetupSummary(setup) {
|
|
|
5922
5423
|
if (Array.isArray(rules) && rules.length > 0) {
|
|
5923
5424
|
for (const rule of rules) {
|
|
5924
5425
|
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
5925
|
-
const icon =
|
|
5426
|
+
const icon = fs21.existsSync(rulePath) ? chalk6.yellow("~") : chalk6.green("+");
|
|
5926
5427
|
const desc = getDescription(rulePath);
|
|
5927
|
-
console.log(` ${icon} ${
|
|
5428
|
+
console.log(` ${icon} ${chalk6.bold(rulePath)}`);
|
|
5928
5429
|
if (desc) {
|
|
5929
|
-
console.log(
|
|
5430
|
+
console.log(chalk6.dim(` ${desc}`));
|
|
5930
5431
|
} else {
|
|
5931
5432
|
const firstLine = rule.content.split("\n").filter((l) => l.trim() && !l.trim().startsWith("#"))[0];
|
|
5932
|
-
if (firstLine) console.log(
|
|
5433
|
+
if (firstLine) console.log(chalk6.dim(` ${firstLine.trim().slice(0, 80)}`));
|
|
5933
5434
|
}
|
|
5934
5435
|
console.log("");
|
|
5935
5436
|
}
|
|
5936
5437
|
}
|
|
5937
5438
|
}
|
|
5938
|
-
if (!codex && !
|
|
5939
|
-
console.log(` ${
|
|
5940
|
-
console.log(
|
|
5439
|
+
if (!codex && !fs21.existsSync("AGENTS.md")) {
|
|
5440
|
+
console.log(` ${chalk6.green("+")} ${chalk6.bold("AGENTS.md")}`);
|
|
5441
|
+
console.log(chalk6.dim(" Cross-agent coordination file"));
|
|
5941
5442
|
console.log("");
|
|
5942
5443
|
}
|
|
5943
5444
|
if (Array.isArray(deletions) && deletions.length > 0) {
|
|
5944
5445
|
for (const del of deletions) {
|
|
5945
|
-
console.log(` ${
|
|
5946
|
-
console.log(
|
|
5446
|
+
console.log(` ${chalk6.red("-")} ${chalk6.bold(del.filePath)}`);
|
|
5447
|
+
console.log(chalk6.dim(` ${del.reason}`));
|
|
5947
5448
|
console.log("");
|
|
5948
5449
|
}
|
|
5949
5450
|
}
|
|
5950
|
-
console.log(` ${
|
|
5451
|
+
console.log(` ${chalk6.green("+")} ${chalk6.dim("new")} ${chalk6.yellow("~")} ${chalk6.dim("modified")} ${chalk6.red("-")} ${chalk6.dim("removed")}`);
|
|
5951
5452
|
console.log("");
|
|
5952
5453
|
}
|
|
5953
5454
|
function ensurePermissions() {
|
|
5954
5455
|
const settingsPath = ".claude/settings.json";
|
|
5955
5456
|
let settings = {};
|
|
5956
5457
|
try {
|
|
5957
|
-
if (
|
|
5958
|
-
settings = JSON.parse(
|
|
5458
|
+
if (fs21.existsSync(settingsPath)) {
|
|
5459
|
+
settings = JSON.parse(fs21.readFileSync(settingsPath, "utf-8"));
|
|
5959
5460
|
}
|
|
5960
5461
|
} catch {
|
|
5961
5462
|
}
|
|
@@ -5969,15 +5470,15 @@ function ensurePermissions() {
|
|
|
5969
5470
|
"Bash(git *)"
|
|
5970
5471
|
];
|
|
5971
5472
|
settings.permissions = permissions;
|
|
5972
|
-
if (!
|
|
5973
|
-
|
|
5473
|
+
if (!fs21.existsSync(".claude")) fs21.mkdirSync(".claude", { recursive: true });
|
|
5474
|
+
fs21.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5974
5475
|
}
|
|
5975
5476
|
|
|
5976
5477
|
// src/commands/undo.ts
|
|
5977
|
-
import
|
|
5978
|
-
import
|
|
5478
|
+
import chalk7 from "chalk";
|
|
5479
|
+
import ora3 from "ora";
|
|
5979
5480
|
function undoCommand() {
|
|
5980
|
-
const spinner =
|
|
5481
|
+
const spinner = ora3("Reverting setup...").start();
|
|
5981
5482
|
try {
|
|
5982
5483
|
const { restored, removed } = undoSetup();
|
|
5983
5484
|
if (restored.length === 0 && removed.length === 0) {
|
|
@@ -5986,27 +5487,27 @@ function undoCommand() {
|
|
|
5986
5487
|
}
|
|
5987
5488
|
spinner.succeed("Setup reverted successfully.\n");
|
|
5988
5489
|
if (restored.length > 0) {
|
|
5989
|
-
console.log(
|
|
5490
|
+
console.log(chalk7.cyan(" Restored from backup:"));
|
|
5990
5491
|
for (const file of restored) {
|
|
5991
|
-
console.log(` ${
|
|
5492
|
+
console.log(` ${chalk7.green("\u21A9")} ${file}`);
|
|
5992
5493
|
}
|
|
5993
5494
|
}
|
|
5994
5495
|
if (removed.length > 0) {
|
|
5995
|
-
console.log(
|
|
5496
|
+
console.log(chalk7.cyan(" Removed:"));
|
|
5996
5497
|
for (const file of removed) {
|
|
5997
|
-
console.log(` ${
|
|
5498
|
+
console.log(` ${chalk7.red("\u2717")} ${file}`);
|
|
5998
5499
|
}
|
|
5999
5500
|
}
|
|
6000
5501
|
console.log("");
|
|
6001
5502
|
} catch (err) {
|
|
6002
|
-
spinner.fail(
|
|
5503
|
+
spinner.fail(chalk7.red(err instanceof Error ? err.message : "Undo failed"));
|
|
6003
5504
|
throw new Error("__exit__");
|
|
6004
5505
|
}
|
|
6005
5506
|
}
|
|
6006
5507
|
|
|
6007
5508
|
// src/commands/status.ts
|
|
6008
|
-
import
|
|
6009
|
-
import
|
|
5509
|
+
import chalk8 from "chalk";
|
|
5510
|
+
import fs22 from "fs";
|
|
6010
5511
|
async function statusCommand(options) {
|
|
6011
5512
|
const config = loadConfig();
|
|
6012
5513
|
const manifest = readManifest();
|
|
@@ -6019,52 +5520,52 @@ async function statusCommand(options) {
|
|
|
6019
5520
|
}, null, 2));
|
|
6020
5521
|
return;
|
|
6021
5522
|
}
|
|
6022
|
-
console.log(
|
|
5523
|
+
console.log(chalk8.bold("\nCaliber Status\n"));
|
|
6023
5524
|
if (config) {
|
|
6024
|
-
console.log(` LLM: ${
|
|
5525
|
+
console.log(` LLM: ${chalk8.green(config.provider)} (${config.model})`);
|
|
6025
5526
|
} else {
|
|
6026
|
-
console.log(` LLM: ${
|
|
5527
|
+
console.log(` LLM: ${chalk8.yellow("Not configured")} \u2014 run ${chalk8.hex("#83D1EB")("caliber config")}`);
|
|
6027
5528
|
}
|
|
6028
5529
|
if (!manifest) {
|
|
6029
|
-
console.log(` Setup: ${
|
|
6030
|
-
console.log(
|
|
5530
|
+
console.log(` Setup: ${chalk8.dim("No setup applied")}`);
|
|
5531
|
+
console.log(chalk8.dim("\n Run ") + chalk8.hex("#83D1EB")("caliber onboard") + chalk8.dim(" to get started.\n"));
|
|
6031
5532
|
return;
|
|
6032
5533
|
}
|
|
6033
|
-
console.log(` Files managed: ${
|
|
5534
|
+
console.log(` Files managed: ${chalk8.cyan(manifest.entries.length.toString())}`);
|
|
6034
5535
|
for (const entry of manifest.entries) {
|
|
6035
|
-
const exists =
|
|
6036
|
-
const icon = exists ?
|
|
5536
|
+
const exists = fs22.existsSync(entry.path);
|
|
5537
|
+
const icon = exists ? chalk8.green("\u2713") : chalk8.red("\u2717");
|
|
6037
5538
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
6038
5539
|
}
|
|
6039
5540
|
console.log("");
|
|
6040
5541
|
}
|
|
6041
5542
|
|
|
6042
5543
|
// src/commands/regenerate.ts
|
|
6043
|
-
import
|
|
6044
|
-
import
|
|
5544
|
+
import chalk9 from "chalk";
|
|
5545
|
+
import ora4 from "ora";
|
|
6045
5546
|
import select5 from "@inquirer/select";
|
|
6046
5547
|
async function regenerateCommand(options) {
|
|
6047
5548
|
const config = loadConfig();
|
|
6048
5549
|
if (!config) {
|
|
6049
|
-
console.log(
|
|
5550
|
+
console.log(chalk9.red("No LLM provider configured. Run ") + chalk9.hex("#83D1EB")("caliber config") + chalk9.red(" first."));
|
|
6050
5551
|
throw new Error("__exit__");
|
|
6051
5552
|
}
|
|
6052
5553
|
const manifest = readManifest();
|
|
6053
5554
|
if (!manifest) {
|
|
6054
|
-
console.log(
|
|
5555
|
+
console.log(chalk9.yellow("No existing setup found. Run ") + chalk9.hex("#83D1EB")("caliber onboard") + chalk9.yellow(" first."));
|
|
6055
5556
|
throw new Error("__exit__");
|
|
6056
5557
|
}
|
|
6057
5558
|
const targetAgent = readState()?.targetAgent ?? ["claude", "cursor"];
|
|
6058
|
-
const spinner =
|
|
5559
|
+
const spinner = ora4("Analyzing project...").start();
|
|
6059
5560
|
const fingerprint = await collectFingerprint(process.cwd());
|
|
6060
5561
|
spinner.succeed("Project analyzed");
|
|
6061
5562
|
const baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
6062
5563
|
displayScoreSummary(baselineScore);
|
|
6063
5564
|
if (baselineScore.score === 100) {
|
|
6064
|
-
console.log(
|
|
5565
|
+
console.log(chalk9.green(" Your setup is already at 100/100 \u2014 nothing to regenerate.\n"));
|
|
6065
5566
|
return;
|
|
6066
5567
|
}
|
|
6067
|
-
const genSpinner =
|
|
5568
|
+
const genSpinner = ora4("Regenerating setup...").start();
|
|
6068
5569
|
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
|
|
6069
5570
|
genMessages.start();
|
|
6070
5571
|
let generatedSetup = null;
|
|
@@ -6102,18 +5603,18 @@ async function regenerateCommand(options) {
|
|
|
6102
5603
|
const setupFiles = collectSetupFiles(generatedSetup);
|
|
6103
5604
|
const staged = stageFiles(setupFiles, process.cwd());
|
|
6104
5605
|
const totalChanges = staged.newFiles + staged.modifiedFiles;
|
|
6105
|
-
console.log(
|
|
6106
|
-
${
|
|
5606
|
+
console.log(chalk9.dim(`
|
|
5607
|
+
${chalk9.green(`${staged.newFiles} new`)} / ${chalk9.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}
|
|
6107
5608
|
`));
|
|
6108
5609
|
if (totalChanges === 0) {
|
|
6109
|
-
console.log(
|
|
5610
|
+
console.log(chalk9.dim(" No changes needed \u2014 your configs are already up to date.\n"));
|
|
6110
5611
|
cleanupStaging();
|
|
6111
5612
|
return;
|
|
6112
5613
|
}
|
|
6113
5614
|
if (options.dryRun) {
|
|
6114
|
-
console.log(
|
|
5615
|
+
console.log(chalk9.yellow("[Dry run] Would write:"));
|
|
6115
5616
|
for (const f of staged.stagedFiles) {
|
|
6116
|
-
console.log(` ${f.isNew ?
|
|
5617
|
+
console.log(` ${f.isNew ? chalk9.green("+") : chalk9.yellow("~")} ${f.relativePath}`);
|
|
6117
5618
|
}
|
|
6118
5619
|
cleanupStaging();
|
|
6119
5620
|
return;
|
|
@@ -6132,28 +5633,28 @@ async function regenerateCommand(options) {
|
|
|
6132
5633
|
});
|
|
6133
5634
|
cleanupStaging();
|
|
6134
5635
|
if (action === "decline") {
|
|
6135
|
-
console.log(
|
|
5636
|
+
console.log(chalk9.dim("Regeneration cancelled. No files were modified."));
|
|
6136
5637
|
return;
|
|
6137
5638
|
}
|
|
6138
|
-
const writeSpinner =
|
|
5639
|
+
const writeSpinner = ora4("Writing config files...").start();
|
|
6139
5640
|
try {
|
|
6140
5641
|
const result = writeSetup(generatedSetup);
|
|
6141
5642
|
writeSpinner.succeed("Config files written");
|
|
6142
5643
|
for (const file of result.written) {
|
|
6143
|
-
console.log(` ${
|
|
5644
|
+
console.log(` ${chalk9.green("\u2713")} ${file}`);
|
|
6144
5645
|
}
|
|
6145
5646
|
if (result.deleted.length > 0) {
|
|
6146
5647
|
for (const file of result.deleted) {
|
|
6147
|
-
console.log(` ${
|
|
5648
|
+
console.log(` ${chalk9.red("\u2717")} ${file}`);
|
|
6148
5649
|
}
|
|
6149
5650
|
}
|
|
6150
5651
|
if (result.backupDir) {
|
|
6151
|
-
console.log(
|
|
5652
|
+
console.log(chalk9.dim(`
|
|
6152
5653
|
Backups saved to ${result.backupDir}`));
|
|
6153
5654
|
}
|
|
6154
5655
|
} catch (err) {
|
|
6155
5656
|
writeSpinner.fail("Failed to write files");
|
|
6156
|
-
console.error(
|
|
5657
|
+
console.error(chalk9.red(err instanceof Error ? err.message : "Unknown error"));
|
|
6157
5658
|
throw new Error("__exit__");
|
|
6158
5659
|
}
|
|
6159
5660
|
const sha = getCurrentHeadSha();
|
|
@@ -6165,24 +5666,24 @@ async function regenerateCommand(options) {
|
|
|
6165
5666
|
const afterScore = computeLocalScore(process.cwd(), targetAgent);
|
|
6166
5667
|
if (afterScore.score < baselineScore.score) {
|
|
6167
5668
|
console.log("");
|
|
6168
|
-
console.log(
|
|
5669
|
+
console.log(chalk9.yellow(` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`));
|
|
6169
5670
|
try {
|
|
6170
5671
|
const { restored, removed } = undoSetup();
|
|
6171
5672
|
if (restored.length > 0 || removed.length > 0) {
|
|
6172
|
-
console.log(
|
|
5673
|
+
console.log(chalk9.dim(` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`));
|
|
6173
5674
|
}
|
|
6174
5675
|
} catch {
|
|
6175
5676
|
}
|
|
6176
|
-
console.log(
|
|
5677
|
+
console.log(chalk9.dim(" Run ") + chalk9.hex("#83D1EB")("caliber onboard --force") + chalk9.dim(" to override.\n"));
|
|
6177
5678
|
return;
|
|
6178
5679
|
}
|
|
6179
5680
|
displayScoreDelta(baselineScore, afterScore);
|
|
6180
|
-
console.log(
|
|
6181
|
-
console.log(
|
|
5681
|
+
console.log(chalk9.bold.green(" Regeneration complete!"));
|
|
5682
|
+
console.log(chalk9.dim(" Run ") + chalk9.hex("#83D1EB")("caliber undo") + chalk9.dim(" to revert changes.\n"));
|
|
6182
5683
|
}
|
|
6183
5684
|
|
|
6184
5685
|
// src/commands/score.ts
|
|
6185
|
-
import
|
|
5686
|
+
import chalk10 from "chalk";
|
|
6186
5687
|
async function scoreCommand(options) {
|
|
6187
5688
|
const dir = process.cwd();
|
|
6188
5689
|
const target = options.agent ?? readState()?.targetAgent;
|
|
@@ -6196,23 +5697,23 @@ async function scoreCommand(options) {
|
|
|
6196
5697
|
return;
|
|
6197
5698
|
}
|
|
6198
5699
|
displayScore(result);
|
|
6199
|
-
const separator =
|
|
5700
|
+
const separator = chalk10.gray(" " + "\u2500".repeat(53));
|
|
6200
5701
|
console.log(separator);
|
|
6201
5702
|
if (result.score < 40) {
|
|
6202
|
-
console.log(
|
|
5703
|
+
console.log(chalk10.gray(" Run ") + chalk10.hex("#83D1EB")("caliber onboard") + chalk10.gray(" to generate a complete, optimized setup."));
|
|
6203
5704
|
} else if (result.score < 70) {
|
|
6204
|
-
console.log(
|
|
5705
|
+
console.log(chalk10.gray(" Run ") + chalk10.hex("#83D1EB")("caliber onboard") + chalk10.gray(" to improve your setup."));
|
|
6205
5706
|
} else {
|
|
6206
|
-
console.log(
|
|
5707
|
+
console.log(chalk10.green(" Looking good!") + chalk10.gray(" Run ") + chalk10.hex("#83D1EB")("caliber regenerate") + chalk10.gray(" to rebuild from scratch."));
|
|
6207
5708
|
}
|
|
6208
5709
|
console.log("");
|
|
6209
5710
|
}
|
|
6210
5711
|
|
|
6211
5712
|
// src/commands/refresh.ts
|
|
6212
|
-
import
|
|
6213
|
-
import
|
|
6214
|
-
import
|
|
6215
|
-
import
|
|
5713
|
+
import fs24 from "fs";
|
|
5714
|
+
import path18 from "path";
|
|
5715
|
+
import chalk11 from "chalk";
|
|
5716
|
+
import ora5 from "ora";
|
|
6216
5717
|
|
|
6217
5718
|
// src/lib/git-diff.ts
|
|
6218
5719
|
import { execSync as execSync8 } from "child_process";
|
|
@@ -6287,37 +5788,37 @@ function collectDiff(lastSha) {
|
|
|
6287
5788
|
}
|
|
6288
5789
|
|
|
6289
5790
|
// src/writers/refresh.ts
|
|
6290
|
-
import
|
|
6291
|
-
import
|
|
5791
|
+
import fs23 from "fs";
|
|
5792
|
+
import path17 from "path";
|
|
6292
5793
|
function writeRefreshDocs(docs) {
|
|
6293
5794
|
const written = [];
|
|
6294
5795
|
if (docs.claudeMd) {
|
|
6295
|
-
|
|
5796
|
+
fs23.writeFileSync("CLAUDE.md", docs.claudeMd);
|
|
6296
5797
|
written.push("CLAUDE.md");
|
|
6297
5798
|
}
|
|
6298
5799
|
if (docs.readmeMd) {
|
|
6299
|
-
|
|
5800
|
+
fs23.writeFileSync("README.md", docs.readmeMd);
|
|
6300
5801
|
written.push("README.md");
|
|
6301
5802
|
}
|
|
6302
5803
|
if (docs.cursorrules) {
|
|
6303
|
-
|
|
5804
|
+
fs23.writeFileSync(".cursorrules", docs.cursorrules);
|
|
6304
5805
|
written.push(".cursorrules");
|
|
6305
5806
|
}
|
|
6306
5807
|
if (docs.cursorRules) {
|
|
6307
|
-
const rulesDir =
|
|
6308
|
-
if (!
|
|
5808
|
+
const rulesDir = path17.join(".cursor", "rules");
|
|
5809
|
+
if (!fs23.existsSync(rulesDir)) fs23.mkdirSync(rulesDir, { recursive: true });
|
|
6309
5810
|
for (const rule of docs.cursorRules) {
|
|
6310
|
-
const filePath =
|
|
6311
|
-
|
|
5811
|
+
const filePath = path17.join(rulesDir, rule.filename);
|
|
5812
|
+
fs23.writeFileSync(filePath, rule.content);
|
|
6312
5813
|
written.push(filePath);
|
|
6313
5814
|
}
|
|
6314
5815
|
}
|
|
6315
5816
|
if (docs.claudeSkills) {
|
|
6316
|
-
const skillsDir =
|
|
6317
|
-
if (!
|
|
5817
|
+
const skillsDir = path17.join(".claude", "skills");
|
|
5818
|
+
if (!fs23.existsSync(skillsDir)) fs23.mkdirSync(skillsDir, { recursive: true });
|
|
6318
5819
|
for (const skill of docs.claudeSkills) {
|
|
6319
|
-
const filePath =
|
|
6320
|
-
|
|
5820
|
+
const filePath = path17.join(skillsDir, skill.filename);
|
|
5821
|
+
fs23.writeFileSync(filePath, skill.content);
|
|
6321
5822
|
written.push(filePath);
|
|
6322
5823
|
}
|
|
6323
5824
|
}
|
|
@@ -6394,11 +5895,11 @@ function log(quiet, ...args) {
|
|
|
6394
5895
|
function discoverGitRepos(parentDir) {
|
|
6395
5896
|
const repos = [];
|
|
6396
5897
|
try {
|
|
6397
|
-
const entries =
|
|
5898
|
+
const entries = fs24.readdirSync(parentDir, { withFileTypes: true });
|
|
6398
5899
|
for (const entry of entries) {
|
|
6399
5900
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
6400
|
-
const childPath =
|
|
6401
|
-
if (
|
|
5901
|
+
const childPath = path18.join(parentDir, entry.name);
|
|
5902
|
+
if (fs24.existsSync(path18.join(childPath, ".git"))) {
|
|
6402
5903
|
repos.push(childPath);
|
|
6403
5904
|
}
|
|
6404
5905
|
}
|
|
@@ -6408,7 +5909,7 @@ function discoverGitRepos(parentDir) {
|
|
|
6408
5909
|
}
|
|
6409
5910
|
async function refreshSingleRepo(repoDir, options) {
|
|
6410
5911
|
const quiet = !!options.quiet;
|
|
6411
|
-
const prefix = options.label ? `${
|
|
5912
|
+
const prefix = options.label ? `${chalk11.bold(options.label)} ` : "";
|
|
6412
5913
|
const state = readState();
|
|
6413
5914
|
const lastSha = state?.lastRefreshSha ?? null;
|
|
6414
5915
|
const diff = collectDiff(lastSha);
|
|
@@ -6417,10 +5918,10 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
6417
5918
|
if (currentSha) {
|
|
6418
5919
|
writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
6419
5920
|
}
|
|
6420
|
-
log(quiet,
|
|
5921
|
+
log(quiet, chalk11.dim(`${prefix}No changes since last refresh.`));
|
|
6421
5922
|
return;
|
|
6422
5923
|
}
|
|
6423
|
-
const spinner = quiet ? null :
|
|
5924
|
+
const spinner = quiet ? null : ora5(`${prefix}Analyzing changes...`).start();
|
|
6424
5925
|
const existingDocs = readExistingConfigs(repoDir);
|
|
6425
5926
|
const fingerprint = await collectFingerprint(repoDir);
|
|
6426
5927
|
const projectContext = {
|
|
@@ -6449,10 +5950,10 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
6449
5950
|
if (options.dryRun) {
|
|
6450
5951
|
spinner?.info(`${prefix}Dry run \u2014 would update:`);
|
|
6451
5952
|
for (const doc of response.docsUpdated) {
|
|
6452
|
-
console.log(` ${
|
|
5953
|
+
console.log(` ${chalk11.yellow("~")} ${doc}`);
|
|
6453
5954
|
}
|
|
6454
5955
|
if (response.changesSummary) {
|
|
6455
|
-
console.log(
|
|
5956
|
+
console.log(chalk11.dim(`
|
|
6456
5957
|
${response.changesSummary}`));
|
|
6457
5958
|
}
|
|
6458
5959
|
return;
|
|
@@ -6460,10 +5961,10 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
6460
5961
|
const written = writeRefreshDocs(response.updatedDocs);
|
|
6461
5962
|
spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
|
|
6462
5963
|
for (const file of written) {
|
|
6463
|
-
log(quiet, ` ${
|
|
5964
|
+
log(quiet, ` ${chalk11.green("\u2713")} ${file}`);
|
|
6464
5965
|
}
|
|
6465
5966
|
if (response.changesSummary) {
|
|
6466
|
-
log(quiet,
|
|
5967
|
+
log(quiet, chalk11.dim(`
|
|
6467
5968
|
${response.changesSummary}`));
|
|
6468
5969
|
}
|
|
6469
5970
|
if (currentSha) {
|
|
@@ -6476,7 +5977,7 @@ async function refreshCommand(options) {
|
|
|
6476
5977
|
const config = loadConfig();
|
|
6477
5978
|
if (!config) {
|
|
6478
5979
|
if (quiet) return;
|
|
6479
|
-
console.log(
|
|
5980
|
+
console.log(chalk11.red("No LLM provider configured. Run ") + chalk11.hex("#83D1EB")("caliber config") + chalk11.red(" (e.g. choose Cursor) or set an API key."));
|
|
6480
5981
|
throw new Error("__exit__");
|
|
6481
5982
|
}
|
|
6482
5983
|
if (isGitRepo()) {
|
|
@@ -6486,20 +5987,20 @@ async function refreshCommand(options) {
|
|
|
6486
5987
|
const repos = discoverGitRepos(process.cwd());
|
|
6487
5988
|
if (repos.length === 0) {
|
|
6488
5989
|
if (quiet) return;
|
|
6489
|
-
console.log(
|
|
5990
|
+
console.log(chalk11.red("Not inside a git repository and no git repos found in child directories."));
|
|
6490
5991
|
throw new Error("__exit__");
|
|
6491
5992
|
}
|
|
6492
|
-
log(quiet,
|
|
5993
|
+
log(quiet, chalk11.dim(`Found ${repos.length} git repo${repos.length === 1 ? "" : "s"}
|
|
6493
5994
|
`));
|
|
6494
5995
|
const originalDir = process.cwd();
|
|
6495
5996
|
for (const repo of repos) {
|
|
6496
|
-
const repoName =
|
|
5997
|
+
const repoName = path18.basename(repo);
|
|
6497
5998
|
try {
|
|
6498
5999
|
process.chdir(repo);
|
|
6499
6000
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
6500
6001
|
} catch (err) {
|
|
6501
6002
|
if (err instanceof Error && err.message === "__exit__") continue;
|
|
6502
|
-
log(quiet,
|
|
6003
|
+
log(quiet, chalk11.yellow(`${repoName}: refresh failed \u2014 ${err instanceof Error ? err.message : "unknown error"}`));
|
|
6503
6004
|
}
|
|
6504
6005
|
}
|
|
6505
6006
|
process.chdir(originalDir);
|
|
@@ -6507,13 +6008,13 @@ async function refreshCommand(options) {
|
|
|
6507
6008
|
if (err instanceof Error && err.message === "__exit__") throw err;
|
|
6508
6009
|
if (quiet) return;
|
|
6509
6010
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
6510
|
-
console.log(
|
|
6011
|
+
console.log(chalk11.red(`Refresh failed: ${msg}`));
|
|
6511
6012
|
throw new Error("__exit__");
|
|
6512
6013
|
}
|
|
6513
6014
|
}
|
|
6514
6015
|
|
|
6515
6016
|
// src/commands/hooks.ts
|
|
6516
|
-
import
|
|
6017
|
+
import chalk12 from "chalk";
|
|
6517
6018
|
var HOOKS = [
|
|
6518
6019
|
{
|
|
6519
6020
|
id: "session-end",
|
|
@@ -6533,13 +6034,13 @@ var HOOKS = [
|
|
|
6533
6034
|
}
|
|
6534
6035
|
];
|
|
6535
6036
|
function printStatus() {
|
|
6536
|
-
console.log(
|
|
6037
|
+
console.log(chalk12.bold("\n Hooks\n"));
|
|
6537
6038
|
for (const hook of HOOKS) {
|
|
6538
6039
|
const installed = hook.isInstalled();
|
|
6539
|
-
const icon = installed ?
|
|
6540
|
-
const state = installed ?
|
|
6040
|
+
const icon = installed ? chalk12.green("\u2713") : chalk12.dim("\u2717");
|
|
6041
|
+
const state = installed ? chalk12.green("enabled") : chalk12.dim("disabled");
|
|
6541
6042
|
console.log(` ${icon} ${hook.label.padEnd(26)} ${state}`);
|
|
6542
|
-
console.log(
|
|
6043
|
+
console.log(chalk12.dim(` ${hook.description}`));
|
|
6543
6044
|
}
|
|
6544
6045
|
console.log("");
|
|
6545
6046
|
}
|
|
@@ -6548,9 +6049,9 @@ async function hooksCommand(options) {
|
|
|
6548
6049
|
for (const hook of HOOKS) {
|
|
6549
6050
|
const result = hook.install();
|
|
6550
6051
|
if (result.alreadyInstalled) {
|
|
6551
|
-
console.log(
|
|
6052
|
+
console.log(chalk12.dim(` ${hook.label} already enabled.`));
|
|
6552
6053
|
} else {
|
|
6553
|
-
console.log(
|
|
6054
|
+
console.log(chalk12.green(" \u2713") + ` ${hook.label} enabled`);
|
|
6554
6055
|
}
|
|
6555
6056
|
}
|
|
6556
6057
|
return;
|
|
@@ -6559,9 +6060,9 @@ async function hooksCommand(options) {
|
|
|
6559
6060
|
for (const hook of HOOKS) {
|
|
6560
6061
|
const result = hook.remove();
|
|
6561
6062
|
if (result.notFound) {
|
|
6562
|
-
console.log(
|
|
6063
|
+
console.log(chalk12.dim(` ${hook.label} already disabled.`));
|
|
6563
6064
|
} else {
|
|
6564
|
-
console.log(
|
|
6065
|
+
console.log(chalk12.green(" \u2713") + ` ${hook.label} removed`);
|
|
6565
6066
|
}
|
|
6566
6067
|
}
|
|
6567
6068
|
return;
|
|
@@ -6576,18 +6077,18 @@ async function hooksCommand(options) {
|
|
|
6576
6077
|
const states = HOOKS.map((h) => h.isInstalled());
|
|
6577
6078
|
function render() {
|
|
6578
6079
|
const lines = [];
|
|
6579
|
-
lines.push(
|
|
6080
|
+
lines.push(chalk12.bold(" Hooks"));
|
|
6580
6081
|
lines.push("");
|
|
6581
6082
|
for (let i = 0; i < HOOKS.length; i++) {
|
|
6582
6083
|
const hook = HOOKS[i];
|
|
6583
6084
|
const enabled = states[i];
|
|
6584
|
-
const toggle = enabled ?
|
|
6585
|
-
const ptr = i === cursor ?
|
|
6085
|
+
const toggle = enabled ? chalk12.green("[on] ") : chalk12.dim("[off]");
|
|
6086
|
+
const ptr = i === cursor ? chalk12.cyan(">") : " ";
|
|
6586
6087
|
lines.push(` ${ptr} ${toggle} ${hook.label}`);
|
|
6587
|
-
lines.push(
|
|
6088
|
+
lines.push(chalk12.dim(` ${hook.description}`));
|
|
6588
6089
|
}
|
|
6589
6090
|
lines.push("");
|
|
6590
|
-
lines.push(
|
|
6091
|
+
lines.push(chalk12.dim(" \u2191\u2193 navigate \u23B5 toggle a all on n all off \u23CE apply q cancel"));
|
|
6591
6092
|
return lines.join("\n");
|
|
6592
6093
|
}
|
|
6593
6094
|
function draw(initial) {
|
|
@@ -6618,16 +6119,16 @@ async function hooksCommand(options) {
|
|
|
6618
6119
|
const wantEnabled = states[i];
|
|
6619
6120
|
if (wantEnabled && !wasInstalled) {
|
|
6620
6121
|
hook.install();
|
|
6621
|
-
console.log(
|
|
6122
|
+
console.log(chalk12.green(" \u2713") + ` ${hook.label} enabled`);
|
|
6622
6123
|
changed++;
|
|
6623
6124
|
} else if (!wantEnabled && wasInstalled) {
|
|
6624
6125
|
hook.remove();
|
|
6625
|
-
console.log(
|
|
6126
|
+
console.log(chalk12.green(" \u2713") + ` ${hook.label} disabled`);
|
|
6626
6127
|
changed++;
|
|
6627
6128
|
}
|
|
6628
6129
|
}
|
|
6629
6130
|
if (changed === 0) {
|
|
6630
|
-
console.log(
|
|
6131
|
+
console.log(chalk12.dim(" No changes."));
|
|
6631
6132
|
}
|
|
6632
6133
|
console.log("");
|
|
6633
6134
|
}
|
|
@@ -6663,7 +6164,7 @@ async function hooksCommand(options) {
|
|
|
6663
6164
|
case "\x1B":
|
|
6664
6165
|
case "":
|
|
6665
6166
|
cleanup();
|
|
6666
|
-
console.log(
|
|
6167
|
+
console.log(chalk12.dim("\n Cancelled.\n"));
|
|
6667
6168
|
resolve2();
|
|
6668
6169
|
break;
|
|
6669
6170
|
}
|
|
@@ -6673,48 +6174,48 @@ async function hooksCommand(options) {
|
|
|
6673
6174
|
}
|
|
6674
6175
|
|
|
6675
6176
|
// src/commands/config.ts
|
|
6676
|
-
import
|
|
6177
|
+
import chalk13 from "chalk";
|
|
6677
6178
|
async function configCommand() {
|
|
6678
6179
|
const existing = loadConfig();
|
|
6679
6180
|
if (existing) {
|
|
6680
6181
|
const displayModel = existing.model === "default" && existing.provider === "claude-cli" ? process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)" : existing.model;
|
|
6681
6182
|
const fastModel = getFastModel();
|
|
6682
|
-
console.log(
|
|
6683
|
-
console.log(` Provider: ${
|
|
6684
|
-
console.log(` Model: ${
|
|
6183
|
+
console.log(chalk13.bold("\nCurrent Configuration\n"));
|
|
6184
|
+
console.log(` Provider: ${chalk13.cyan(existing.provider)}`);
|
|
6185
|
+
console.log(` Model: ${chalk13.cyan(displayModel)}`);
|
|
6685
6186
|
if (fastModel) {
|
|
6686
|
-
console.log(` Scan: ${
|
|
6187
|
+
console.log(` Scan: ${chalk13.cyan(fastModel)}`);
|
|
6687
6188
|
}
|
|
6688
6189
|
if (existing.apiKey) {
|
|
6689
6190
|
const masked = existing.apiKey.slice(0, 8) + "..." + existing.apiKey.slice(-4);
|
|
6690
|
-
console.log(` API Key: ${
|
|
6191
|
+
console.log(` API Key: ${chalk13.dim(masked)}`);
|
|
6691
6192
|
}
|
|
6692
6193
|
if (existing.provider === "cursor") {
|
|
6693
|
-
console.log(` Seat: ${
|
|
6194
|
+
console.log(` Seat: ${chalk13.dim("Cursor (agent acp)")}`);
|
|
6694
6195
|
}
|
|
6695
6196
|
if (existing.provider === "claude-cli") {
|
|
6696
|
-
console.log(` Seat: ${
|
|
6197
|
+
console.log(` Seat: ${chalk13.dim("Claude Code (claude -p)")}`);
|
|
6697
6198
|
}
|
|
6698
6199
|
if (existing.baseUrl) {
|
|
6699
|
-
console.log(` Base URL: ${
|
|
6200
|
+
console.log(` Base URL: ${chalk13.dim(existing.baseUrl)}`);
|
|
6700
6201
|
}
|
|
6701
6202
|
if (existing.vertexProjectId) {
|
|
6702
|
-
console.log(` Vertex Project: ${
|
|
6703
|
-
console.log(` Vertex Region: ${
|
|
6203
|
+
console.log(` Vertex Project: ${chalk13.dim(existing.vertexProjectId)}`);
|
|
6204
|
+
console.log(` Vertex Region: ${chalk13.dim(existing.vertexRegion || "us-east5")}`);
|
|
6704
6205
|
}
|
|
6705
|
-
console.log(` Source: ${
|
|
6206
|
+
console.log(` Source: ${chalk13.dim(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.VERTEX_PROJECT_ID || process.env.CALIBER_USE_CURSOR_SEAT || process.env.CALIBER_USE_CLAUDE_CLI ? "environment variables" : getConfigFilePath())}`);
|
|
6706
6207
|
console.log("");
|
|
6707
6208
|
}
|
|
6708
6209
|
await runInteractiveProviderSetup();
|
|
6709
|
-
console.log(
|
|
6710
|
-
console.log(
|
|
6210
|
+
console.log(chalk13.green("\n\u2713 Configuration saved"));
|
|
6211
|
+
console.log(chalk13.dim(` ${getConfigFilePath()}
|
|
6711
6212
|
`));
|
|
6712
|
-
console.log(
|
|
6713
|
-
console.log(
|
|
6213
|
+
console.log(chalk13.dim(" You can also set environment variables instead:"));
|
|
6214
|
+
console.log(chalk13.dim(" ANTHROPIC_API_KEY, OPENAI_API_KEY, VERTEX_PROJECT_ID, CALIBER_USE_CURSOR_SEAT=1, or CALIBER_USE_CLAUDE_CLI=1\n"));
|
|
6714
6215
|
}
|
|
6715
6216
|
|
|
6716
6217
|
// src/commands/learn.ts
|
|
6717
|
-
import
|
|
6218
|
+
import chalk14 from "chalk";
|
|
6718
6219
|
|
|
6719
6220
|
// src/learner/stdin.ts
|
|
6720
6221
|
var STDIN_TIMEOUT_MS = 5e3;
|
|
@@ -6745,8 +6246,8 @@ function readStdin() {
|
|
|
6745
6246
|
|
|
6746
6247
|
// src/learner/storage.ts
|
|
6747
6248
|
init_constants();
|
|
6748
|
-
import
|
|
6749
|
-
import
|
|
6249
|
+
import fs25 from "fs";
|
|
6250
|
+
import path19 from "path";
|
|
6750
6251
|
var MAX_RESPONSE_LENGTH = 2e3;
|
|
6751
6252
|
var DEFAULT_STATE = {
|
|
6752
6253
|
sessionId: null,
|
|
@@ -6754,15 +6255,15 @@ var DEFAULT_STATE = {
|
|
|
6754
6255
|
lastAnalysisTimestamp: null
|
|
6755
6256
|
};
|
|
6756
6257
|
function ensureLearningDir() {
|
|
6757
|
-
if (!
|
|
6758
|
-
|
|
6258
|
+
if (!fs25.existsSync(LEARNING_DIR)) {
|
|
6259
|
+
fs25.mkdirSync(LEARNING_DIR, { recursive: true });
|
|
6759
6260
|
}
|
|
6760
6261
|
}
|
|
6761
6262
|
function sessionFilePath() {
|
|
6762
|
-
return
|
|
6263
|
+
return path19.join(LEARNING_DIR, LEARNING_SESSION_FILE);
|
|
6763
6264
|
}
|
|
6764
6265
|
function stateFilePath() {
|
|
6765
|
-
return
|
|
6266
|
+
return path19.join(LEARNING_DIR, LEARNING_STATE_FILE);
|
|
6766
6267
|
}
|
|
6767
6268
|
function truncateResponse(response) {
|
|
6768
6269
|
const str = JSON.stringify(response);
|
|
@@ -6773,50 +6274,50 @@ function appendEvent(event) {
|
|
|
6773
6274
|
ensureLearningDir();
|
|
6774
6275
|
const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
|
|
6775
6276
|
const filePath = sessionFilePath();
|
|
6776
|
-
|
|
6277
|
+
fs25.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
6777
6278
|
const count = getEventCount();
|
|
6778
6279
|
if (count > LEARNING_MAX_EVENTS) {
|
|
6779
|
-
const lines =
|
|
6280
|
+
const lines = fs25.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
6780
6281
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
6781
|
-
|
|
6282
|
+
fs25.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
6782
6283
|
}
|
|
6783
6284
|
}
|
|
6784
6285
|
function readAllEvents() {
|
|
6785
6286
|
const filePath = sessionFilePath();
|
|
6786
|
-
if (!
|
|
6787
|
-
const lines =
|
|
6287
|
+
if (!fs25.existsSync(filePath)) return [];
|
|
6288
|
+
const lines = fs25.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
6788
6289
|
return lines.map((line) => JSON.parse(line));
|
|
6789
6290
|
}
|
|
6790
6291
|
function getEventCount() {
|
|
6791
6292
|
const filePath = sessionFilePath();
|
|
6792
|
-
if (!
|
|
6793
|
-
const content =
|
|
6293
|
+
if (!fs25.existsSync(filePath)) return 0;
|
|
6294
|
+
const content = fs25.readFileSync(filePath, "utf-8");
|
|
6794
6295
|
return content.split("\n").filter(Boolean).length;
|
|
6795
6296
|
}
|
|
6796
6297
|
function clearSession() {
|
|
6797
6298
|
const filePath = sessionFilePath();
|
|
6798
|
-
if (
|
|
6299
|
+
if (fs25.existsSync(filePath)) fs25.unlinkSync(filePath);
|
|
6799
6300
|
}
|
|
6800
6301
|
function readState2() {
|
|
6801
6302
|
const filePath = stateFilePath();
|
|
6802
|
-
if (!
|
|
6303
|
+
if (!fs25.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
6803
6304
|
try {
|
|
6804
|
-
return JSON.parse(
|
|
6305
|
+
return JSON.parse(fs25.readFileSync(filePath, "utf-8"));
|
|
6805
6306
|
} catch {
|
|
6806
6307
|
return { ...DEFAULT_STATE };
|
|
6807
6308
|
}
|
|
6808
6309
|
}
|
|
6809
6310
|
function writeState2(state) {
|
|
6810
6311
|
ensureLearningDir();
|
|
6811
|
-
|
|
6312
|
+
fs25.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
|
|
6812
6313
|
}
|
|
6813
6314
|
function resetState() {
|
|
6814
6315
|
writeState2({ ...DEFAULT_STATE });
|
|
6815
6316
|
}
|
|
6816
6317
|
|
|
6817
6318
|
// src/learner/writer.ts
|
|
6818
|
-
import
|
|
6819
|
-
import
|
|
6319
|
+
import fs26 from "fs";
|
|
6320
|
+
import path20 from "path";
|
|
6820
6321
|
var LEARNED_START = "<!-- caliber:learned -->";
|
|
6821
6322
|
var LEARNED_END = "<!-- /caliber:learned -->";
|
|
6822
6323
|
function writeLearnedContent(update) {
|
|
@@ -6836,8 +6337,8 @@ function writeLearnedContent(update) {
|
|
|
6836
6337
|
function writeLearnedSection(content) {
|
|
6837
6338
|
const claudeMdPath = "CLAUDE.md";
|
|
6838
6339
|
let existing = "";
|
|
6839
|
-
if (
|
|
6840
|
-
existing =
|
|
6340
|
+
if (fs26.existsSync(claudeMdPath)) {
|
|
6341
|
+
existing = fs26.readFileSync(claudeMdPath, "utf-8");
|
|
6841
6342
|
}
|
|
6842
6343
|
const section = `${LEARNED_START}
|
|
6843
6344
|
${content}
|
|
@@ -6851,15 +6352,15 @@ ${LEARNED_END}`;
|
|
|
6851
6352
|
const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
6852
6353
|
updated = existing + separator + "\n" + section + "\n";
|
|
6853
6354
|
}
|
|
6854
|
-
|
|
6355
|
+
fs26.writeFileSync(claudeMdPath, updated);
|
|
6855
6356
|
}
|
|
6856
6357
|
function writeLearnedSkill(skill) {
|
|
6857
|
-
const skillDir =
|
|
6858
|
-
if (!
|
|
6859
|
-
const skillPath =
|
|
6860
|
-
if (!skill.isNew &&
|
|
6861
|
-
const existing =
|
|
6862
|
-
|
|
6358
|
+
const skillDir = path20.join(".claude", "skills", skill.name);
|
|
6359
|
+
if (!fs26.existsSync(skillDir)) fs26.mkdirSync(skillDir, { recursive: true });
|
|
6360
|
+
const skillPath = path20.join(skillDir, "SKILL.md");
|
|
6361
|
+
if (!skill.isNew && fs26.existsSync(skillPath)) {
|
|
6362
|
+
const existing = fs26.readFileSync(skillPath, "utf-8");
|
|
6363
|
+
fs26.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
6863
6364
|
} else {
|
|
6864
6365
|
const frontmatter = [
|
|
6865
6366
|
"---",
|
|
@@ -6868,14 +6369,14 @@ function writeLearnedSkill(skill) {
|
|
|
6868
6369
|
"---",
|
|
6869
6370
|
""
|
|
6870
6371
|
].join("\n");
|
|
6871
|
-
|
|
6372
|
+
fs26.writeFileSync(skillPath, frontmatter + skill.content);
|
|
6872
6373
|
}
|
|
6873
6374
|
return skillPath;
|
|
6874
6375
|
}
|
|
6875
6376
|
function readLearnedSection() {
|
|
6876
6377
|
const claudeMdPath = "CLAUDE.md";
|
|
6877
|
-
if (!
|
|
6878
|
-
const content =
|
|
6378
|
+
if (!fs26.existsSync(claudeMdPath)) return null;
|
|
6379
|
+
const content = fs26.readFileSync(claudeMdPath, "utf-8");
|
|
6879
6380
|
const startIdx = content.indexOf(LEARNED_START);
|
|
6880
6381
|
const endIdx = content.indexOf(LEARNED_END);
|
|
6881
6382
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
@@ -6945,10 +6446,12 @@ ${skillsSummary}`);
|
|
|
6945
6446
|
const prompt = `${contextParts.length ? contextParts.join("\n\n---\n\n") + "\n\n---\n\n" : ""}## Tool Events from Session (${fittedEvents.length} events)
|
|
6946
6447
|
|
|
6947
6448
|
${eventsText}`;
|
|
6449
|
+
const fastModel = getFastModel();
|
|
6948
6450
|
const raw = await llmCall({
|
|
6949
6451
|
system: LEARN_SYSTEM_PROMPT,
|
|
6950
6452
|
prompt,
|
|
6951
|
-
maxTokens: 4096
|
|
6453
|
+
maxTokens: 4096,
|
|
6454
|
+
...fastModel ? { model: fastModel } : {}
|
|
6952
6455
|
});
|
|
6953
6456
|
return parseAnalysisResponse(raw);
|
|
6954
6457
|
}
|
|
@@ -7015,53 +6518,53 @@ async function learnFinalizeCommand() {
|
|
|
7015
6518
|
async function learnInstallCommand() {
|
|
7016
6519
|
const result = installLearningHooks();
|
|
7017
6520
|
if (result.alreadyInstalled) {
|
|
7018
|
-
console.log(
|
|
6521
|
+
console.log(chalk14.dim("Learning hooks already installed."));
|
|
7019
6522
|
return;
|
|
7020
6523
|
}
|
|
7021
|
-
console.log(
|
|
7022
|
-
console.log(
|
|
7023
|
-
console.log(
|
|
6524
|
+
console.log(chalk14.green("\u2713") + " Learning hooks installed in .claude/settings.json");
|
|
6525
|
+
console.log(chalk14.dim(" PostToolUse, PostToolUseFailure, and SessionEnd hooks active."));
|
|
6526
|
+
console.log(chalk14.dim(" Session learnings will be written to CLAUDE.md and skills."));
|
|
7024
6527
|
}
|
|
7025
6528
|
async function learnRemoveCommand() {
|
|
7026
6529
|
const result = removeLearningHooks();
|
|
7027
6530
|
if (result.notFound) {
|
|
7028
|
-
console.log(
|
|
6531
|
+
console.log(chalk14.dim("Learning hooks not found."));
|
|
7029
6532
|
return;
|
|
7030
6533
|
}
|
|
7031
|
-
console.log(
|
|
6534
|
+
console.log(chalk14.green("\u2713") + " Learning hooks removed from .claude/settings.json");
|
|
7032
6535
|
}
|
|
7033
6536
|
async function learnStatusCommand() {
|
|
7034
6537
|
const installed = areLearningHooksInstalled();
|
|
7035
6538
|
const state = readState2();
|
|
7036
6539
|
const eventCount = getEventCount();
|
|
7037
|
-
console.log(
|
|
6540
|
+
console.log(chalk14.bold("Session Learning Status"));
|
|
7038
6541
|
console.log();
|
|
7039
6542
|
if (installed) {
|
|
7040
|
-
console.log(
|
|
6543
|
+
console.log(chalk14.green("\u2713") + " Learning hooks are " + chalk14.green("installed"));
|
|
7041
6544
|
} else {
|
|
7042
|
-
console.log(
|
|
7043
|
-
console.log(
|
|
6545
|
+
console.log(chalk14.dim("\u2717") + " Learning hooks are " + chalk14.yellow("not installed"));
|
|
6546
|
+
console.log(chalk14.dim(" Run `caliber learn install` to enable session learning."));
|
|
7044
6547
|
}
|
|
7045
6548
|
console.log();
|
|
7046
|
-
console.log(`Events recorded: ${
|
|
7047
|
-
console.log(`Total this session: ${
|
|
6549
|
+
console.log(`Events recorded: ${chalk14.cyan(String(eventCount))}`);
|
|
6550
|
+
console.log(`Total this session: ${chalk14.cyan(String(state.eventCount))}`);
|
|
7048
6551
|
if (state.lastAnalysisTimestamp) {
|
|
7049
|
-
console.log(`Last analysis: ${
|
|
6552
|
+
console.log(`Last analysis: ${chalk14.cyan(state.lastAnalysisTimestamp)}`);
|
|
7050
6553
|
} else {
|
|
7051
|
-
console.log(`Last analysis: ${
|
|
6554
|
+
console.log(`Last analysis: ${chalk14.dim("none")}`);
|
|
7052
6555
|
}
|
|
7053
6556
|
const learnedSection = readLearnedSection();
|
|
7054
6557
|
if (learnedSection) {
|
|
7055
6558
|
const lineCount = learnedSection.split("\n").filter(Boolean).length;
|
|
7056
6559
|
console.log(`
|
|
7057
|
-
Learned items in CLAUDE.md: ${
|
|
6560
|
+
Learned items in CLAUDE.md: ${chalk14.cyan(String(lineCount))}`);
|
|
7058
6561
|
}
|
|
7059
6562
|
}
|
|
7060
6563
|
|
|
7061
6564
|
// src/cli.ts
|
|
7062
|
-
var __dirname =
|
|
6565
|
+
var __dirname = path21.dirname(fileURLToPath(import.meta.url));
|
|
7063
6566
|
var pkg = JSON.parse(
|
|
7064
|
-
|
|
6567
|
+
fs27.readFileSync(path21.resolve(__dirname, "..", "package.json"), "utf-8")
|
|
7065
6568
|
);
|
|
7066
6569
|
var program = new Command();
|
|
7067
6570
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
|
|
@@ -7094,22 +6597,22 @@ learn.command("remove").description("Remove learning hooks from .claude/settings
|
|
|
7094
6597
|
learn.command("status").description("Show learning system status").action(learnStatusCommand);
|
|
7095
6598
|
|
|
7096
6599
|
// src/utils/version-check.ts
|
|
7097
|
-
import
|
|
7098
|
-
import
|
|
6600
|
+
import fs28 from "fs";
|
|
6601
|
+
import path22 from "path";
|
|
7099
6602
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7100
6603
|
import { execSync as execSync9 } from "child_process";
|
|
7101
|
-
import
|
|
7102
|
-
import
|
|
6604
|
+
import chalk15 from "chalk";
|
|
6605
|
+
import ora6 from "ora";
|
|
7103
6606
|
import confirm from "@inquirer/confirm";
|
|
7104
|
-
var __dirname_vc =
|
|
6607
|
+
var __dirname_vc = path22.dirname(fileURLToPath2(import.meta.url));
|
|
7105
6608
|
var pkg2 = JSON.parse(
|
|
7106
|
-
|
|
6609
|
+
fs28.readFileSync(path22.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
7107
6610
|
);
|
|
7108
6611
|
function getInstalledVersion() {
|
|
7109
6612
|
try {
|
|
7110
6613
|
const globalRoot = execSync9("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
7111
|
-
const pkgPath =
|
|
7112
|
-
return JSON.parse(
|
|
6614
|
+
const pkgPath = path22.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
6615
|
+
return JSON.parse(fs28.readFileSync(pkgPath, "utf-8")).version;
|
|
7113
6616
|
} catch {
|
|
7114
6617
|
return null;
|
|
7115
6618
|
}
|
|
@@ -7132,17 +6635,17 @@ async function checkForUpdates() {
|
|
|
7132
6635
|
const isInteractive = process.stdin.isTTY === true;
|
|
7133
6636
|
if (!isInteractive) {
|
|
7134
6637
|
console.log(
|
|
7135
|
-
|
|
6638
|
+
chalk15.yellow(
|
|
7136
6639
|
`
|
|
7137
6640
|
Update available: ${current} -> ${latest}
|
|
7138
|
-
Run ${
|
|
6641
|
+
Run ${chalk15.bold("npm install -g @rely-ai/caliber")} to upgrade.
|
|
7139
6642
|
`
|
|
7140
6643
|
)
|
|
7141
6644
|
);
|
|
7142
6645
|
return;
|
|
7143
6646
|
}
|
|
7144
6647
|
console.log(
|
|
7145
|
-
|
|
6648
|
+
chalk15.yellow(`
|
|
7146
6649
|
Update available: ${current} -> ${latest}`)
|
|
7147
6650
|
);
|
|
7148
6651
|
const shouldUpdate = await confirm({ message: "Would you like to update now? (Y/n)", default: true });
|
|
@@ -7150,7 +6653,7 @@ Update available: ${current} -> ${latest}`)
|
|
|
7150
6653
|
console.log();
|
|
7151
6654
|
return;
|
|
7152
6655
|
}
|
|
7153
|
-
const spinner =
|
|
6656
|
+
const spinner = ora6("Updating caliber...").start();
|
|
7154
6657
|
try {
|
|
7155
6658
|
execSync9(`npm install -g @rely-ai/caliber@${latest}`, {
|
|
7156
6659
|
stdio: "pipe",
|
|
@@ -7160,13 +6663,13 @@ Update available: ${current} -> ${latest}`)
|
|
|
7160
6663
|
const installed = getInstalledVersion();
|
|
7161
6664
|
if (installed !== latest) {
|
|
7162
6665
|
spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
|
|
7163
|
-
console.log(
|
|
6666
|
+
console.log(chalk15.yellow(`Run ${chalk15.bold(`npm install -g @rely-ai/caliber@${latest}`)} manually.
|
|
7164
6667
|
`));
|
|
7165
6668
|
return;
|
|
7166
6669
|
}
|
|
7167
|
-
spinner.succeed(
|
|
6670
|
+
spinner.succeed(chalk15.green(`Updated to ${latest}`));
|
|
7168
6671
|
const args = process.argv.slice(2);
|
|
7169
|
-
console.log(
|
|
6672
|
+
console.log(chalk15.dim(`
|
|
7170
6673
|
Restarting: caliber ${args.join(" ")}
|
|
7171
6674
|
`));
|
|
7172
6675
|
execSync9(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
@@ -7179,11 +6682,11 @@ Restarting: caliber ${args.join(" ")}
|
|
|
7179
6682
|
if (err instanceof Error) {
|
|
7180
6683
|
const stderr = err.stderr;
|
|
7181
6684
|
const errMsg = stderr ? String(stderr).trim().split("\n").pop() : err.message.split("\n")[0];
|
|
7182
|
-
if (errMsg && !errMsg.includes("SIGTERM")) console.log(
|
|
6685
|
+
if (errMsg && !errMsg.includes("SIGTERM")) console.log(chalk15.dim(` ${errMsg}`));
|
|
7183
6686
|
}
|
|
7184
6687
|
console.log(
|
|
7185
|
-
|
|
7186
|
-
`Run ${
|
|
6688
|
+
chalk15.yellow(
|
|
6689
|
+
`Run ${chalk15.bold(`npm install -g @rely-ai/caliber@${latest}`)} manually to upgrade.
|
|
7187
6690
|
`
|
|
7188
6691
|
)
|
|
7189
6692
|
);
|