@rely-ai/caliber 1.0.0 → 1.1.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 +1198 -364
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -51,16 +51,16 @@ 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 path24 from "path";
|
|
56
56
|
import { fileURLToPath } from "url";
|
|
57
57
|
|
|
58
58
|
// src/commands/onboard.ts
|
|
59
|
-
import
|
|
60
|
-
import
|
|
61
|
-
import
|
|
59
|
+
import chalk5 from "chalk";
|
|
60
|
+
import ora2 from "ora";
|
|
61
|
+
import readline4 from "readline";
|
|
62
62
|
import select2 from "@inquirer/select";
|
|
63
|
-
import
|
|
63
|
+
import fs20 from "fs";
|
|
64
64
|
|
|
65
65
|
// src/fingerprint/index.ts
|
|
66
66
|
import fs7 from "fs";
|
|
@@ -2127,16 +2127,25 @@ import path12 from "path";
|
|
|
2127
2127
|
var STAGED_DIR = path12.join(CALIBER_DIR, "staged");
|
|
2128
2128
|
var PROPOSED_DIR = path12.join(STAGED_DIR, "proposed");
|
|
2129
2129
|
var CURRENT_DIR = path12.join(STAGED_DIR, "current");
|
|
2130
|
+
function normalizeContent(content) {
|
|
2131
|
+
return content.split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
2132
|
+
}
|
|
2130
2133
|
function stageFiles(files, projectDir) {
|
|
2131
2134
|
cleanupStaging();
|
|
2132
2135
|
let newFiles = 0;
|
|
2133
2136
|
let modifiedFiles = 0;
|
|
2134
2137
|
const stagedFiles = [];
|
|
2135
2138
|
for (const file of files) {
|
|
2139
|
+
const originalPath = path12.join(projectDir, file.path);
|
|
2140
|
+
if (fs13.existsSync(originalPath)) {
|
|
2141
|
+
const existing = fs13.readFileSync(originalPath, "utf-8");
|
|
2142
|
+
if (normalizeContent(existing) === normalizeContent(file.content)) {
|
|
2143
|
+
continue;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2136
2146
|
const proposedPath = path12.join(PROPOSED_DIR, file.path);
|
|
2137
2147
|
fs13.mkdirSync(path12.dirname(proposedPath), { recursive: true });
|
|
2138
2148
|
fs13.writeFileSync(proposedPath, file.content);
|
|
2139
|
-
const originalPath = path12.join(projectDir, file.path);
|
|
2140
2149
|
if (fs13.existsSync(originalPath)) {
|
|
2141
2150
|
const currentPath = path12.join(CURRENT_DIR, file.path);
|
|
2142
2151
|
fs13.mkdirSync(path12.dirname(currentPath), { recursive: true });
|
|
@@ -2715,15 +2724,15 @@ function computeGrade(score) {
|
|
|
2715
2724
|
// src/scoring/checks/coverage.ts
|
|
2716
2725
|
import { readFileSync, readdirSync } from "fs";
|
|
2717
2726
|
import { join } from "path";
|
|
2718
|
-
function readFileOrNull(
|
|
2727
|
+
function readFileOrNull(path26) {
|
|
2719
2728
|
try {
|
|
2720
|
-
return readFileSync(
|
|
2729
|
+
return readFileSync(path26, "utf-8");
|
|
2721
2730
|
} catch {
|
|
2722
2731
|
return null;
|
|
2723
2732
|
}
|
|
2724
2733
|
}
|
|
2725
|
-
function readJsonOrNull(
|
|
2726
|
-
const content = readFileOrNull(
|
|
2734
|
+
function readJsonOrNull(path26) {
|
|
2735
|
+
const content = readFileOrNull(path26);
|
|
2727
2736
|
if (!content) return null;
|
|
2728
2737
|
try {
|
|
2729
2738
|
return JSON.parse(content);
|
|
@@ -3079,9 +3088,9 @@ function checkExistence(dir) {
|
|
|
3079
3088
|
// src/scoring/checks/quality.ts
|
|
3080
3089
|
import { readFileSync as readFileSync3 } from "fs";
|
|
3081
3090
|
import { join as join3 } from "path";
|
|
3082
|
-
function readFileOrNull2(
|
|
3091
|
+
function readFileOrNull2(path26) {
|
|
3083
3092
|
try {
|
|
3084
|
-
return readFileSync3(
|
|
3093
|
+
return readFileSync3(path26, "utf-8");
|
|
3085
3094
|
} catch {
|
|
3086
3095
|
return null;
|
|
3087
3096
|
}
|
|
@@ -3232,15 +3241,15 @@ function checkQuality(dir) {
|
|
|
3232
3241
|
// src/scoring/checks/accuracy.ts
|
|
3233
3242
|
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync } from "fs";
|
|
3234
3243
|
import { join as join4 } from "path";
|
|
3235
|
-
function readFileOrNull3(
|
|
3244
|
+
function readFileOrNull3(path26) {
|
|
3236
3245
|
try {
|
|
3237
|
-
return readFileSync4(
|
|
3246
|
+
return readFileSync4(path26, "utf-8");
|
|
3238
3247
|
} catch {
|
|
3239
3248
|
return null;
|
|
3240
3249
|
}
|
|
3241
3250
|
}
|
|
3242
|
-
function readJsonOrNull2(
|
|
3243
|
-
const content = readFileOrNull3(
|
|
3251
|
+
function readJsonOrNull2(path26) {
|
|
3252
|
+
const content = readFileOrNull3(path26);
|
|
3244
3253
|
if (!content) return null;
|
|
3245
3254
|
try {
|
|
3246
3255
|
return JSON.parse(content);
|
|
@@ -3423,9 +3432,9 @@ function checkAccuracy(dir) {
|
|
|
3423
3432
|
// src/scoring/checks/freshness.ts
|
|
3424
3433
|
import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
3425
3434
|
import { join as join5 } from "path";
|
|
3426
|
-
function readFileOrNull4(
|
|
3435
|
+
function readFileOrNull4(path26) {
|
|
3427
3436
|
try {
|
|
3428
|
-
return readFileSync5(
|
|
3437
|
+
return readFileSync5(path26, "utf-8");
|
|
3429
3438
|
} catch {
|
|
3430
3439
|
return null;
|
|
3431
3440
|
}
|
|
@@ -3535,9 +3544,9 @@ function checkFreshness(dir) {
|
|
|
3535
3544
|
import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "fs";
|
|
3536
3545
|
import { execSync as execSync7 } from "child_process";
|
|
3537
3546
|
import { join as join6 } from "path";
|
|
3538
|
-
function readFileOrNull5(
|
|
3547
|
+
function readFileOrNull5(path26) {
|
|
3539
3548
|
try {
|
|
3540
|
-
return readFileSync6(
|
|
3549
|
+
return readFileSync6(path26, "utf-8");
|
|
3541
3550
|
} catch {
|
|
3542
3551
|
return null;
|
|
3543
3552
|
}
|
|
@@ -3630,6 +3639,29 @@ function checkBonus(dir) {
|
|
|
3630
3639
|
return checks;
|
|
3631
3640
|
}
|
|
3632
3641
|
|
|
3642
|
+
// src/scoring/dismissed.ts
|
|
3643
|
+
init_constants();
|
|
3644
|
+
import fs17 from "fs";
|
|
3645
|
+
import path16 from "path";
|
|
3646
|
+
var DISMISSED_FILE = path16.join(CALIBER_DIR, "dismissed-checks.json");
|
|
3647
|
+
function readDismissedChecks() {
|
|
3648
|
+
try {
|
|
3649
|
+
if (!fs17.existsSync(DISMISSED_FILE)) return [];
|
|
3650
|
+
return JSON.parse(fs17.readFileSync(DISMISSED_FILE, "utf-8"));
|
|
3651
|
+
} catch {
|
|
3652
|
+
return [];
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
function writeDismissedChecks(checks) {
|
|
3656
|
+
if (!fs17.existsSync(CALIBER_DIR)) {
|
|
3657
|
+
fs17.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
3658
|
+
}
|
|
3659
|
+
fs17.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
|
|
3660
|
+
}
|
|
3661
|
+
function getDismissedIds() {
|
|
3662
|
+
return new Set(readDismissedChecks().map((c) => c.id));
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3633
3665
|
// src/scoring/index.ts
|
|
3634
3666
|
function sumCategory(checks, category) {
|
|
3635
3667
|
const categoryChecks = checks.filter((c) => c.category === category);
|
|
@@ -3666,7 +3698,8 @@ function computeLocalScore(dir, targetAgent) {
|
|
|
3666
3698
|
...checkFreshness(dir),
|
|
3667
3699
|
...checkBonus(dir)
|
|
3668
3700
|
];
|
|
3669
|
-
const
|
|
3701
|
+
const dismissed = getDismissedIds();
|
|
3702
|
+
const checks = filterChecksForTarget(allChecks, target).filter((c) => !dismissed.has(c.id));
|
|
3670
3703
|
const maxPossible = checks.reduce((s, c) => s + c.maxPoints, 0);
|
|
3671
3704
|
const earned = checks.reduce((s, c) => s + c.earnedPoints, 0);
|
|
3672
3705
|
const score = maxPossible > 0 ? Math.min(100, Math.max(0, Math.round(earned / maxPossible * 100))) : 0;
|
|
@@ -3772,7 +3805,7 @@ function displayScoreSummary(result) {
|
|
|
3772
3805
|
const remaining = failing.length - shown.length;
|
|
3773
3806
|
const moreText = remaining > 0 ? ` (+${remaining} more)` : "";
|
|
3774
3807
|
console.log(chalk3.dim(`
|
|
3775
|
-
Run ${chalk3.
|
|
3808
|
+
Run ${chalk3.hex("#83D1EB")("caliber score")} for details.${moreText}`));
|
|
3776
3809
|
}
|
|
3777
3810
|
console.log("");
|
|
3778
3811
|
}
|
|
@@ -3820,10 +3853,758 @@ function displayScoreDelta(before, after) {
|
|
|
3820
3853
|
}
|
|
3821
3854
|
}
|
|
3822
3855
|
|
|
3856
|
+
// src/mcp/index.ts
|
|
3857
|
+
import chalk4 from "chalk";
|
|
3858
|
+
import ora from "ora";
|
|
3859
|
+
import readline3 from "readline";
|
|
3860
|
+
import fs19 from "fs";
|
|
3861
|
+
import path18 from "path";
|
|
3862
|
+
|
|
3863
|
+
// src/mcp/deps.ts
|
|
3864
|
+
import fs18 from "fs";
|
|
3865
|
+
import path17 from "path";
|
|
3866
|
+
function extractAllDeps(dir) {
|
|
3867
|
+
const deps = /* @__PURE__ */ new Set();
|
|
3868
|
+
parsePackageJson(dir, deps);
|
|
3869
|
+
parseRequirementsTxt(dir, deps);
|
|
3870
|
+
parsePyprojectToml(dir, deps);
|
|
3871
|
+
parseGoMod(dir, deps);
|
|
3872
|
+
parseCargoToml(dir, deps);
|
|
3873
|
+
parseGemfile(dir, deps);
|
|
3874
|
+
parseComposerJson(dir, deps);
|
|
3875
|
+
return Array.from(deps);
|
|
3876
|
+
}
|
|
3877
|
+
function readFileSafe(filePath) {
|
|
3878
|
+
try {
|
|
3879
|
+
if (fs18.existsSync(filePath)) {
|
|
3880
|
+
return fs18.readFileSync(filePath, "utf-8");
|
|
3881
|
+
}
|
|
3882
|
+
} catch {
|
|
3883
|
+
}
|
|
3884
|
+
return null;
|
|
3885
|
+
}
|
|
3886
|
+
function parsePackageJson(dir, deps) {
|
|
3887
|
+
const content = readFileSafe(path17.join(dir, "package.json"));
|
|
3888
|
+
if (!content) return;
|
|
3889
|
+
try {
|
|
3890
|
+
const pkg3 = JSON.parse(content);
|
|
3891
|
+
const allDeps = {
|
|
3892
|
+
...pkg3.dependencies,
|
|
3893
|
+
...pkg3.devDependencies
|
|
3894
|
+
};
|
|
3895
|
+
for (const name of Object.keys(allDeps)) {
|
|
3896
|
+
deps.add(name);
|
|
3897
|
+
}
|
|
3898
|
+
} catch {
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
function parseRequirementsTxt(dir, deps) {
|
|
3902
|
+
const content = readFileSafe(path17.join(dir, "requirements.txt"));
|
|
3903
|
+
if (!content) return;
|
|
3904
|
+
for (const line of content.split("\n")) {
|
|
3905
|
+
const trimmed = line.trim();
|
|
3906
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-")) continue;
|
|
3907
|
+
const match = trimmed.match(/^([a-zA-Z0-9_-]+(?:\[[^\]]*\])?)/);
|
|
3908
|
+
if (match) {
|
|
3909
|
+
deps.add(match[1].replace(/\[.*\]/, "").toLowerCase());
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
function parsePyprojectToml(dir, deps) {
|
|
3914
|
+
const content = readFileSafe(path17.join(dir, "pyproject.toml"));
|
|
3915
|
+
if (!content) return;
|
|
3916
|
+
const depsMatch = content.match(/\bdependencies\s*=\s*\[([\s\S]*?)\]/);
|
|
3917
|
+
if (depsMatch) {
|
|
3918
|
+
const items = depsMatch[1].matchAll(/"([a-zA-Z0-9_-]+)/g);
|
|
3919
|
+
for (const m of items) {
|
|
3920
|
+
deps.add(m[1].toLowerCase());
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
function parseGoMod(dir, deps) {
|
|
3925
|
+
const content = readFileSafe(path17.join(dir, "go.mod"));
|
|
3926
|
+
if (!content) return;
|
|
3927
|
+
const requireBlock = content.match(/require\s*\(([\s\S]*?)\)/g);
|
|
3928
|
+
if (requireBlock) {
|
|
3929
|
+
for (const block of requireBlock) {
|
|
3930
|
+
const lines = block.split("\n");
|
|
3931
|
+
for (const line of lines) {
|
|
3932
|
+
const match = line.trim().match(/^([a-zA-Z0-9./\-_]+)\s/);
|
|
3933
|
+
if (match && !match[1].startsWith("//") && match[1].includes("/")) {
|
|
3934
|
+
const parts = match[1].split("/");
|
|
3935
|
+
deps.add(parts[parts.length - 1]);
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
function parseCargoToml(dir, deps) {
|
|
3942
|
+
const content = readFileSafe(path17.join(dir, "Cargo.toml"));
|
|
3943
|
+
if (!content) return;
|
|
3944
|
+
const sections = content.split(/\[/);
|
|
3945
|
+
for (const section of sections) {
|
|
3946
|
+
if (section.startsWith("dependencies]") || section.startsWith("dev-dependencies]")) {
|
|
3947
|
+
const lines = section.split("\n").slice(1);
|
|
3948
|
+
for (const line of lines) {
|
|
3949
|
+
if (line.startsWith("[")) break;
|
|
3950
|
+
const match = line.match(/^([a-zA-Z0-9_-]+)\s*=/);
|
|
3951
|
+
if (match) {
|
|
3952
|
+
deps.add(match[1]);
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
function parseGemfile(dir, deps) {
|
|
3959
|
+
const content = readFileSafe(path17.join(dir, "Gemfile"));
|
|
3960
|
+
if (!content) return;
|
|
3961
|
+
const gemPattern = /gem\s+['"]([a-zA-Z0-9_-]+)['"]/g;
|
|
3962
|
+
let match;
|
|
3963
|
+
while ((match = gemPattern.exec(content)) !== null) {
|
|
3964
|
+
deps.add(match[1]);
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
function parseComposerJson(dir, deps) {
|
|
3968
|
+
const content = readFileSafe(path17.join(dir, "composer.json"));
|
|
3969
|
+
if (!content) return;
|
|
3970
|
+
try {
|
|
3971
|
+
const composer = JSON.parse(content);
|
|
3972
|
+
const allDeps = {
|
|
3973
|
+
...composer.require,
|
|
3974
|
+
...composer["require-dev"]
|
|
3975
|
+
};
|
|
3976
|
+
for (const name of Object.keys(allDeps)) {
|
|
3977
|
+
if (name !== "php" && !name.startsWith("ext-")) {
|
|
3978
|
+
deps.add(name);
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
} catch {
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
|
|
3985
|
+
// src/mcp/prompts.ts
|
|
3986
|
+
var CLASSIFY_DEPS_PROMPT = `You classify software dependencies into two categories:
|
|
3987
|
+
|
|
3988
|
+
**Tools** (MCP-worthy): Services, platforms, APIs, databases, SaaS products, and cloud services that have their own web dashboards, APIs, or external infrastructure. Examples: supabase, stripe, sentry, datadog, firebase, mongodb, redis, slack, linear, github, vercel, aws-sdk, twilio, sendgrid, algolia, elasticsearch, prisma, planetscale, neon, clerk, auth0.
|
|
3989
|
+
|
|
3990
|
+
**Libraries** (skip): Utility packages, frameworks, build tools, test runners, and local-only code that does NOT connect to an external service. Examples: lodash, react, express, vitest, webpack, zod, chalk, commander, typescript, eslint, prettier, axios, dayjs, uuid.
|
|
3991
|
+
|
|
3992
|
+
Given a list of dependencies, return ONLY the tool dependencies as a JSON array of strings.
|
|
3993
|
+
Return ONLY the JSON array, no explanation.`;
|
|
3994
|
+
var SCORE_MCP_PROMPT = `You evaluate MCP (Model Context Protocol) server candidates for relevance to a software project.
|
|
3995
|
+
|
|
3996
|
+
Score each candidate from 0-100 based on:
|
|
3997
|
+
- **Relevance** (40%): How directly does it match the project's detected tool dependencies?
|
|
3998
|
+
- **Capabilities** (30%): Does it provide meaningful read+write operations (not just read-only)?
|
|
3999
|
+
- **Quality signals** (30%): Stars, recent activity, vendor/official status
|
|
4000
|
+
|
|
4001
|
+
Return a JSON array where each element has:
|
|
4002
|
+
- "index": the candidate's index number
|
|
4003
|
+
- "score": relevance score 0-100
|
|
4004
|
+
- "reason": one-liner explaining the score (max 80 chars)
|
|
4005
|
+
|
|
4006
|
+
Be selective. Only score candidates that would genuinely help developers working on this project.
|
|
4007
|
+
Return ONLY the JSON array.`;
|
|
4008
|
+
var EXTRACT_CONFIG_PROMPT = `You extract MCP server configuration from a README file.
|
|
4009
|
+
|
|
4010
|
+
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.
|
|
4011
|
+
|
|
4012
|
+
Return a JSON object with:
|
|
4013
|
+
- "command": the executable command (e.g., "npx", "uvx", "node", "docker")
|
|
4014
|
+
- "args": array of arguments (e.g., ["-y", "@supabase/mcp-server"])
|
|
4015
|
+
- "env": array of objects, each with:
|
|
4016
|
+
- "key": environment variable name (e.g., "SUPABASE_ACCESS_TOKEN")
|
|
4017
|
+
- "description": brief description of what this value is (e.g., "Personal access token from dashboard")
|
|
4018
|
+
- "required": boolean
|
|
4019
|
+
|
|
4020
|
+
If the README shows multiple configuration methods, prefer npx > uvx > node > docker.
|
|
4021
|
+
If you cannot determine the configuration, return {"command": "", "args": [], "env": []}.
|
|
4022
|
+
Return ONLY the JSON object.`;
|
|
4023
|
+
|
|
4024
|
+
// src/mcp/classify.ts
|
|
4025
|
+
async function classifyDeps(allDeps) {
|
|
4026
|
+
if (allDeps.length === 0) return [];
|
|
4027
|
+
try {
|
|
4028
|
+
const result = await llmJsonCall({
|
|
4029
|
+
system: CLASSIFY_DEPS_PROMPT,
|
|
4030
|
+
prompt: `Dependencies:
|
|
4031
|
+
${JSON.stringify(allDeps)}`,
|
|
4032
|
+
maxTokens: 2e3
|
|
4033
|
+
});
|
|
4034
|
+
if (!Array.isArray(result)) return [];
|
|
4035
|
+
const inputLower = new Set(allDeps.map((d) => d.toLowerCase()));
|
|
4036
|
+
return result.filter((d) => typeof d === "string" && inputLower.has(d.toLowerCase()));
|
|
4037
|
+
} catch {
|
|
4038
|
+
return fallbackClassify(allDeps);
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
function fallbackClassify(deps) {
|
|
4042
|
+
const utilityPatterns = [
|
|
4043
|
+
/^@types\//,
|
|
4044
|
+
/^eslint/,
|
|
4045
|
+
/^prettier/,
|
|
4046
|
+
/^@typescript-eslint\//,
|
|
4047
|
+
/^@commitlint\//,
|
|
4048
|
+
/^@eslint\//,
|
|
4049
|
+
/^webpack/,
|
|
4050
|
+
/^rollup/,
|
|
4051
|
+
/^babel/,
|
|
4052
|
+
/^@babel\//,
|
|
4053
|
+
/^postcss/,
|
|
4054
|
+
/^tailwindcss/,
|
|
4055
|
+
/^autoprefixer/
|
|
4056
|
+
];
|
|
4057
|
+
const utilities = /* @__PURE__ */ new Set([
|
|
4058
|
+
"typescript",
|
|
4059
|
+
"tslib",
|
|
4060
|
+
"ts-node",
|
|
4061
|
+
"tsx",
|
|
4062
|
+
"prettier",
|
|
4063
|
+
"eslint",
|
|
4064
|
+
"rimraf",
|
|
4065
|
+
"cross-env",
|
|
4066
|
+
"dotenv",
|
|
4067
|
+
"nodemon",
|
|
4068
|
+
"husky",
|
|
4069
|
+
"lint-staged",
|
|
4070
|
+
"commitlint",
|
|
4071
|
+
"chalk",
|
|
4072
|
+
"ora",
|
|
4073
|
+
"commander",
|
|
4074
|
+
"yargs",
|
|
4075
|
+
"meow",
|
|
4076
|
+
"inquirer",
|
|
4077
|
+
"glob",
|
|
4078
|
+
"minimatch",
|
|
4079
|
+
"micromatch",
|
|
4080
|
+
"diff",
|
|
4081
|
+
"semver",
|
|
4082
|
+
"uuid",
|
|
4083
|
+
"nanoid",
|
|
4084
|
+
"debug",
|
|
4085
|
+
"ms",
|
|
4086
|
+
"lodash",
|
|
4087
|
+
"underscore",
|
|
4088
|
+
"ramda",
|
|
4089
|
+
"tsup",
|
|
4090
|
+
"esbuild",
|
|
4091
|
+
"rollup",
|
|
4092
|
+
"webpack",
|
|
4093
|
+
"vite",
|
|
4094
|
+
"parcel",
|
|
4095
|
+
"vitest",
|
|
4096
|
+
"jest",
|
|
4097
|
+
"mocha",
|
|
4098
|
+
"chai",
|
|
4099
|
+
"ava",
|
|
4100
|
+
"tap",
|
|
4101
|
+
"fs-extra",
|
|
4102
|
+
"mkdirp",
|
|
4103
|
+
"del",
|
|
4104
|
+
"path-to-regexp",
|
|
4105
|
+
"strip-ansi",
|
|
4106
|
+
"ansi-colors",
|
|
4107
|
+
"react",
|
|
4108
|
+
"react-dom",
|
|
4109
|
+
"next",
|
|
4110
|
+
"vue",
|
|
4111
|
+
"angular",
|
|
4112
|
+
"svelte",
|
|
4113
|
+
"express",
|
|
4114
|
+
"fastify",
|
|
4115
|
+
"koa",
|
|
4116
|
+
"hapi",
|
|
4117
|
+
"zod",
|
|
4118
|
+
"joi",
|
|
4119
|
+
"yup",
|
|
4120
|
+
"ajv",
|
|
4121
|
+
"axios",
|
|
4122
|
+
"node-fetch",
|
|
4123
|
+
"got",
|
|
4124
|
+
"undici",
|
|
4125
|
+
"moment",
|
|
4126
|
+
"dayjs",
|
|
4127
|
+
"date-fns",
|
|
4128
|
+
"luxon"
|
|
4129
|
+
]);
|
|
4130
|
+
return deps.filter((d) => {
|
|
4131
|
+
const lower = d.toLowerCase();
|
|
4132
|
+
if (utilities.has(lower)) return false;
|
|
4133
|
+
if (utilityPatterns.some((p) => p.test(lower))) return false;
|
|
4134
|
+
return true;
|
|
4135
|
+
});
|
|
4136
|
+
}
|
|
4137
|
+
|
|
4138
|
+
// src/mcp/search.ts
|
|
4139
|
+
var AWESOME_MCP_URL = "https://raw.githubusercontent.com/punkpeye/awesome-mcp-servers/main/README.md";
|
|
4140
|
+
var GITHUB_SEARCH_URL = "https://github.com/search";
|
|
4141
|
+
var SEARCH_HEADERS = {
|
|
4142
|
+
"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",
|
|
4143
|
+
"Accept": "text/html"
|
|
4144
|
+
};
|
|
4145
|
+
function parseGitHubSearchHtml(html) {
|
|
4146
|
+
try {
|
|
4147
|
+
const scriptMatch = html.match(/<script type="application\/json" data-target="react-app\.embeddedData">([\s\S]*?)<\/script>/);
|
|
4148
|
+
if (!scriptMatch) {
|
|
4149
|
+
return [];
|
|
4150
|
+
}
|
|
4151
|
+
const data = JSON.parse(scriptMatch[1]);
|
|
4152
|
+
const results = data?.payload?.results;
|
|
4153
|
+
if (!Array.isArray(results)) {
|
|
4154
|
+
return [];
|
|
4155
|
+
}
|
|
4156
|
+
return results.map((r) => {
|
|
4157
|
+
const repo = r.repo?.repository;
|
|
4158
|
+
const ownerLogin = repo?.owner_login || "";
|
|
4159
|
+
const name = repo?.name || "";
|
|
4160
|
+
const description = typeof r.hl_trunc_description === "string" ? r.hl_trunc_description.replace(/<\/?em>/g, "") : null;
|
|
4161
|
+
return {
|
|
4162
|
+
full_name: `${ownerLogin}/${name}`,
|
|
4163
|
+
description,
|
|
4164
|
+
stargazers_count: typeof r.followers === "number" ? r.followers : 0,
|
|
4165
|
+
pushed_at: repo?.updated_at || "",
|
|
4166
|
+
owner_login: ownerLogin
|
|
4167
|
+
};
|
|
4168
|
+
});
|
|
4169
|
+
} catch {
|
|
4170
|
+
return [];
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
async function searchAllMcpSources(toolDeps) {
|
|
4174
|
+
if (toolDeps.length === 0) return [];
|
|
4175
|
+
const searches = [
|
|
4176
|
+
searchAwesomeMcpLists(toolDeps),
|
|
4177
|
+
...toolDeps.map((dep) => searchGitHub(dep)),
|
|
4178
|
+
...toolDeps.map((dep) => searchVendorOrg(dep))
|
|
4179
|
+
];
|
|
4180
|
+
const results = await Promise.all(searches);
|
|
4181
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4182
|
+
for (const batch of results) {
|
|
4183
|
+
for (const candidate of batch) {
|
|
4184
|
+
const key = candidate.repoFullName.toLowerCase();
|
|
4185
|
+
const existing = seen.get(key);
|
|
4186
|
+
if (!existing || candidate.vendor || candidate.stars > existing.stars) {
|
|
4187
|
+
seen.set(key, candidate);
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
}
|
|
4191
|
+
return Array.from(seen.values());
|
|
4192
|
+
}
|
|
4193
|
+
async function searchAwesomeMcpLists(toolDeps) {
|
|
4194
|
+
try {
|
|
4195
|
+
const resp = await fetch(AWESOME_MCP_URL, {
|
|
4196
|
+
signal: AbortSignal.timeout(1e4)
|
|
4197
|
+
});
|
|
4198
|
+
if (!resp.ok) return [];
|
|
4199
|
+
const markdown = await resp.text();
|
|
4200
|
+
const candidates = [];
|
|
4201
|
+
const depLower = toolDeps.map((d) => d.toLowerCase().replace(/^@[^/]+\//, ""));
|
|
4202
|
+
const itemPattern = /^[-*]\s+\[([^\]]+)\]\(([^)]+)\)\s*[-–—:]\s*(.*)/gm;
|
|
4203
|
+
let match;
|
|
4204
|
+
while ((match = itemPattern.exec(markdown)) !== null) {
|
|
4205
|
+
const [, name, url, description] = match;
|
|
4206
|
+
if (!url.includes("github.com")) continue;
|
|
4207
|
+
const text = `${name} ${description}`.toLowerCase();
|
|
4208
|
+
const matchedDep = depLower.find((d) => text.includes(d));
|
|
4209
|
+
if (!matchedDep) continue;
|
|
4210
|
+
const repoMatch = url.match(/github\.com\/([^/]+\/[^/]+)/);
|
|
4211
|
+
if (!repoMatch) continue;
|
|
4212
|
+
candidates.push({
|
|
4213
|
+
name: name.trim(),
|
|
4214
|
+
repoFullName: repoMatch[1],
|
|
4215
|
+
url: url.trim(),
|
|
4216
|
+
description: description.trim().slice(0, 200),
|
|
4217
|
+
stars: 0,
|
|
4218
|
+
lastPush: "",
|
|
4219
|
+
vendor: false,
|
|
4220
|
+
score: 0,
|
|
4221
|
+
reason: "",
|
|
4222
|
+
matchedDep: toolDeps.find((d) => d.toLowerCase().replace(/^@[^/]+\//, "") === matchedDep) || matchedDep
|
|
4223
|
+
});
|
|
4224
|
+
}
|
|
4225
|
+
return candidates;
|
|
4226
|
+
} catch {
|
|
4227
|
+
return [];
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
4230
|
+
async function searchGitHub(dep) {
|
|
4231
|
+
const depName = dep.replace(/^@[^/]+\//, "");
|
|
4232
|
+
try {
|
|
4233
|
+
const query = encodeURIComponent(`${depName} mcp server`);
|
|
4234
|
+
const url = `${GITHUB_SEARCH_URL}?q=${query}&type=repositories&s=stars&o=desc`;
|
|
4235
|
+
const resp = await fetch(url, {
|
|
4236
|
+
signal: AbortSignal.timeout(1e4),
|
|
4237
|
+
headers: SEARCH_HEADERS
|
|
4238
|
+
});
|
|
4239
|
+
if (!resp.ok) return [];
|
|
4240
|
+
const html = await resp.text();
|
|
4241
|
+
const repos = parseGitHubSearchHtml(html).slice(0, 5);
|
|
4242
|
+
return repos.map((repo) => ({
|
|
4243
|
+
name: repo.full_name.split("/")[1],
|
|
4244
|
+
repoFullName: repo.full_name,
|
|
4245
|
+
url: `https://github.com/${repo.full_name}`,
|
|
4246
|
+
description: repo.description || "",
|
|
4247
|
+
stars: repo.stargazers_count,
|
|
4248
|
+
lastPush: repo.pushed_at,
|
|
4249
|
+
vendor: false,
|
|
4250
|
+
score: 0,
|
|
4251
|
+
reason: "",
|
|
4252
|
+
matchedDep: dep
|
|
4253
|
+
}));
|
|
4254
|
+
} catch {
|
|
4255
|
+
return [];
|
|
4256
|
+
}
|
|
4257
|
+
}
|
|
4258
|
+
async function searchVendorOrg(dep) {
|
|
4259
|
+
const depName = dep.replace(/^@([^/]+)\/.*/, "$1").replace(/^@/, "");
|
|
4260
|
+
const orgName = depName.toLowerCase();
|
|
4261
|
+
try {
|
|
4262
|
+
const query = encodeURIComponent(`mcp org:${orgName}`);
|
|
4263
|
+
const url = `${GITHUB_SEARCH_URL}?q=${query}&type=repositories&s=stars&o=desc`;
|
|
4264
|
+
const resp = await fetch(url, {
|
|
4265
|
+
signal: AbortSignal.timeout(1e4),
|
|
4266
|
+
headers: SEARCH_HEADERS
|
|
4267
|
+
});
|
|
4268
|
+
if (!resp.ok) return [];
|
|
4269
|
+
const html = await resp.text();
|
|
4270
|
+
const repos = parseGitHubSearchHtml(html).slice(0, 5);
|
|
4271
|
+
return repos.map((repo) => ({
|
|
4272
|
+
name: repo.full_name.split("/")[1],
|
|
4273
|
+
repoFullName: repo.full_name,
|
|
4274
|
+
url: `https://github.com/${repo.full_name}`,
|
|
4275
|
+
description: repo.description || "",
|
|
4276
|
+
stars: repo.stargazers_count,
|
|
4277
|
+
lastPush: repo.pushed_at,
|
|
4278
|
+
vendor: repo.owner_login.toLowerCase() === orgName,
|
|
4279
|
+
score: 0,
|
|
4280
|
+
reason: "",
|
|
4281
|
+
matchedDep: dep
|
|
4282
|
+
}));
|
|
4283
|
+
} catch {
|
|
4284
|
+
return [];
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
|
|
4288
|
+
// src/mcp/validate.ts
|
|
4289
|
+
var MIN_STARS = 100;
|
|
4290
|
+
var MAX_AGE_DAYS = 180;
|
|
4291
|
+
async function validateAndScore(candidates, toolDeps) {
|
|
4292
|
+
const qualityFiltered = candidates.filter((c) => {
|
|
4293
|
+
if (c.vendor) return true;
|
|
4294
|
+
if (c.stars > 0 && c.stars < MIN_STARS) return false;
|
|
4295
|
+
if (c.lastPush) {
|
|
4296
|
+
const pushDate = new Date(c.lastPush);
|
|
4297
|
+
const daysAgo = (Date.now() - pushDate.getTime()) / (1e3 * 60 * 60 * 24);
|
|
4298
|
+
if (daysAgo > MAX_AGE_DAYS) return false;
|
|
4299
|
+
}
|
|
4300
|
+
return true;
|
|
4301
|
+
});
|
|
4302
|
+
if (qualityFiltered.length === 0) return [];
|
|
4303
|
+
try {
|
|
4304
|
+
return await scoreWithLLM(qualityFiltered, toolDeps);
|
|
4305
|
+
} catch {
|
|
4306
|
+
return qualityFiltered.slice(0, 5).map((c) => ({
|
|
4307
|
+
...c,
|
|
4308
|
+
score: 50,
|
|
4309
|
+
reason: c.description.slice(0, 80)
|
|
4310
|
+
}));
|
|
4311
|
+
}
|
|
4312
|
+
}
|
|
4313
|
+
async function scoreWithLLM(candidates, toolDeps) {
|
|
4314
|
+
const candidateList = candidates.map((c, i) => {
|
|
4315
|
+
const vendorTag = c.vendor ? " [VENDOR/OFFICIAL]" : "";
|
|
4316
|
+
return `${i}. "${c.name}"${vendorTag} (${c.stars} stars) \u2014 ${c.description.slice(0, 100)}`;
|
|
4317
|
+
}).join("\n");
|
|
4318
|
+
const scored = await llmJsonCall({
|
|
4319
|
+
system: SCORE_MCP_PROMPT,
|
|
4320
|
+
prompt: `TOOL DEPENDENCIES IN PROJECT:
|
|
4321
|
+
${toolDeps.join(", ")}
|
|
4322
|
+
|
|
4323
|
+
MCP SERVER CANDIDATES:
|
|
4324
|
+
${candidateList}`,
|
|
4325
|
+
maxTokens: 4e3
|
|
4326
|
+
});
|
|
4327
|
+
if (!Array.isArray(scored)) return [];
|
|
4328
|
+
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) => ({
|
|
4329
|
+
...candidates[s.index],
|
|
4330
|
+
score: s.score,
|
|
4331
|
+
reason: s.reason || candidates[s.index].description.slice(0, 80)
|
|
4332
|
+
}));
|
|
4333
|
+
}
|
|
4334
|
+
|
|
4335
|
+
// src/mcp/config-extract.ts
|
|
4336
|
+
async function fetchReadme(repoFullName) {
|
|
4337
|
+
try {
|
|
4338
|
+
const resp = await fetch(
|
|
4339
|
+
`https://raw.githubusercontent.com/${repoFullName}/HEAD/README.md`,
|
|
4340
|
+
{ signal: AbortSignal.timeout(1e4) }
|
|
4341
|
+
);
|
|
4342
|
+
if (resp.ok) return await resp.text();
|
|
4343
|
+
} catch {
|
|
4344
|
+
}
|
|
4345
|
+
try {
|
|
4346
|
+
const resp = await fetch(
|
|
4347
|
+
`https://raw.githubusercontent.com/${repoFullName}/main/README.md`,
|
|
4348
|
+
{ signal: AbortSignal.timeout(1e4) }
|
|
4349
|
+
);
|
|
4350
|
+
if (resp.ok) return await resp.text();
|
|
4351
|
+
} catch {
|
|
4352
|
+
}
|
|
4353
|
+
return null;
|
|
4354
|
+
}
|
|
4355
|
+
async function extractMcpConfig(readme, serverName) {
|
|
4356
|
+
try {
|
|
4357
|
+
const truncated = readme.length > 15e3 ? readme.slice(0, 15e3) : readme;
|
|
4358
|
+
const result = await llmJsonCall({
|
|
4359
|
+
system: EXTRACT_CONFIG_PROMPT,
|
|
4360
|
+
prompt: `MCP Server: ${serverName}
|
|
4361
|
+
|
|
4362
|
+
README:
|
|
4363
|
+
${truncated}`,
|
|
4364
|
+
maxTokens: 2e3
|
|
4365
|
+
});
|
|
4366
|
+
if (!result || !result.command) return null;
|
|
4367
|
+
return {
|
|
4368
|
+
command: result.command,
|
|
4369
|
+
args: Array.isArray(result.args) ? result.args : [],
|
|
4370
|
+
env: Array.isArray(result.env) ? result.env : []
|
|
4371
|
+
};
|
|
4372
|
+
} catch {
|
|
4373
|
+
return null;
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
|
|
4377
|
+
// src/mcp/index.ts
|
|
4378
|
+
async function discoverAndInstallMcps(targetAgent, fingerprint, dir) {
|
|
4379
|
+
console.log(chalk4.hex("#6366f1").bold("\n MCP Server Discovery\n"));
|
|
4380
|
+
const spinner = ora("Analyzing dependencies for tool integrations...").start();
|
|
4381
|
+
const allDeps = extractAllDeps(dir);
|
|
4382
|
+
if (allDeps.length === 0) {
|
|
4383
|
+
spinner.succeed(chalk4.dim("No dependencies found \u2014 skipping MCP discovery"));
|
|
4384
|
+
return { installed: 0, names: [] };
|
|
4385
|
+
}
|
|
4386
|
+
const toolDeps = await classifyDeps(allDeps);
|
|
4387
|
+
if (toolDeps.length === 0) {
|
|
4388
|
+
spinner.succeed(chalk4.dim("No tool dependencies detected \u2014 skipping MCP discovery"));
|
|
4389
|
+
console.log(chalk4.dim(` All deps (${allDeps.length}): ${allDeps.slice(0, 10).join(", ")}${allDeps.length > 10 ? "..." : ""}`));
|
|
4390
|
+
return { installed: 0, names: [] };
|
|
4391
|
+
}
|
|
4392
|
+
spinner.succeed(`Found ${toolDeps.length} tool dependenc${toolDeps.length === 1 ? "y" : "ies"}: ${toolDeps.join(", ")}`);
|
|
4393
|
+
console.log(chalk4.dim(` All deps (${allDeps.length}): ${allDeps.slice(0, 10).join(", ")}${allDeps.length > 10 ? "..." : ""}`));
|
|
4394
|
+
console.log(chalk4.dim(` Classified as tools: ${toolDeps.join(", ")}`));
|
|
4395
|
+
const existingMcps = getExistingMcpNames(fingerprint, targetAgent);
|
|
4396
|
+
const filteredDeps = toolDeps.filter((d) => {
|
|
4397
|
+
const lower = d.toLowerCase().replace(/^@[^/]+\//, "");
|
|
4398
|
+
return !existingMcps.some((name) => name.includes(lower) || lower.includes(name));
|
|
4399
|
+
});
|
|
4400
|
+
if (filteredDeps.length === 0) {
|
|
4401
|
+
console.log(chalk4.dim(" All detected tools already have MCP servers configured."));
|
|
4402
|
+
console.log(chalk4.dim(` Existing MCPs: ${existingMcps.join(", ")}`));
|
|
4403
|
+
return { installed: 0, names: [] };
|
|
4404
|
+
}
|
|
4405
|
+
const searchSpinner = ora("Searching for MCP servers...").start();
|
|
4406
|
+
const candidates = await searchAllMcpSources(filteredDeps);
|
|
4407
|
+
if (candidates.length === 0) {
|
|
4408
|
+
searchSpinner.succeed(chalk4.dim("No MCP servers found for your dependencies"));
|
|
4409
|
+
console.log(chalk4.dim(` Searched for: ${filteredDeps.join(", ")}`));
|
|
4410
|
+
return { installed: 0, names: [] };
|
|
4411
|
+
}
|
|
4412
|
+
searchSpinner.succeed(`Found ${candidates.length} candidate${candidates.length === 1 ? "" : "s"}`);
|
|
4413
|
+
console.log(chalk4.dim(` Sources: ${candidates.map((c) => c.repoFullName).join(", ")}`));
|
|
4414
|
+
const scoreSpinner = ora("Scoring MCP candidates...").start();
|
|
4415
|
+
const scored = await validateAndScore(candidates, filteredDeps);
|
|
4416
|
+
if (scored.length === 0) {
|
|
4417
|
+
scoreSpinner.succeed(chalk4.dim("No quality MCP servers passed validation"));
|
|
4418
|
+
console.log(chalk4.dim(` Candidates checked: ${candidates.map((c) => c.name).join(", ")}`));
|
|
4419
|
+
return { installed: 0, names: [] };
|
|
4420
|
+
}
|
|
4421
|
+
scoreSpinner.succeed(`${scored.length} quality MCP server${scored.length === 1 ? "" : "s"} found`);
|
|
4422
|
+
console.log(chalk4.dim(` Scored: ${scored.map((c) => `${c.name} (${c.score})`).join(", ")}`));
|
|
4423
|
+
const selected = await interactiveSelect(scored);
|
|
4424
|
+
if (!selected || selected.length === 0) {
|
|
4425
|
+
return { installed: 0, names: [] };
|
|
4426
|
+
}
|
|
4427
|
+
const mcpServers = {};
|
|
4428
|
+
const installedNames = [];
|
|
4429
|
+
for (const mcp of selected) {
|
|
4430
|
+
console.log(chalk4.bold(`
|
|
4431
|
+
Configuring ${mcp.name}...`));
|
|
4432
|
+
const readme = await fetchReadme(mcp.repoFullName);
|
|
4433
|
+
if (!readme) {
|
|
4434
|
+
console.log(chalk4.yellow(` Could not fetch README for ${mcp.repoFullName} \u2014 skipping`));
|
|
4435
|
+
console.log(chalk4.dim(` Manual setup: ${mcp.url}`));
|
|
4436
|
+
continue;
|
|
4437
|
+
}
|
|
4438
|
+
const config = await extractMcpConfig(readme, mcp.name);
|
|
4439
|
+
if (!config || !config.command) {
|
|
4440
|
+
console.log(chalk4.yellow(` Could not extract config for ${mcp.name} \u2014 skipping`));
|
|
4441
|
+
console.log(chalk4.dim(` Manual setup: ${mcp.url}`));
|
|
4442
|
+
continue;
|
|
4443
|
+
}
|
|
4444
|
+
const env = {};
|
|
4445
|
+
for (const envVar of config.env) {
|
|
4446
|
+
if (!envVar.required) continue;
|
|
4447
|
+
const value = await promptInput2(` ? ${envVar.key} (${envVar.description})`);
|
|
4448
|
+
if (value) {
|
|
4449
|
+
env[envVar.key] = value;
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
const serverConfig = {
|
|
4453
|
+
command: config.command
|
|
4454
|
+
};
|
|
4455
|
+
if (config.args.length > 0) serverConfig.args = config.args;
|
|
4456
|
+
if (Object.keys(env).length > 0) serverConfig.env = env;
|
|
4457
|
+
mcpServers[mcp.name] = serverConfig;
|
|
4458
|
+
installedNames.push(mcp.name);
|
|
4459
|
+
console.log(` ${chalk4.green("\u2713")} ${mcp.name} configured`);
|
|
4460
|
+
}
|
|
4461
|
+
if (installedNames.length === 0) {
|
|
4462
|
+
return { installed: 0, names: [] };
|
|
4463
|
+
}
|
|
4464
|
+
if (targetAgent === "claude" || targetAgent === "both") {
|
|
4465
|
+
writeMcpJson(path18.join(dir, ".mcp.json"), mcpServers);
|
|
4466
|
+
}
|
|
4467
|
+
if (targetAgent === "cursor" || targetAgent === "both") {
|
|
4468
|
+
const cursorDir = path18.join(dir, ".cursor");
|
|
4469
|
+
if (!fs19.existsSync(cursorDir)) fs19.mkdirSync(cursorDir, { recursive: true });
|
|
4470
|
+
writeMcpJson(path18.join(cursorDir, "mcp.json"), mcpServers);
|
|
4471
|
+
}
|
|
4472
|
+
return { installed: installedNames.length, names: installedNames };
|
|
4473
|
+
}
|
|
4474
|
+
function writeMcpJson(filePath, mcpServers) {
|
|
4475
|
+
let existing = {};
|
|
4476
|
+
try {
|
|
4477
|
+
if (fs19.existsSync(filePath)) {
|
|
4478
|
+
const parsed = JSON.parse(fs19.readFileSync(filePath, "utf-8"));
|
|
4479
|
+
if (parsed.mcpServers) existing = parsed.mcpServers;
|
|
4480
|
+
}
|
|
4481
|
+
} catch {
|
|
4482
|
+
}
|
|
4483
|
+
const merged = { ...existing, ...mcpServers };
|
|
4484
|
+
fs19.writeFileSync(filePath, JSON.stringify({ mcpServers: merged }, null, 2) + "\n");
|
|
4485
|
+
}
|
|
4486
|
+
function getExistingMcpNames(fingerprint, targetAgent) {
|
|
4487
|
+
const names = [];
|
|
4488
|
+
if (targetAgent === "claude" || targetAgent === "both") {
|
|
4489
|
+
if (fingerprint.existingConfigs.claudeMcpServers) {
|
|
4490
|
+
names.push(...Object.keys(fingerprint.existingConfigs.claudeMcpServers).map((k) => k.toLowerCase()));
|
|
4491
|
+
}
|
|
4492
|
+
}
|
|
4493
|
+
if (targetAgent === "cursor" || targetAgent === "both") {
|
|
4494
|
+
if (fingerprint.existingConfigs.cursorMcpServers) {
|
|
4495
|
+
names.push(...Object.keys(fingerprint.existingConfigs.cursorMcpServers).map((k) => k.toLowerCase()));
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
return names;
|
|
4499
|
+
}
|
|
4500
|
+
function promptInput2(question) {
|
|
4501
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
4502
|
+
return new Promise((resolve2) => {
|
|
4503
|
+
rl.question(chalk4.cyan(`${question}: `), (answer) => {
|
|
4504
|
+
rl.close();
|
|
4505
|
+
resolve2(answer.trim());
|
|
4506
|
+
});
|
|
4507
|
+
});
|
|
4508
|
+
}
|
|
4509
|
+
async function interactiveSelect(candidates) {
|
|
4510
|
+
if (!process.stdin.isTTY) {
|
|
4511
|
+
console.log(chalk4.bold("\n Available MCP servers:\n"));
|
|
4512
|
+
for (const c of candidates) {
|
|
4513
|
+
const vendorTag = c.vendor ? chalk4.blue(" (vendor)") : "";
|
|
4514
|
+
console.log(` ${String(c.score).padStart(3)} ${c.name}${vendorTag} ${chalk4.dim(c.reason)}`);
|
|
4515
|
+
}
|
|
4516
|
+
console.log("");
|
|
4517
|
+
return null;
|
|
4518
|
+
}
|
|
4519
|
+
const selected = /* @__PURE__ */ new Set();
|
|
4520
|
+
let cursor = 0;
|
|
4521
|
+
const { stdin, stdout } = process;
|
|
4522
|
+
let lineCount = 0;
|
|
4523
|
+
function render() {
|
|
4524
|
+
const lines = [];
|
|
4525
|
+
lines.push(chalk4.bold(" Select MCP servers to install:"));
|
|
4526
|
+
lines.push("");
|
|
4527
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
4528
|
+
const c = candidates[i];
|
|
4529
|
+
const check = selected.has(i) ? chalk4.green("[x]") : "[ ]";
|
|
4530
|
+
const ptr = i === cursor ? chalk4.cyan(">") : " ";
|
|
4531
|
+
const scoreColor = c.score >= 90 ? chalk4.green : c.score >= 70 ? chalk4.yellow : chalk4.dim;
|
|
4532
|
+
const vendorTag = c.vendor ? chalk4.blue(" (vendor)") : "";
|
|
4533
|
+
lines.push(` ${ptr} ${check} ${scoreColor(String(c.score).padStart(3))} ${c.name}${vendorTag} ${chalk4.dim(c.reason.slice(0, 40))}`);
|
|
4534
|
+
}
|
|
4535
|
+
lines.push("");
|
|
4536
|
+
lines.push(chalk4.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q skip"));
|
|
4537
|
+
return lines.join("\n");
|
|
4538
|
+
}
|
|
4539
|
+
function draw(initial) {
|
|
4540
|
+
if (!initial && lineCount > 0) {
|
|
4541
|
+
stdout.write(`\x1B[${lineCount}A`);
|
|
4542
|
+
}
|
|
4543
|
+
stdout.write("\x1B[0J");
|
|
4544
|
+
const output = render();
|
|
4545
|
+
stdout.write(output + "\n");
|
|
4546
|
+
lineCount = output.split("\n").length;
|
|
4547
|
+
}
|
|
4548
|
+
return new Promise((resolve2) => {
|
|
4549
|
+
console.log("");
|
|
4550
|
+
draw(true);
|
|
4551
|
+
stdin.setRawMode(true);
|
|
4552
|
+
stdin.resume();
|
|
4553
|
+
stdin.setEncoding("utf8");
|
|
4554
|
+
function cleanup() {
|
|
4555
|
+
stdin.removeListener("data", onData);
|
|
4556
|
+
stdin.setRawMode(false);
|
|
4557
|
+
stdin.pause();
|
|
4558
|
+
}
|
|
4559
|
+
function onData(key) {
|
|
4560
|
+
switch (key) {
|
|
4561
|
+
case "\x1B[A":
|
|
4562
|
+
cursor = (cursor - 1 + candidates.length) % candidates.length;
|
|
4563
|
+
draw(false);
|
|
4564
|
+
break;
|
|
4565
|
+
case "\x1B[B":
|
|
4566
|
+
cursor = (cursor + 1) % candidates.length;
|
|
4567
|
+
draw(false);
|
|
4568
|
+
break;
|
|
4569
|
+
case " ":
|
|
4570
|
+
selected.has(cursor) ? selected.delete(cursor) : selected.add(cursor);
|
|
4571
|
+
draw(false);
|
|
4572
|
+
break;
|
|
4573
|
+
case "a":
|
|
4574
|
+
candidates.forEach((_, i) => selected.add(i));
|
|
4575
|
+
draw(false);
|
|
4576
|
+
break;
|
|
4577
|
+
case "n":
|
|
4578
|
+
selected.clear();
|
|
4579
|
+
draw(false);
|
|
4580
|
+
break;
|
|
4581
|
+
case "\r":
|
|
4582
|
+
case "\n":
|
|
4583
|
+
cleanup();
|
|
4584
|
+
if (selected.size === 0) {
|
|
4585
|
+
console.log(chalk4.dim("\n No MCP servers selected.\n"));
|
|
4586
|
+
resolve2(null);
|
|
4587
|
+
} else {
|
|
4588
|
+
resolve2(Array.from(selected).sort().map((i) => candidates[i]));
|
|
4589
|
+
}
|
|
4590
|
+
break;
|
|
4591
|
+
case "q":
|
|
4592
|
+
case "\x1B":
|
|
4593
|
+
case "":
|
|
4594
|
+
cleanup();
|
|
4595
|
+
console.log(chalk4.dim("\n Skipped MCP server installation.\n"));
|
|
4596
|
+
resolve2(null);
|
|
4597
|
+
break;
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
stdin.on("data", onData);
|
|
4601
|
+
});
|
|
4602
|
+
}
|
|
4603
|
+
|
|
3823
4604
|
// src/commands/onboard.ts
|
|
3824
4605
|
async function initCommand(options) {
|
|
3825
|
-
const brand =
|
|
3826
|
-
const title =
|
|
4606
|
+
const brand = chalk5.hex("#EB9D83");
|
|
4607
|
+
const title = chalk5.hex("#83D1EB");
|
|
3827
4608
|
console.log(brand.bold(`
|
|
3828
4609
|
\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
|
|
3829
4610
|
\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
|
|
@@ -3832,19 +4613,19 @@ async function initCommand(options) {
|
|
|
3832
4613
|
\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
|
|
3833
4614
|
\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
|
|
3834
4615
|
`));
|
|
3835
|
-
console.log(
|
|
4616
|
+
console.log(chalk5.dim(" Onboard your project for AI-assisted development\n"));
|
|
3836
4617
|
console.log(title.bold(" Welcome to Caliber\n"));
|
|
3837
|
-
console.log(
|
|
3838
|
-
console.log(
|
|
4618
|
+
console.log(chalk5.dim(" Caliber analyzes your codebase and creates tailored config files"));
|
|
4619
|
+
console.log(chalk5.dim(" so your AI coding agents understand your project from day one.\n"));
|
|
3839
4620
|
console.log(title.bold(" How onboarding works:\n"));
|
|
3840
|
-
console.log(
|
|
3841
|
-
console.log(
|
|
3842
|
-
console.log(
|
|
3843
|
-
console.log(
|
|
4621
|
+
console.log(chalk5.dim(" 1. Connect Set up your LLM provider"));
|
|
4622
|
+
console.log(chalk5.dim(" 2. Discover Analyze your code, dependencies, and structure"));
|
|
4623
|
+
console.log(chalk5.dim(" 3. Generate Create config files tailored to your project"));
|
|
4624
|
+
console.log(chalk5.dim(" 4. Review Preview, refine, and apply the changes\n"));
|
|
3844
4625
|
console.log(title.bold(" Step 1/4 \u2014 Connect your LLM\n"));
|
|
3845
4626
|
let config = loadConfig();
|
|
3846
4627
|
if (!config) {
|
|
3847
|
-
console.log(
|
|
4628
|
+
console.log(chalk5.dim(" No LLM provider set yet. Choose how to run Caliber:\n"));
|
|
3848
4629
|
try {
|
|
3849
4630
|
await runInteractiveProviderSetup({
|
|
3850
4631
|
selectMessage: "How do you want to use Caliber? (choose LLM provider)"
|
|
@@ -3855,36 +4636,47 @@ async function initCommand(options) {
|
|
|
3855
4636
|
}
|
|
3856
4637
|
config = loadConfig();
|
|
3857
4638
|
if (!config) {
|
|
3858
|
-
console.log(
|
|
4639
|
+
console.log(chalk5.red(" Setup was cancelled or failed.\n"));
|
|
3859
4640
|
throw new Error("__exit__");
|
|
3860
4641
|
}
|
|
3861
|
-
console.log(
|
|
4642
|
+
console.log(chalk5.green(" \u2713 Provider saved. Let's continue.\n"));
|
|
3862
4643
|
}
|
|
3863
4644
|
const displayModel = config.model === "default" && config.provider === "claude-cli" ? process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)" : config.model;
|
|
3864
4645
|
const fastModel = process.env.ANTHROPIC_SMALL_FAST_MODEL;
|
|
3865
4646
|
const modelLine = fastModel ? ` Provider: ${config.provider} | Model: ${displayModel} | Scan: ${fastModel}` : ` Provider: ${config.provider} | Model: ${displayModel}`;
|
|
3866
|
-
console.log(
|
|
4647
|
+
console.log(chalk5.dim(modelLine + "\n"));
|
|
3867
4648
|
console.log(title.bold(" Step 2/4 \u2014 Discover your project\n"));
|
|
3868
|
-
console.log(
|
|
3869
|
-
const spinner =
|
|
4649
|
+
console.log(chalk5.dim(" Learning about your languages, dependencies, structure, and existing configs.\n"));
|
|
4650
|
+
const spinner = ora2("Analyzing project...").start();
|
|
3870
4651
|
const fingerprint = collectFingerprint(process.cwd());
|
|
3871
4652
|
await enrichFingerprintWithLLM(fingerprint, process.cwd());
|
|
3872
4653
|
spinner.succeed("Project analyzed");
|
|
3873
|
-
console.log(
|
|
3874
|
-
console.log(
|
|
4654
|
+
console.log(chalk5.dim(` Languages: ${fingerprint.languages.join(", ") || "none detected"}`));
|
|
4655
|
+
console.log(chalk5.dim(` Files: ${fingerprint.fileTree.length} found
|
|
3875
4656
|
`));
|
|
3876
4657
|
const targetAgent = options.agent || await promptAgent();
|
|
4658
|
+
const preScore = computeLocalScore(process.cwd(), targetAgent);
|
|
4659
|
+
const failingForDismissal = preScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
|
|
4660
|
+
if (failingForDismissal.length > 0) {
|
|
4661
|
+
const newDismissals = await evaluateDismissals(failingForDismissal, fingerprint);
|
|
4662
|
+
if (newDismissals.length > 0) {
|
|
4663
|
+
const existing = readDismissedChecks();
|
|
4664
|
+
const existingIds = new Set(existing.map((d) => d.id));
|
|
4665
|
+
const merged = [...existing, ...newDismissals.filter((d) => !existingIds.has(d.id))];
|
|
4666
|
+
writeDismissedChecks(merged);
|
|
4667
|
+
}
|
|
4668
|
+
}
|
|
3877
4669
|
const baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
3878
4670
|
displayScoreSummary(baselineScore);
|
|
3879
4671
|
const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length);
|
|
3880
4672
|
if (hasExistingConfig && baselineScore.score === 100) {
|
|
3881
|
-
console.log(
|
|
3882
|
-
console.log(
|
|
4673
|
+
console.log(chalk5.bold.green(" Your setup is already optimal \u2014 nothing to change.\n"));
|
|
4674
|
+
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber onboard --force") + chalk5.dim(" to regenerate anyway.\n"));
|
|
3883
4675
|
if (!options.force) return;
|
|
3884
4676
|
}
|
|
3885
4677
|
const isEmpty = fingerprint.fileTree.length < 3;
|
|
3886
4678
|
if (isEmpty) {
|
|
3887
|
-
fingerprint.description = await
|
|
4679
|
+
fingerprint.description = await promptInput3("What will you build in this project?");
|
|
3888
4680
|
}
|
|
3889
4681
|
let failingChecks;
|
|
3890
4682
|
let passingChecks;
|
|
@@ -3895,24 +4687,24 @@ async function initCommand(options) {
|
|
|
3895
4687
|
currentScore = baselineScore.score;
|
|
3896
4688
|
if (failingChecks.length > 0) {
|
|
3897
4689
|
console.log(title.bold(" Step 3/4 \u2014 Fine-tuning\n"));
|
|
3898
|
-
console.log(
|
|
4690
|
+
console.log(chalk5.dim(` Your setup scores ${baselineScore.score}/100 \u2014 fixing ${failingChecks.length} remaining issue${failingChecks.length === 1 ? "" : "s"}:
|
|
3899
4691
|
`));
|
|
3900
4692
|
for (const check of failingChecks) {
|
|
3901
|
-
console.log(
|
|
4693
|
+
console.log(chalk5.dim(` \u2022 ${check.name}`));
|
|
3902
4694
|
}
|
|
3903
4695
|
console.log("");
|
|
3904
4696
|
}
|
|
3905
4697
|
} else if (hasExistingConfig) {
|
|
3906
4698
|
console.log(title.bold(" Step 3/4 \u2014 Improve your setup\n"));
|
|
3907
|
-
console.log(
|
|
3908
|
-
console.log(
|
|
4699
|
+
console.log(chalk5.dim(" Reviewing your existing configs against your codebase"));
|
|
4700
|
+
console.log(chalk5.dim(" and preparing improvements.\n"));
|
|
3909
4701
|
} else {
|
|
3910
4702
|
console.log(title.bold(" Step 3/4 \u2014 Build your agent setup\n"));
|
|
3911
|
-
console.log(
|
|
4703
|
+
console.log(chalk5.dim(" Creating config files tailored to your project.\n"));
|
|
3912
4704
|
}
|
|
3913
|
-
console.log(
|
|
4705
|
+
console.log(chalk5.dim(" This can take a couple of minutes depending on your model and provider.\n"));
|
|
3914
4706
|
const genStartTime = Date.now();
|
|
3915
|
-
const genSpinner =
|
|
4707
|
+
const genSpinner = ora2("Generating setup...").start();
|
|
3916
4708
|
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
|
|
3917
4709
|
genMessages.start();
|
|
3918
4710
|
let generatedSetup = null;
|
|
@@ -3952,8 +4744,8 @@ async function initCommand(options) {
|
|
|
3952
4744
|
if (!generatedSetup) {
|
|
3953
4745
|
genSpinner.fail("Failed to generate setup.");
|
|
3954
4746
|
if (rawOutput) {
|
|
3955
|
-
console.log(
|
|
3956
|
-
console.log(
|
|
4747
|
+
console.log(chalk5.dim("\nRaw LLM output (JSON parse failed):"));
|
|
4748
|
+
console.log(chalk5.dim(rawOutput.slice(0, 500)));
|
|
3957
4749
|
}
|
|
3958
4750
|
throw new Error("__exit__");
|
|
3959
4751
|
}
|
|
@@ -3961,7 +4753,7 @@ async function initCommand(options) {
|
|
|
3961
4753
|
const mins = Math.floor(elapsedMs / 6e4);
|
|
3962
4754
|
const secs = Math.floor(elapsedMs % 6e4 / 1e3);
|
|
3963
4755
|
const timeStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
|
|
3964
|
-
genSpinner.succeed(`Setup generated ${
|
|
4756
|
+
genSpinner.succeed(`Setup generated ${chalk5.dim(`in ${timeStr}`)}`);
|
|
3965
4757
|
printSetupSummary(generatedSetup);
|
|
3966
4758
|
const sessionHistory = [];
|
|
3967
4759
|
sessionHistory.push({
|
|
@@ -3971,7 +4763,7 @@ async function initCommand(options) {
|
|
|
3971
4763
|
console.log(title.bold(" Step 4/4 \u2014 Review and apply\n"));
|
|
3972
4764
|
const setupFiles = collectSetupFiles(generatedSetup);
|
|
3973
4765
|
const staged = stageFiles(setupFiles, process.cwd());
|
|
3974
|
-
console.log(
|
|
4766
|
+
console.log(chalk5.dim(` ${chalk5.green(`${staged.newFiles} new`)} / ${chalk5.yellow(`${staged.modifiedFiles} modified`)} file${staged.newFiles + staged.modifiedFiles !== 1 ? "s" : ""}
|
|
3975
4767
|
`));
|
|
3976
4768
|
const wantsReview = await promptWantsReview();
|
|
3977
4769
|
if (wantsReview) {
|
|
@@ -3983,12 +4775,12 @@ async function initCommand(options) {
|
|
|
3983
4775
|
generatedSetup = await refineLoop(generatedSetup, targetAgent, sessionHistory);
|
|
3984
4776
|
if (!generatedSetup) {
|
|
3985
4777
|
cleanupStaging();
|
|
3986
|
-
console.log(
|
|
4778
|
+
console.log(chalk5.dim("Refinement cancelled. No files were modified."));
|
|
3987
4779
|
return;
|
|
3988
4780
|
}
|
|
3989
4781
|
const updatedFiles = collectSetupFiles(generatedSetup);
|
|
3990
4782
|
const restaged = stageFiles(updatedFiles, process.cwd());
|
|
3991
|
-
console.log(
|
|
4783
|
+
console.log(chalk5.dim(` ${chalk5.green(`${restaged.newFiles} new`)} / ${chalk5.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
|
|
3992
4784
|
`));
|
|
3993
4785
|
printSetupSummary(generatedSetup);
|
|
3994
4786
|
await openReview("terminal", restaged.stagedFiles);
|
|
@@ -3996,37 +4788,49 @@ async function initCommand(options) {
|
|
|
3996
4788
|
}
|
|
3997
4789
|
cleanupStaging();
|
|
3998
4790
|
if (action === "decline") {
|
|
3999
|
-
console.log(
|
|
4791
|
+
console.log(chalk5.dim("Setup declined. No files were modified."));
|
|
4000
4792
|
return;
|
|
4001
4793
|
}
|
|
4002
4794
|
if (options.dryRun) {
|
|
4003
|
-
console.log(
|
|
4795
|
+
console.log(chalk5.yellow("\n[Dry run] Would write the following files:"));
|
|
4004
4796
|
console.log(JSON.stringify(generatedSetup, null, 2));
|
|
4005
4797
|
return;
|
|
4006
4798
|
}
|
|
4007
|
-
const writeSpinner =
|
|
4799
|
+
const writeSpinner = ora2("Writing config files...").start();
|
|
4008
4800
|
try {
|
|
4009
4801
|
const result = writeSetup(generatedSetup);
|
|
4010
4802
|
writeSpinner.succeed("Config files written");
|
|
4011
|
-
console.log(
|
|
4803
|
+
console.log(chalk5.bold("\nFiles created/updated:"));
|
|
4012
4804
|
for (const file of result.written) {
|
|
4013
|
-
console.log(` ${
|
|
4805
|
+
console.log(` ${chalk5.green("\u2713")} ${file}`);
|
|
4014
4806
|
}
|
|
4015
4807
|
if (result.deleted.length > 0) {
|
|
4016
|
-
console.log(
|
|
4808
|
+
console.log(chalk5.bold("\nFiles removed:"));
|
|
4017
4809
|
for (const file of result.deleted) {
|
|
4018
|
-
console.log(` ${
|
|
4810
|
+
console.log(` ${chalk5.red("\u2717")} ${file}`);
|
|
4019
4811
|
}
|
|
4020
4812
|
}
|
|
4021
4813
|
if (result.backupDir) {
|
|
4022
|
-
console.log(
|
|
4814
|
+
console.log(chalk5.dim(`
|
|
4023
4815
|
Backups saved to ${result.backupDir}`));
|
|
4024
4816
|
}
|
|
4025
4817
|
} catch (err) {
|
|
4026
4818
|
writeSpinner.fail("Failed to write files");
|
|
4027
|
-
console.error(
|
|
4819
|
+
console.error(chalk5.red(err instanceof Error ? err.message : "Unknown error"));
|
|
4028
4820
|
throw new Error("__exit__");
|
|
4029
4821
|
}
|
|
4822
|
+
try {
|
|
4823
|
+
const mcpResult = await discoverAndInstallMcps(targetAgent, fingerprint, process.cwd());
|
|
4824
|
+
if (mcpResult.installed > 0) {
|
|
4825
|
+
console.log(chalk5.bold(`
|
|
4826
|
+
${mcpResult.installed} MCP server${mcpResult.installed > 1 ? "s" : ""} configured`));
|
|
4827
|
+
for (const name of mcpResult.names) {
|
|
4828
|
+
console.log(` ${chalk5.green("\u2713")} ${name}`);
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
} catch (err) {
|
|
4832
|
+
console.log(chalk5.dim(" MCP discovery skipped: " + (err instanceof Error ? err.message : "unknown error")));
|
|
4833
|
+
}
|
|
4030
4834
|
ensurePermissions();
|
|
4031
4835
|
const sha = getCurrentHeadSha();
|
|
4032
4836
|
writeState({
|
|
@@ -4036,56 +4840,56 @@ async function initCommand(options) {
|
|
|
4036
4840
|
});
|
|
4037
4841
|
console.log("");
|
|
4038
4842
|
console.log(title.bold(" Keep your configs fresh\n"));
|
|
4039
|
-
console.log(
|
|
4843
|
+
console.log(chalk5.dim(" Caliber can automatically update your agent configs when your code changes.\n"));
|
|
4040
4844
|
const hookChoice = await promptHookType(targetAgent);
|
|
4041
4845
|
if (hookChoice === "claude" || hookChoice === "both") {
|
|
4042
4846
|
const hookResult = installHook();
|
|
4043
4847
|
if (hookResult.installed) {
|
|
4044
|
-
console.log(` ${
|
|
4045
|
-
console.log(
|
|
4848
|
+
console.log(` ${chalk5.green("\u2713")} Claude Code hook installed \u2014 docs update on session end`);
|
|
4849
|
+
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber hooks remove") + chalk5.dim(" to disable"));
|
|
4046
4850
|
} else if (hookResult.alreadyInstalled) {
|
|
4047
|
-
console.log(
|
|
4851
|
+
console.log(chalk5.dim(" Claude Code hook already installed"));
|
|
4048
4852
|
}
|
|
4049
4853
|
const learnResult = installLearningHooks();
|
|
4050
4854
|
if (learnResult.installed) {
|
|
4051
|
-
console.log(` ${
|
|
4052
|
-
console.log(
|
|
4855
|
+
console.log(` ${chalk5.green("\u2713")} Learning hooks installed \u2014 session insights captured automatically`);
|
|
4856
|
+
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber learn remove") + chalk5.dim(" to disable"));
|
|
4053
4857
|
} else if (learnResult.alreadyInstalled) {
|
|
4054
|
-
console.log(
|
|
4858
|
+
console.log(chalk5.dim(" Learning hooks already installed"));
|
|
4055
4859
|
}
|
|
4056
4860
|
}
|
|
4057
4861
|
if (hookChoice === "precommit" || hookChoice === "both") {
|
|
4058
4862
|
const precommitResult = installPreCommitHook();
|
|
4059
4863
|
if (precommitResult.installed) {
|
|
4060
|
-
console.log(` ${
|
|
4061
|
-
console.log(
|
|
4864
|
+
console.log(` ${chalk5.green("\u2713")} Pre-commit hook installed \u2014 docs refresh before each commit`);
|
|
4865
|
+
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber hooks remove-precommit") + chalk5.dim(" to disable"));
|
|
4062
4866
|
} else if (precommitResult.alreadyInstalled) {
|
|
4063
|
-
console.log(
|
|
4867
|
+
console.log(chalk5.dim(" Pre-commit hook already installed"));
|
|
4064
4868
|
} else {
|
|
4065
|
-
console.log(
|
|
4869
|
+
console.log(chalk5.yellow(" Could not install pre-commit hook (not a git repository?)"));
|
|
4066
4870
|
}
|
|
4067
4871
|
}
|
|
4068
4872
|
if (hookChoice === "skip") {
|
|
4069
|
-
console.log(
|
|
4873
|
+
console.log(chalk5.dim(" Skipped auto-refresh hooks. Run ") + chalk5.hex("#83D1EB")("caliber hooks install") + chalk5.dim(" later to enable."));
|
|
4070
4874
|
}
|
|
4071
4875
|
const afterScore = computeLocalScore(process.cwd(), targetAgent);
|
|
4072
4876
|
if (afterScore.score < baselineScore.score) {
|
|
4073
4877
|
console.log("");
|
|
4074
|
-
console.log(
|
|
4878
|
+
console.log(chalk5.yellow(` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`));
|
|
4075
4879
|
try {
|
|
4076
4880
|
const { restored, removed } = undoSetup();
|
|
4077
4881
|
if (restored.length > 0 || removed.length > 0) {
|
|
4078
|
-
console.log(
|
|
4882
|
+
console.log(chalk5.dim(` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`));
|
|
4079
4883
|
}
|
|
4080
4884
|
} catch {
|
|
4081
4885
|
}
|
|
4082
|
-
console.log(
|
|
4886
|
+
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber onboard --force") + chalk5.dim(" to override.\n"));
|
|
4083
4887
|
return;
|
|
4084
4888
|
}
|
|
4085
4889
|
displayScoreDelta(baselineScore, afterScore);
|
|
4086
|
-
console.log(
|
|
4087
|
-
console.log(
|
|
4088
|
-
console.log(
|
|
4890
|
+
console.log(chalk5.bold.green(" Onboarding complete! Your project is ready for AI-assisted development."));
|
|
4891
|
+
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber undo") + chalk5.dim(" to revert changes.\n"));
|
|
4892
|
+
console.log(chalk5.bold(" Next steps:\n"));
|
|
4089
4893
|
console.log(` ${title("caliber score")} See your full config breakdown`);
|
|
4090
4894
|
console.log(` ${title("caliber recommend")} Discover community skills for your stack`);
|
|
4091
4895
|
console.log(` ${title("caliber undo")} Revert all changes from this run`);
|
|
@@ -4093,7 +4897,7 @@ async function initCommand(options) {
|
|
|
4093
4897
|
}
|
|
4094
4898
|
async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
4095
4899
|
while (true) {
|
|
4096
|
-
const message = await
|
|
4900
|
+
const message = await promptInput3("\nWhat would you like to change?");
|
|
4097
4901
|
if (!message || message.toLowerCase() === "done" || message.toLowerCase() === "accept") {
|
|
4098
4902
|
return currentSetup;
|
|
4099
4903
|
}
|
|
@@ -4102,12 +4906,12 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
|
4102
4906
|
}
|
|
4103
4907
|
const isValid = await classifyRefineIntent(message);
|
|
4104
4908
|
if (!isValid) {
|
|
4105
|
-
console.log(
|
|
4106
|
-
console.log(
|
|
4107
|
-
console.log(
|
|
4909
|
+
console.log(chalk5.dim(" This doesn't look like a config change request."));
|
|
4910
|
+
console.log(chalk5.dim(" Describe what to add, remove, or modify in your configs."));
|
|
4911
|
+
console.log(chalk5.dim(' Type "done" to accept the current setup.\n'));
|
|
4108
4912
|
continue;
|
|
4109
4913
|
}
|
|
4110
|
-
const refineSpinner =
|
|
4914
|
+
const refineSpinner = ora2("Refining setup...").start();
|
|
4111
4915
|
const refineMessages = new SpinnerMessages(refineSpinner, REFINE_MESSAGES);
|
|
4112
4916
|
refineMessages.start();
|
|
4113
4917
|
const refined = await refineSetup(
|
|
@@ -4125,16 +4929,16 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
|
4125
4929
|
});
|
|
4126
4930
|
refineSpinner.succeed("Setup updated");
|
|
4127
4931
|
printSetupSummary(refined);
|
|
4128
|
-
console.log(
|
|
4932
|
+
console.log(chalk5.dim('Type "done" to accept, or describe more changes.'));
|
|
4129
4933
|
} else {
|
|
4130
4934
|
refineSpinner.fail("Refinement failed \u2014 could not parse AI response.");
|
|
4131
|
-
console.log(
|
|
4935
|
+
console.log(chalk5.dim('Try rephrasing your request, or type "done" to keep the current setup.'));
|
|
4132
4936
|
}
|
|
4133
4937
|
}
|
|
4134
4938
|
}
|
|
4135
4939
|
function summarizeSetup(action, setup) {
|
|
4136
4940
|
const descriptions = setup.fileDescriptions;
|
|
4137
|
-
const files = descriptions ? Object.entries(descriptions).map(([
|
|
4941
|
+
const files = descriptions ? Object.entries(descriptions).map(([path26, desc]) => ` ${path26}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
|
|
4138
4942
|
return `${action}. Files:
|
|
4139
4943
|
${files}`;
|
|
4140
4944
|
}
|
|
@@ -4155,10 +4959,40 @@ Return {"valid": true} or {"valid": false}. Nothing else.`,
|
|
|
4155
4959
|
return true;
|
|
4156
4960
|
}
|
|
4157
4961
|
}
|
|
4158
|
-
function
|
|
4159
|
-
const
|
|
4962
|
+
async function evaluateDismissals(failingChecks, fingerprint) {
|
|
4963
|
+
const fastModel = process.env.ANTHROPIC_SMALL_FAST_MODEL;
|
|
4964
|
+
const checkList = failingChecks.map((c) => ({
|
|
4965
|
+
id: c.id,
|
|
4966
|
+
name: c.name,
|
|
4967
|
+
suggestion: c.suggestion
|
|
4968
|
+
}));
|
|
4969
|
+
try {
|
|
4970
|
+
const result = await llmJsonCall({
|
|
4971
|
+
system: `You evaluate whether scoring checks are applicable to a project.
|
|
4972
|
+
Given the project's languages/frameworks and a list of failing checks, return which checks are NOT applicable.
|
|
4973
|
+
|
|
4974
|
+
Only dismiss checks that truly don't apply \u2014 e.g. "Build/test/lint commands" for a pure Terraform/HCL repo with no build system.
|
|
4975
|
+
Do NOT dismiss checks that could reasonably apply even if the project doesn't use them yet.
|
|
4976
|
+
|
|
4977
|
+
Return {"dismissed": [{"id": "check_id", "reason": "brief reason"}]} or {"dismissed": []} if all apply.`,
|
|
4978
|
+
prompt: `Languages: ${fingerprint.languages.join(", ") || "none"}
|
|
4979
|
+
Frameworks: ${fingerprint.frameworks.join(", ") || "none"}
|
|
4980
|
+
|
|
4981
|
+
Failing checks:
|
|
4982
|
+
${JSON.stringify(checkList, null, 2)}`,
|
|
4983
|
+
maxTokens: 200,
|
|
4984
|
+
...fastModel ? { model: fastModel } : {}
|
|
4985
|
+
});
|
|
4986
|
+
if (!Array.isArray(result.dismissed)) return [];
|
|
4987
|
+
return result.dismissed.filter((d) => d.id && d.reason && failingChecks.some((c) => c.id === d.id)).map((d) => ({ id: d.id, reason: d.reason, dismissedAt: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
4988
|
+
} catch {
|
|
4989
|
+
return [];
|
|
4990
|
+
}
|
|
4991
|
+
}
|
|
4992
|
+
function promptInput3(question) {
|
|
4993
|
+
const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
|
|
4160
4994
|
return new Promise((resolve2) => {
|
|
4161
|
-
rl.question(
|
|
4995
|
+
rl.question(chalk5.cyan(`${question} `), (answer) => {
|
|
4162
4996
|
rl.close();
|
|
4163
4997
|
resolve2(answer.trim());
|
|
4164
4998
|
});
|
|
@@ -4220,12 +5054,12 @@ async function openReview(method, stagedFiles) {
|
|
|
4220
5054
|
originalPath: f.originalPath,
|
|
4221
5055
|
proposedPath: f.proposedPath
|
|
4222
5056
|
})));
|
|
4223
|
-
console.log(
|
|
5057
|
+
console.log(chalk5.dim(" Diffs opened in your editor.\n"));
|
|
4224
5058
|
return;
|
|
4225
5059
|
}
|
|
4226
5060
|
const fileInfos = stagedFiles.map((file) => {
|
|
4227
|
-
const proposed =
|
|
4228
|
-
const current = file.currentPath ?
|
|
5061
|
+
const proposed = fs20.readFileSync(file.proposedPath, "utf-8");
|
|
5062
|
+
const current = file.currentPath ? fs20.readFileSync(file.currentPath, "utf-8") : "";
|
|
4229
5063
|
const patch = createTwoFilesPatch(
|
|
4230
5064
|
file.isNew ? "/dev/null" : file.relativePath,
|
|
4231
5065
|
file.relativePath,
|
|
@@ -4251,8 +5085,8 @@ async function openReview(method, stagedFiles) {
|
|
|
4251
5085
|
async function interactiveDiffExplorer(files) {
|
|
4252
5086
|
if (!process.stdin.isTTY) {
|
|
4253
5087
|
for (const f of files) {
|
|
4254
|
-
const icon = f.isNew ?
|
|
4255
|
-
const stats = f.isNew ?
|
|
5088
|
+
const icon = f.isNew ? chalk5.green("+") : chalk5.yellow("~");
|
|
5089
|
+
const stats = f.isNew ? chalk5.dim(`${f.lines} lines`) : `${chalk5.green(`+${f.added}`)} ${chalk5.red(`-${f.removed}`)}`;
|
|
4256
5090
|
console.log(` ${icon} ${f.relativePath} ${stats}`);
|
|
4257
5091
|
}
|
|
4258
5092
|
console.log("");
|
|
@@ -4268,47 +5102,47 @@ async function interactiveDiffExplorer(files) {
|
|
|
4268
5102
|
}
|
|
4269
5103
|
function renderFileList() {
|
|
4270
5104
|
const lines = [];
|
|
4271
|
-
lines.push(
|
|
5105
|
+
lines.push(chalk5.bold(" Review changes"));
|
|
4272
5106
|
lines.push("");
|
|
4273
5107
|
for (let i = 0; i < files.length; i++) {
|
|
4274
5108
|
const f = files[i];
|
|
4275
|
-
const ptr = i === cursor ?
|
|
4276
|
-
const icon = f.isNew ?
|
|
4277
|
-
const stats = f.isNew ?
|
|
5109
|
+
const ptr = i === cursor ? chalk5.cyan(">") : " ";
|
|
5110
|
+
const icon = f.isNew ? chalk5.green("+") : chalk5.yellow("~");
|
|
5111
|
+
const stats = f.isNew ? chalk5.dim(`${f.lines} lines`) : `${chalk5.green(`+${f.added}`)} ${chalk5.red(`-${f.removed}`)}`;
|
|
4278
5112
|
lines.push(` ${ptr} ${icon} ${f.relativePath} ${stats}`);
|
|
4279
5113
|
}
|
|
4280
5114
|
lines.push("");
|
|
4281
|
-
lines.push(
|
|
5115
|
+
lines.push(chalk5.dim(" \u2191\u2193 navigate \u23CE view diff q done"));
|
|
4282
5116
|
return lines.join("\n");
|
|
4283
5117
|
}
|
|
4284
5118
|
function renderDiff(index) {
|
|
4285
5119
|
const f = files[index];
|
|
4286
5120
|
const lines = [];
|
|
4287
|
-
const header = f.isNew ? ` ${
|
|
5121
|
+
const header = f.isNew ? ` ${chalk5.green("+")} ${f.relativePath} ${chalk5.dim("(new file)")}` : ` ${chalk5.yellow("~")} ${f.relativePath} ${chalk5.green(`+${f.added}`)} ${chalk5.red(`-${f.removed}`)}`;
|
|
4288
5122
|
lines.push(header);
|
|
4289
|
-
lines.push(
|
|
5123
|
+
lines.push(chalk5.dim(" " + "\u2500".repeat(60)));
|
|
4290
5124
|
const patchLines = f.patch.split("\n");
|
|
4291
5125
|
const bodyLines = patchLines.slice(4);
|
|
4292
5126
|
const maxVisible = getTermHeight() - 4;
|
|
4293
5127
|
const visibleLines = bodyLines.slice(scrollOffset, scrollOffset + maxVisible);
|
|
4294
5128
|
for (const line of visibleLines) {
|
|
4295
5129
|
if (line.startsWith("+")) {
|
|
4296
|
-
lines.push(
|
|
5130
|
+
lines.push(chalk5.green(" " + line));
|
|
4297
5131
|
} else if (line.startsWith("-")) {
|
|
4298
|
-
lines.push(
|
|
5132
|
+
lines.push(chalk5.red(" " + line));
|
|
4299
5133
|
} else if (line.startsWith("@@")) {
|
|
4300
|
-
lines.push(
|
|
5134
|
+
lines.push(chalk5.cyan(" " + line));
|
|
4301
5135
|
} else {
|
|
4302
|
-
lines.push(
|
|
5136
|
+
lines.push(chalk5.dim(" " + line));
|
|
4303
5137
|
}
|
|
4304
5138
|
}
|
|
4305
5139
|
const totalBody = bodyLines.length;
|
|
4306
5140
|
if (totalBody > maxVisible) {
|
|
4307
5141
|
const pct = Math.round((scrollOffset + maxVisible) / totalBody * 100);
|
|
4308
|
-
lines.push(
|
|
5142
|
+
lines.push(chalk5.dim(` \u2500\u2500 ${Math.min(pct, 100)}% \u2500\u2500`));
|
|
4309
5143
|
}
|
|
4310
5144
|
lines.push("");
|
|
4311
|
-
lines.push(
|
|
5145
|
+
lines.push(chalk5.dim(" \u2191\u2193 scroll \u23B5/esc back to file list"));
|
|
4312
5146
|
return lines.join("\n");
|
|
4313
5147
|
}
|
|
4314
5148
|
function draw(initial) {
|
|
@@ -4402,46 +5236,46 @@ function printSetupSummary(setup) {
|
|
|
4402
5236
|
const fileDescriptions = setup.fileDescriptions;
|
|
4403
5237
|
const deletions = setup.deletions;
|
|
4404
5238
|
console.log("");
|
|
4405
|
-
console.log(
|
|
5239
|
+
console.log(chalk5.bold(" Proposed changes:\n"));
|
|
4406
5240
|
const getDescription = (filePath) => {
|
|
4407
5241
|
return fileDescriptions?.[filePath];
|
|
4408
5242
|
};
|
|
4409
5243
|
if (claude) {
|
|
4410
5244
|
if (claude.claudeMd) {
|
|
4411
|
-
const icon =
|
|
5245
|
+
const icon = fs20.existsSync("CLAUDE.md") ? chalk5.yellow("~") : chalk5.green("+");
|
|
4412
5246
|
const desc = getDescription("CLAUDE.md");
|
|
4413
|
-
console.log(` ${icon} ${
|
|
4414
|
-
if (desc) console.log(
|
|
5247
|
+
console.log(` ${icon} ${chalk5.bold("CLAUDE.md")}`);
|
|
5248
|
+
if (desc) console.log(chalk5.dim(` ${desc}`));
|
|
4415
5249
|
console.log("");
|
|
4416
5250
|
}
|
|
4417
5251
|
const skills = claude.skills;
|
|
4418
5252
|
if (Array.isArray(skills) && skills.length > 0) {
|
|
4419
5253
|
for (const skill of skills) {
|
|
4420
5254
|
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
4421
|
-
const icon =
|
|
5255
|
+
const icon = fs20.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
4422
5256
|
const desc = getDescription(skillPath);
|
|
4423
|
-
console.log(` ${icon} ${
|
|
4424
|
-
console.log(
|
|
5257
|
+
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
5258
|
+
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
4425
5259
|
console.log("");
|
|
4426
5260
|
}
|
|
4427
5261
|
}
|
|
4428
5262
|
}
|
|
4429
5263
|
if (cursor) {
|
|
4430
5264
|
if (cursor.cursorrules) {
|
|
4431
|
-
const icon =
|
|
5265
|
+
const icon = fs20.existsSync(".cursorrules") ? chalk5.yellow("~") : chalk5.green("+");
|
|
4432
5266
|
const desc = getDescription(".cursorrules");
|
|
4433
|
-
console.log(` ${icon} ${
|
|
4434
|
-
if (desc) console.log(
|
|
5267
|
+
console.log(` ${icon} ${chalk5.bold(".cursorrules")}`);
|
|
5268
|
+
if (desc) console.log(chalk5.dim(` ${desc}`));
|
|
4435
5269
|
console.log("");
|
|
4436
5270
|
}
|
|
4437
5271
|
const cursorSkills = cursor.skills;
|
|
4438
5272
|
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
4439
5273
|
for (const skill of cursorSkills) {
|
|
4440
5274
|
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
4441
|
-
const icon =
|
|
5275
|
+
const icon = fs20.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
4442
5276
|
const desc = getDescription(skillPath);
|
|
4443
|
-
console.log(` ${icon} ${
|
|
4444
|
-
console.log(
|
|
5277
|
+
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
5278
|
+
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
4445
5279
|
console.log("");
|
|
4446
5280
|
}
|
|
4447
5281
|
}
|
|
@@ -4449,32 +5283,32 @@ function printSetupSummary(setup) {
|
|
|
4449
5283
|
if (Array.isArray(rules) && rules.length > 0) {
|
|
4450
5284
|
for (const rule of rules) {
|
|
4451
5285
|
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
4452
|
-
const icon =
|
|
5286
|
+
const icon = fs20.existsSync(rulePath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
4453
5287
|
const desc = getDescription(rulePath);
|
|
4454
|
-
console.log(` ${icon} ${
|
|
5288
|
+
console.log(` ${icon} ${chalk5.bold(rulePath)}`);
|
|
4455
5289
|
if (desc) {
|
|
4456
|
-
console.log(
|
|
5290
|
+
console.log(chalk5.dim(` ${desc}`));
|
|
4457
5291
|
} else {
|
|
4458
5292
|
const firstLine = rule.content.split("\n").filter((l) => l.trim() && !l.trim().startsWith("#"))[0];
|
|
4459
|
-
if (firstLine) console.log(
|
|
5293
|
+
if (firstLine) console.log(chalk5.dim(` ${firstLine.trim().slice(0, 80)}`));
|
|
4460
5294
|
}
|
|
4461
5295
|
console.log("");
|
|
4462
5296
|
}
|
|
4463
5297
|
}
|
|
4464
5298
|
}
|
|
4465
|
-
if (!
|
|
4466
|
-
console.log(` ${
|
|
4467
|
-
console.log(
|
|
5299
|
+
if (!fs20.existsSync("AGENTS.md")) {
|
|
5300
|
+
console.log(` ${chalk5.green("+")} ${chalk5.bold("AGENTS.md")}`);
|
|
5301
|
+
console.log(chalk5.dim(" Cross-agent coordination file"));
|
|
4468
5302
|
console.log("");
|
|
4469
5303
|
}
|
|
4470
5304
|
if (Array.isArray(deletions) && deletions.length > 0) {
|
|
4471
5305
|
for (const del of deletions) {
|
|
4472
|
-
console.log(` ${
|
|
4473
|
-
console.log(
|
|
5306
|
+
console.log(` ${chalk5.red("-")} ${chalk5.bold(del.filePath)}`);
|
|
5307
|
+
console.log(chalk5.dim(` ${del.reason}`));
|
|
4474
5308
|
console.log("");
|
|
4475
5309
|
}
|
|
4476
5310
|
}
|
|
4477
|
-
console.log(` ${
|
|
5311
|
+
console.log(` ${chalk5.green("+")} ${chalk5.dim("new")} ${chalk5.yellow("~")} ${chalk5.dim("modified")} ${chalk5.red("-")} ${chalk5.dim("removed")}`);
|
|
4478
5312
|
console.log("");
|
|
4479
5313
|
}
|
|
4480
5314
|
function buildSkillContent(skill) {
|
|
@@ -4490,8 +5324,8 @@ function ensurePermissions() {
|
|
|
4490
5324
|
const settingsPath = ".claude/settings.json";
|
|
4491
5325
|
let settings = {};
|
|
4492
5326
|
try {
|
|
4493
|
-
if (
|
|
4494
|
-
settings = JSON.parse(
|
|
5327
|
+
if (fs20.existsSync(settingsPath)) {
|
|
5328
|
+
settings = JSON.parse(fs20.readFileSync(settingsPath, "utf-8"));
|
|
4495
5329
|
}
|
|
4496
5330
|
} catch {
|
|
4497
5331
|
}
|
|
@@ -4505,8 +5339,8 @@ function ensurePermissions() {
|
|
|
4505
5339
|
"Bash(git *)"
|
|
4506
5340
|
];
|
|
4507
5341
|
settings.permissions = permissions;
|
|
4508
|
-
if (!
|
|
4509
|
-
|
|
5342
|
+
if (!fs20.existsSync(".claude")) fs20.mkdirSync(".claude", { recursive: true });
|
|
5343
|
+
fs20.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4510
5344
|
}
|
|
4511
5345
|
function collectSetupFiles(setup) {
|
|
4512
5346
|
const files = [];
|
|
@@ -4536,7 +5370,7 @@ function collectSetupFiles(setup) {
|
|
|
4536
5370
|
}
|
|
4537
5371
|
}
|
|
4538
5372
|
}
|
|
4539
|
-
if (!
|
|
5373
|
+
if (!fs20.existsSync("AGENTS.md")) {
|
|
4540
5374
|
const agentRefs = [];
|
|
4541
5375
|
if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
|
|
4542
5376
|
if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
|
|
@@ -4555,10 +5389,10 @@ ${agentRefs.join(" ")}
|
|
|
4555
5389
|
}
|
|
4556
5390
|
|
|
4557
5391
|
// src/commands/undo.ts
|
|
4558
|
-
import
|
|
4559
|
-
import
|
|
5392
|
+
import chalk6 from "chalk";
|
|
5393
|
+
import ora3 from "ora";
|
|
4560
5394
|
function undoCommand() {
|
|
4561
|
-
const spinner =
|
|
5395
|
+
const spinner = ora3("Reverting setup...").start();
|
|
4562
5396
|
try {
|
|
4563
5397
|
const { restored, removed } = undoSetup();
|
|
4564
5398
|
if (restored.length === 0 && removed.length === 0) {
|
|
@@ -4567,27 +5401,27 @@ function undoCommand() {
|
|
|
4567
5401
|
}
|
|
4568
5402
|
spinner.succeed("Setup reverted successfully.\n");
|
|
4569
5403
|
if (restored.length > 0) {
|
|
4570
|
-
console.log(
|
|
5404
|
+
console.log(chalk6.cyan(" Restored from backup:"));
|
|
4571
5405
|
for (const file of restored) {
|
|
4572
|
-
console.log(` ${
|
|
5406
|
+
console.log(` ${chalk6.green("\u21A9")} ${file}`);
|
|
4573
5407
|
}
|
|
4574
5408
|
}
|
|
4575
5409
|
if (removed.length > 0) {
|
|
4576
|
-
console.log(
|
|
5410
|
+
console.log(chalk6.cyan(" Removed:"));
|
|
4577
5411
|
for (const file of removed) {
|
|
4578
|
-
console.log(` ${
|
|
5412
|
+
console.log(` ${chalk6.red("\u2717")} ${file}`);
|
|
4579
5413
|
}
|
|
4580
5414
|
}
|
|
4581
5415
|
console.log("");
|
|
4582
5416
|
} catch (err) {
|
|
4583
|
-
spinner.fail(
|
|
5417
|
+
spinner.fail(chalk6.red(err instanceof Error ? err.message : "Undo failed"));
|
|
4584
5418
|
throw new Error("__exit__");
|
|
4585
5419
|
}
|
|
4586
5420
|
}
|
|
4587
5421
|
|
|
4588
5422
|
// src/commands/status.ts
|
|
4589
|
-
import
|
|
4590
|
-
import
|
|
5423
|
+
import chalk7 from "chalk";
|
|
5424
|
+
import fs21 from "fs";
|
|
4591
5425
|
async function statusCommand(options) {
|
|
4592
5426
|
const config = loadConfig();
|
|
4593
5427
|
const manifest = readManifest();
|
|
@@ -4600,45 +5434,45 @@ async function statusCommand(options) {
|
|
|
4600
5434
|
}, null, 2));
|
|
4601
5435
|
return;
|
|
4602
5436
|
}
|
|
4603
|
-
console.log(
|
|
5437
|
+
console.log(chalk7.bold("\nCaliber Status\n"));
|
|
4604
5438
|
if (config) {
|
|
4605
|
-
console.log(` LLM: ${
|
|
5439
|
+
console.log(` LLM: ${chalk7.green(config.provider)} (${config.model})`);
|
|
4606
5440
|
} else {
|
|
4607
|
-
console.log(` LLM: ${
|
|
5441
|
+
console.log(` LLM: ${chalk7.yellow("Not configured")} \u2014 run ${chalk7.hex("#83D1EB")("caliber config")}`);
|
|
4608
5442
|
}
|
|
4609
5443
|
if (!manifest) {
|
|
4610
|
-
console.log(` Setup: ${
|
|
4611
|
-
console.log(
|
|
5444
|
+
console.log(` Setup: ${chalk7.dim("No setup applied")}`);
|
|
5445
|
+
console.log(chalk7.dim("\n Run ") + chalk7.hex("#83D1EB")("caliber onboard") + chalk7.dim(" to get started.\n"));
|
|
4612
5446
|
return;
|
|
4613
5447
|
}
|
|
4614
|
-
console.log(` Files managed: ${
|
|
5448
|
+
console.log(` Files managed: ${chalk7.cyan(manifest.entries.length.toString())}`);
|
|
4615
5449
|
for (const entry of manifest.entries) {
|
|
4616
|
-
const exists =
|
|
4617
|
-
const icon = exists ?
|
|
5450
|
+
const exists = fs21.existsSync(entry.path);
|
|
5451
|
+
const icon = exists ? chalk7.green("\u2713") : chalk7.red("\u2717");
|
|
4618
5452
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
4619
5453
|
}
|
|
4620
5454
|
console.log("");
|
|
4621
5455
|
}
|
|
4622
5456
|
|
|
4623
5457
|
// src/commands/regenerate.ts
|
|
4624
|
-
import
|
|
4625
|
-
import
|
|
5458
|
+
import chalk8 from "chalk";
|
|
5459
|
+
import ora4 from "ora";
|
|
4626
5460
|
import confirm from "@inquirer/confirm";
|
|
4627
5461
|
async function regenerateCommand(options) {
|
|
4628
5462
|
const config = loadConfig();
|
|
4629
5463
|
if (!config) {
|
|
4630
|
-
console.log(
|
|
5464
|
+
console.log(chalk8.red("No LLM provider configured. Run ") + chalk8.hex("#83D1EB")("caliber config") + chalk8.red(" (e.g. choose Cursor) or set ANTHROPIC_API_KEY."));
|
|
4631
5465
|
throw new Error("__exit__");
|
|
4632
5466
|
}
|
|
4633
5467
|
const manifest = readManifest();
|
|
4634
5468
|
if (!manifest) {
|
|
4635
|
-
console.log(
|
|
5469
|
+
console.log(chalk8.yellow("No existing setup found. Run ") + chalk8.hex("#83D1EB")("caliber onboard") + chalk8.yellow(" first."));
|
|
4636
5470
|
throw new Error("__exit__");
|
|
4637
5471
|
}
|
|
4638
|
-
const spinner =
|
|
5472
|
+
const spinner = ora4("Re-analyzing project...").start();
|
|
4639
5473
|
const fingerprint = collectFingerprint(process.cwd());
|
|
4640
5474
|
spinner.succeed("Project re-analyzed");
|
|
4641
|
-
const genSpinner =
|
|
5475
|
+
const genSpinner = ora4("Regenerating setup...").start();
|
|
4642
5476
|
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES);
|
|
4643
5477
|
genMessages.start();
|
|
4644
5478
|
let generatedSetup = null;
|
|
@@ -4675,38 +5509,38 @@ async function regenerateCommand(options) {
|
|
|
4675
5509
|
}
|
|
4676
5510
|
genSpinner.succeed("Setup regenerated");
|
|
4677
5511
|
if (options.dryRun) {
|
|
4678
|
-
console.log(
|
|
5512
|
+
console.log(chalk8.yellow("\n[Dry run] Would write:"));
|
|
4679
5513
|
console.log(JSON.stringify(generatedSetup, null, 2));
|
|
4680
5514
|
return;
|
|
4681
5515
|
}
|
|
4682
5516
|
const shouldApply = await confirm({ message: "Apply regenerated setup?", default: true });
|
|
4683
5517
|
if (!shouldApply) {
|
|
4684
|
-
console.log(
|
|
5518
|
+
console.log(chalk8.dim("Regeneration cancelled."));
|
|
4685
5519
|
return;
|
|
4686
5520
|
}
|
|
4687
|
-
const writeSpinner =
|
|
5521
|
+
const writeSpinner = ora4("Updating config files...").start();
|
|
4688
5522
|
const result = writeSetup(generatedSetup);
|
|
4689
5523
|
writeSpinner.succeed("Config files updated");
|
|
4690
5524
|
for (const file of result.written) {
|
|
4691
|
-
console.log(` ${
|
|
5525
|
+
console.log(` ${chalk8.green("\u2713")} ${file}`);
|
|
4692
5526
|
}
|
|
4693
5527
|
console.log("");
|
|
4694
5528
|
}
|
|
4695
5529
|
|
|
4696
5530
|
// src/commands/recommend.ts
|
|
4697
|
-
import
|
|
4698
|
-
import
|
|
5531
|
+
import chalk9 from "chalk";
|
|
5532
|
+
import ora5 from "ora";
|
|
4699
5533
|
import { mkdirSync, readFileSync as readFileSync7, readdirSync as readdirSync5, existsSync as existsSync9, writeFileSync } from "fs";
|
|
4700
5534
|
import { join as join8, dirname as dirname2 } from "path";
|
|
4701
5535
|
|
|
4702
5536
|
// src/scanner/index.ts
|
|
4703
|
-
import
|
|
4704
|
-
import
|
|
5537
|
+
import fs22 from "fs";
|
|
5538
|
+
import path19 from "path";
|
|
4705
5539
|
import crypto2 from "crypto";
|
|
4706
5540
|
function scanLocalState(dir) {
|
|
4707
5541
|
const items = [];
|
|
4708
|
-
const claudeMdPath =
|
|
4709
|
-
if (
|
|
5542
|
+
const claudeMdPath = path19.join(dir, "CLAUDE.md");
|
|
5543
|
+
if (fs22.existsSync(claudeMdPath)) {
|
|
4710
5544
|
items.push({
|
|
4711
5545
|
type: "rule",
|
|
4712
5546
|
platform: "claude",
|
|
@@ -4715,10 +5549,10 @@ function scanLocalState(dir) {
|
|
|
4715
5549
|
path: claudeMdPath
|
|
4716
5550
|
});
|
|
4717
5551
|
}
|
|
4718
|
-
const skillsDir =
|
|
4719
|
-
if (
|
|
4720
|
-
for (const file of
|
|
4721
|
-
const filePath =
|
|
5552
|
+
const skillsDir = path19.join(dir, ".claude", "skills");
|
|
5553
|
+
if (fs22.existsSync(skillsDir)) {
|
|
5554
|
+
for (const file of fs22.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
5555
|
+
const filePath = path19.join(skillsDir, file);
|
|
4722
5556
|
items.push({
|
|
4723
5557
|
type: "skill",
|
|
4724
5558
|
platform: "claude",
|
|
@@ -4728,10 +5562,10 @@ function scanLocalState(dir) {
|
|
|
4728
5562
|
});
|
|
4729
5563
|
}
|
|
4730
5564
|
}
|
|
4731
|
-
const mcpJsonPath =
|
|
4732
|
-
if (
|
|
5565
|
+
const mcpJsonPath = path19.join(dir, ".mcp.json");
|
|
5566
|
+
if (fs22.existsSync(mcpJsonPath)) {
|
|
4733
5567
|
try {
|
|
4734
|
-
const mcpJson = JSON.parse(
|
|
5568
|
+
const mcpJson = JSON.parse(fs22.readFileSync(mcpJsonPath, "utf-8"));
|
|
4735
5569
|
if (mcpJson.mcpServers) {
|
|
4736
5570
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
4737
5571
|
items.push({
|
|
@@ -4746,8 +5580,8 @@ function scanLocalState(dir) {
|
|
|
4746
5580
|
} catch {
|
|
4747
5581
|
}
|
|
4748
5582
|
}
|
|
4749
|
-
const cursorrulesPath =
|
|
4750
|
-
if (
|
|
5583
|
+
const cursorrulesPath = path19.join(dir, ".cursorrules");
|
|
5584
|
+
if (fs22.existsSync(cursorrulesPath)) {
|
|
4751
5585
|
items.push({
|
|
4752
5586
|
type: "rule",
|
|
4753
5587
|
platform: "cursor",
|
|
@@ -4756,10 +5590,10 @@ function scanLocalState(dir) {
|
|
|
4756
5590
|
path: cursorrulesPath
|
|
4757
5591
|
});
|
|
4758
5592
|
}
|
|
4759
|
-
const cursorRulesDir =
|
|
4760
|
-
if (
|
|
4761
|
-
for (const file of
|
|
4762
|
-
const filePath =
|
|
5593
|
+
const cursorRulesDir = path19.join(dir, ".cursor", "rules");
|
|
5594
|
+
if (fs22.existsSync(cursorRulesDir)) {
|
|
5595
|
+
for (const file of fs22.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
5596
|
+
const filePath = path19.join(cursorRulesDir, file);
|
|
4763
5597
|
items.push({
|
|
4764
5598
|
type: "rule",
|
|
4765
5599
|
platform: "cursor",
|
|
@@ -4769,12 +5603,12 @@ function scanLocalState(dir) {
|
|
|
4769
5603
|
});
|
|
4770
5604
|
}
|
|
4771
5605
|
}
|
|
4772
|
-
const cursorSkillsDir =
|
|
4773
|
-
if (
|
|
5606
|
+
const cursorSkillsDir = path19.join(dir, ".cursor", "skills");
|
|
5607
|
+
if (fs22.existsSync(cursorSkillsDir)) {
|
|
4774
5608
|
try {
|
|
4775
|
-
for (const name of
|
|
4776
|
-
const skillFile =
|
|
4777
|
-
if (
|
|
5609
|
+
for (const name of fs22.readdirSync(cursorSkillsDir)) {
|
|
5610
|
+
const skillFile = path19.join(cursorSkillsDir, name, "SKILL.md");
|
|
5611
|
+
if (fs22.existsSync(skillFile)) {
|
|
4778
5612
|
items.push({
|
|
4779
5613
|
type: "skill",
|
|
4780
5614
|
platform: "cursor",
|
|
@@ -4787,10 +5621,10 @@ function scanLocalState(dir) {
|
|
|
4787
5621
|
} catch {
|
|
4788
5622
|
}
|
|
4789
5623
|
}
|
|
4790
|
-
const cursorMcpPath =
|
|
4791
|
-
if (
|
|
5624
|
+
const cursorMcpPath = path19.join(dir, ".cursor", "mcp.json");
|
|
5625
|
+
if (fs22.existsSync(cursorMcpPath)) {
|
|
4792
5626
|
try {
|
|
4793
|
-
const mcpJson = JSON.parse(
|
|
5627
|
+
const mcpJson = JSON.parse(fs22.readFileSync(cursorMcpPath, "utf-8"));
|
|
4794
5628
|
if (mcpJson.mcpServers) {
|
|
4795
5629
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
4796
5630
|
items.push({
|
|
@@ -4808,7 +5642,7 @@ function scanLocalState(dir) {
|
|
|
4808
5642
|
return items;
|
|
4809
5643
|
}
|
|
4810
5644
|
function hashFile(filePath) {
|
|
4811
|
-
const text =
|
|
5645
|
+
const text = fs22.readFileSync(filePath, "utf-8");
|
|
4812
5646
|
return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
|
|
4813
5647
|
}
|
|
4814
5648
|
function hashJson(obj) {
|
|
@@ -4966,7 +5800,7 @@ async function searchAllProviders(technologies, platform) {
|
|
|
4966
5800
|
}
|
|
4967
5801
|
return combined;
|
|
4968
5802
|
}
|
|
4969
|
-
async function
|
|
5803
|
+
async function scoreWithLLM2(candidates, projectContext, technologies) {
|
|
4970
5804
|
const candidateList = candidates.map((c, i) => `${i}. "${c.name}" \u2014 ${c.reason || "no description"}`).join("\n");
|
|
4971
5805
|
const scored = await llmJsonCall({
|
|
4972
5806
|
system: `You evaluate whether AI agent skills and tools are relevant to a specific software project.
|
|
@@ -5119,11 +5953,11 @@ async function recommendCommand(options) {
|
|
|
5119
5953
|
...extractTopDeps()
|
|
5120
5954
|
].filter(Boolean))];
|
|
5121
5955
|
if (technologies.length === 0) {
|
|
5122
|
-
console.log(
|
|
5956
|
+
console.log(chalk9.yellow("Could not detect any languages or dependencies. Try running from a project root."));
|
|
5123
5957
|
throw new Error("__exit__");
|
|
5124
5958
|
}
|
|
5125
5959
|
const primaryPlatform = platforms.includes("claude") ? "claude" : platforms[0];
|
|
5126
|
-
const searchSpinner =
|
|
5960
|
+
const searchSpinner = ora5("Searching skill registries...").start();
|
|
5127
5961
|
const allCandidates = await searchAllProviders(technologies, primaryPlatform);
|
|
5128
5962
|
if (!allCandidates.length) {
|
|
5129
5963
|
searchSpinner.succeed("No skills found matching your tech stack.");
|
|
@@ -5136,15 +5970,15 @@ async function recommendCommand(options) {
|
|
|
5136
5970
|
return;
|
|
5137
5971
|
}
|
|
5138
5972
|
searchSpinner.succeed(
|
|
5139
|
-
`Found ${allCandidates.length} skills` + (filteredCount > 0 ?
|
|
5973
|
+
`Found ${allCandidates.length} skills` + (filteredCount > 0 ? chalk9.dim(` (${filteredCount} already installed)`) : "")
|
|
5140
5974
|
);
|
|
5141
5975
|
let results;
|
|
5142
5976
|
const config = loadConfig();
|
|
5143
5977
|
if (config) {
|
|
5144
|
-
const scoreSpinner =
|
|
5978
|
+
const scoreSpinner = ora5("Scoring relevance for your project...").start();
|
|
5145
5979
|
try {
|
|
5146
5980
|
const projectContext = buildProjectContext(process.cwd());
|
|
5147
|
-
results = await
|
|
5981
|
+
results = await scoreWithLLM2(newCandidates, projectContext, technologies);
|
|
5148
5982
|
if (results.length === 0) {
|
|
5149
5983
|
scoreSpinner.succeed("No highly relevant skills found for your specific project.");
|
|
5150
5984
|
return;
|
|
@@ -5157,7 +5991,7 @@ async function recommendCommand(options) {
|
|
|
5157
5991
|
} else {
|
|
5158
5992
|
results = newCandidates.slice(0, 20);
|
|
5159
5993
|
}
|
|
5160
|
-
const fetchSpinner =
|
|
5994
|
+
const fetchSpinner = ora5("Verifying skill availability...").start();
|
|
5161
5995
|
const contentMap = /* @__PURE__ */ new Map();
|
|
5162
5996
|
await Promise.all(results.map(async (rec) => {
|
|
5163
5997
|
const content = await fetchSkillContent(rec);
|
|
@@ -5170,14 +6004,14 @@ async function recommendCommand(options) {
|
|
|
5170
6004
|
}
|
|
5171
6005
|
const unavailableCount = results.length - available.length;
|
|
5172
6006
|
fetchSpinner.succeed(
|
|
5173
|
-
`${available.length} installable skill${available.length > 1 ? "s" : ""}` + (unavailableCount > 0 ?
|
|
6007
|
+
`${available.length} installable skill${available.length > 1 ? "s" : ""}` + (unavailableCount > 0 ? chalk9.dim(` (${unavailableCount} unavailable)`) : "")
|
|
5174
6008
|
);
|
|
5175
|
-
const selected = await
|
|
6009
|
+
const selected = await interactiveSelect2(available);
|
|
5176
6010
|
if (selected?.length) {
|
|
5177
6011
|
await installSkills(selected, platforms, contentMap);
|
|
5178
6012
|
}
|
|
5179
6013
|
}
|
|
5180
|
-
async function
|
|
6014
|
+
async function interactiveSelect2(recs) {
|
|
5181
6015
|
if (!process.stdin.isTTY) {
|
|
5182
6016
|
printRecommendations(recs);
|
|
5183
6017
|
return null;
|
|
@@ -5189,27 +6023,27 @@ async function interactiveSelect(recs) {
|
|
|
5189
6023
|
const hasScores = recs.some((r) => r.score > 0);
|
|
5190
6024
|
function render() {
|
|
5191
6025
|
const lines = [];
|
|
5192
|
-
lines.push(
|
|
6026
|
+
lines.push(chalk9.bold(" Recommendations"));
|
|
5193
6027
|
lines.push("");
|
|
5194
6028
|
if (hasScores) {
|
|
5195
|
-
lines.push(` ${
|
|
6029
|
+
lines.push(` ${chalk9.dim("Score".padEnd(7))} ${chalk9.dim("Name".padEnd(28))} ${chalk9.dim("Why")}`);
|
|
5196
6030
|
} else {
|
|
5197
|
-
lines.push(` ${
|
|
6031
|
+
lines.push(` ${chalk9.dim("Name".padEnd(30))} ${chalk9.dim("Technology".padEnd(18))} ${chalk9.dim("Source")}`);
|
|
5198
6032
|
}
|
|
5199
|
-
lines.push(
|
|
6033
|
+
lines.push(chalk9.dim(" " + "\u2500".repeat(70)));
|
|
5200
6034
|
for (let i = 0; i < recs.length; i++) {
|
|
5201
6035
|
const rec = recs[i];
|
|
5202
|
-
const check = selected.has(i) ?
|
|
5203
|
-
const ptr = i === cursor ?
|
|
6036
|
+
const check = selected.has(i) ? chalk9.green("[x]") : "[ ]";
|
|
6037
|
+
const ptr = i === cursor ? chalk9.cyan(">") : " ";
|
|
5204
6038
|
if (hasScores) {
|
|
5205
|
-
const scoreColor = rec.score >= 90 ?
|
|
5206
|
-
lines.push(` ${ptr} ${check} ${scoreColor(String(rec.score).padStart(3))} ${rec.name.padEnd(26)} ${
|
|
6039
|
+
const scoreColor = rec.score >= 90 ? chalk9.green : rec.score >= 70 ? chalk9.yellow : chalk9.dim;
|
|
6040
|
+
lines.push(` ${ptr} ${check} ${scoreColor(String(rec.score).padStart(3))} ${rec.name.padEnd(26)} ${chalk9.dim(rec.reason.slice(0, 40))}`);
|
|
5207
6041
|
} else {
|
|
5208
|
-
lines.push(` ${ptr} ${check} ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${
|
|
6042
|
+
lines.push(` ${ptr} ${check} ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk9.dim(rec.source_url || "")}`);
|
|
5209
6043
|
}
|
|
5210
6044
|
}
|
|
5211
6045
|
lines.push("");
|
|
5212
|
-
lines.push(
|
|
6046
|
+
lines.push(chalk9.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q cancel"));
|
|
5213
6047
|
return lines.join("\n");
|
|
5214
6048
|
}
|
|
5215
6049
|
function draw(initial) {
|
|
@@ -5258,7 +6092,7 @@ async function interactiveSelect(recs) {
|
|
|
5258
6092
|
case "\n":
|
|
5259
6093
|
cleanup();
|
|
5260
6094
|
if (selected.size === 0) {
|
|
5261
|
-
console.log(
|
|
6095
|
+
console.log(chalk9.dim("\n No skills selected.\n"));
|
|
5262
6096
|
resolve2(null);
|
|
5263
6097
|
} else {
|
|
5264
6098
|
resolve2(Array.from(selected).sort().map((i) => recs[i]));
|
|
@@ -5268,7 +6102,7 @@ async function interactiveSelect(recs) {
|
|
|
5268
6102
|
case "\x1B":
|
|
5269
6103
|
case "":
|
|
5270
6104
|
cleanup();
|
|
5271
|
-
console.log(
|
|
6105
|
+
console.log(chalk9.dim("\n Cancelled.\n"));
|
|
5272
6106
|
resolve2(null);
|
|
5273
6107
|
break;
|
|
5274
6108
|
}
|
|
@@ -5314,7 +6148,7 @@ async function fetchSkillContent(rec) {
|
|
|
5314
6148
|
return null;
|
|
5315
6149
|
}
|
|
5316
6150
|
async function installSkills(recs, platforms, contentMap) {
|
|
5317
|
-
const spinner =
|
|
6151
|
+
const spinner = ora5(`Installing ${recs.length} skill${recs.length > 1 ? "s" : ""}...`).start();
|
|
5318
6152
|
const installed = [];
|
|
5319
6153
|
for (const rec of recs) {
|
|
5320
6154
|
const content = contentMap.get(rec.slug);
|
|
@@ -5330,7 +6164,7 @@ async function installSkills(recs, platforms, contentMap) {
|
|
|
5330
6164
|
if (installed.length > 0) {
|
|
5331
6165
|
spinner.succeed(`Installed ${installed.length} file${installed.length > 1 ? "s" : ""}`);
|
|
5332
6166
|
for (const p of installed) {
|
|
5333
|
-
console.log(
|
|
6167
|
+
console.log(chalk9.green(` \u2713 ${p}`));
|
|
5334
6168
|
}
|
|
5335
6169
|
} else {
|
|
5336
6170
|
spinner.fail("No skills were installed");
|
|
@@ -5339,25 +6173,25 @@ async function installSkills(recs, platforms, contentMap) {
|
|
|
5339
6173
|
}
|
|
5340
6174
|
function printRecommendations(recs) {
|
|
5341
6175
|
const hasScores = recs.some((r) => r.score > 0);
|
|
5342
|
-
console.log(
|
|
6176
|
+
console.log(chalk9.bold("\n Recommendations\n"));
|
|
5343
6177
|
if (hasScores) {
|
|
5344
|
-
console.log(` ${
|
|
6178
|
+
console.log(` ${chalk9.dim("Score".padEnd(7))} ${chalk9.dim("Name".padEnd(28))} ${chalk9.dim("Why")}`);
|
|
5345
6179
|
} else {
|
|
5346
|
-
console.log(` ${
|
|
6180
|
+
console.log(` ${chalk9.dim("Name".padEnd(30))} ${chalk9.dim("Technology".padEnd(18))} ${chalk9.dim("Source")}`);
|
|
5347
6181
|
}
|
|
5348
|
-
console.log(
|
|
6182
|
+
console.log(chalk9.dim(" " + "\u2500".repeat(70)));
|
|
5349
6183
|
for (const rec of recs) {
|
|
5350
6184
|
if (hasScores) {
|
|
5351
|
-
console.log(` ${String(rec.score).padStart(3)} ${rec.name.padEnd(26)} ${
|
|
6185
|
+
console.log(` ${String(rec.score).padStart(3)} ${rec.name.padEnd(26)} ${chalk9.dim(rec.reason.slice(0, 50))}`);
|
|
5352
6186
|
} else {
|
|
5353
|
-
console.log(` ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${
|
|
6187
|
+
console.log(` ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk9.dim(rec.source_url || "")}`);
|
|
5354
6188
|
}
|
|
5355
6189
|
}
|
|
5356
6190
|
console.log("");
|
|
5357
6191
|
}
|
|
5358
6192
|
|
|
5359
6193
|
// src/commands/score.ts
|
|
5360
|
-
import
|
|
6194
|
+
import chalk10 from "chalk";
|
|
5361
6195
|
async function scoreCommand(options) {
|
|
5362
6196
|
const dir = process.cwd();
|
|
5363
6197
|
const target = options.agent ?? readState()?.targetAgent;
|
|
@@ -5371,23 +6205,23 @@ async function scoreCommand(options) {
|
|
|
5371
6205
|
return;
|
|
5372
6206
|
}
|
|
5373
6207
|
displayScore(result);
|
|
5374
|
-
const separator =
|
|
6208
|
+
const separator = chalk10.gray(" " + "\u2500".repeat(53));
|
|
5375
6209
|
console.log(separator);
|
|
5376
6210
|
if (result.score < 40) {
|
|
5377
|
-
console.log(
|
|
6211
|
+
console.log(chalk10.gray(" Run ") + chalk10.hex("#83D1EB")("caliber onboard") + chalk10.gray(" to generate a complete, optimized setup."));
|
|
5378
6212
|
} else if (result.score < 70) {
|
|
5379
|
-
console.log(
|
|
6213
|
+
console.log(chalk10.gray(" Run ") + chalk10.hex("#83D1EB")("caliber onboard") + chalk10.gray(" to improve your setup."));
|
|
5380
6214
|
} else {
|
|
5381
|
-
console.log(
|
|
6215
|
+
console.log(chalk10.green(" Looking good!") + chalk10.gray(" Run ") + chalk10.hex("#83D1EB")("caliber update") + chalk10.gray(" to keep it fresh."));
|
|
5382
6216
|
}
|
|
5383
6217
|
console.log("");
|
|
5384
6218
|
}
|
|
5385
6219
|
|
|
5386
6220
|
// src/commands/refresh.ts
|
|
5387
|
-
import
|
|
5388
|
-
import
|
|
5389
|
-
import
|
|
5390
|
-
import
|
|
6221
|
+
import fs24 from "fs";
|
|
6222
|
+
import path21 from "path";
|
|
6223
|
+
import chalk11 from "chalk";
|
|
6224
|
+
import ora6 from "ora";
|
|
5391
6225
|
|
|
5392
6226
|
// src/lib/git-diff.ts
|
|
5393
6227
|
import { execSync as execSync8 } from "child_process";
|
|
@@ -5462,37 +6296,37 @@ function collectDiff(lastSha) {
|
|
|
5462
6296
|
}
|
|
5463
6297
|
|
|
5464
6298
|
// src/writers/refresh.ts
|
|
5465
|
-
import
|
|
5466
|
-
import
|
|
6299
|
+
import fs23 from "fs";
|
|
6300
|
+
import path20 from "path";
|
|
5467
6301
|
function writeRefreshDocs(docs) {
|
|
5468
6302
|
const written = [];
|
|
5469
6303
|
if (docs.claudeMd) {
|
|
5470
|
-
|
|
6304
|
+
fs23.writeFileSync("CLAUDE.md", docs.claudeMd);
|
|
5471
6305
|
written.push("CLAUDE.md");
|
|
5472
6306
|
}
|
|
5473
6307
|
if (docs.readmeMd) {
|
|
5474
|
-
|
|
6308
|
+
fs23.writeFileSync("README.md", docs.readmeMd);
|
|
5475
6309
|
written.push("README.md");
|
|
5476
6310
|
}
|
|
5477
6311
|
if (docs.cursorrules) {
|
|
5478
|
-
|
|
6312
|
+
fs23.writeFileSync(".cursorrules", docs.cursorrules);
|
|
5479
6313
|
written.push(".cursorrules");
|
|
5480
6314
|
}
|
|
5481
6315
|
if (docs.cursorRules) {
|
|
5482
|
-
const rulesDir =
|
|
5483
|
-
if (!
|
|
6316
|
+
const rulesDir = path20.join(".cursor", "rules");
|
|
6317
|
+
if (!fs23.existsSync(rulesDir)) fs23.mkdirSync(rulesDir, { recursive: true });
|
|
5484
6318
|
for (const rule of docs.cursorRules) {
|
|
5485
|
-
const filePath =
|
|
5486
|
-
|
|
6319
|
+
const filePath = path20.join(rulesDir, rule.filename);
|
|
6320
|
+
fs23.writeFileSync(filePath, rule.content);
|
|
5487
6321
|
written.push(filePath);
|
|
5488
6322
|
}
|
|
5489
6323
|
}
|
|
5490
6324
|
if (docs.claudeSkills) {
|
|
5491
|
-
const skillsDir =
|
|
5492
|
-
if (!
|
|
6325
|
+
const skillsDir = path20.join(".claude", "skills");
|
|
6326
|
+
if (!fs23.existsSync(skillsDir)) fs23.mkdirSync(skillsDir, { recursive: true });
|
|
5493
6327
|
for (const skill of docs.claudeSkills) {
|
|
5494
|
-
const filePath =
|
|
5495
|
-
|
|
6328
|
+
const filePath = path20.join(skillsDir, skill.filename);
|
|
6329
|
+
fs23.writeFileSync(filePath, skill.content);
|
|
5496
6330
|
written.push(filePath);
|
|
5497
6331
|
}
|
|
5498
6332
|
}
|
|
@@ -5567,11 +6401,11 @@ function log(quiet, ...args) {
|
|
|
5567
6401
|
function discoverGitRepos(parentDir) {
|
|
5568
6402
|
const repos = [];
|
|
5569
6403
|
try {
|
|
5570
|
-
const entries =
|
|
6404
|
+
const entries = fs24.readdirSync(parentDir, { withFileTypes: true });
|
|
5571
6405
|
for (const entry of entries) {
|
|
5572
6406
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
5573
|
-
const childPath =
|
|
5574
|
-
if (
|
|
6407
|
+
const childPath = path21.join(parentDir, entry.name);
|
|
6408
|
+
if (fs24.existsSync(path21.join(childPath, ".git"))) {
|
|
5575
6409
|
repos.push(childPath);
|
|
5576
6410
|
}
|
|
5577
6411
|
}
|
|
@@ -5581,7 +6415,7 @@ function discoverGitRepos(parentDir) {
|
|
|
5581
6415
|
}
|
|
5582
6416
|
async function refreshSingleRepo(repoDir, options) {
|
|
5583
6417
|
const quiet = !!options.quiet;
|
|
5584
|
-
const prefix = options.label ? `${
|
|
6418
|
+
const prefix = options.label ? `${chalk11.bold(options.label)} ` : "";
|
|
5585
6419
|
const state = readState();
|
|
5586
6420
|
const lastSha = state?.lastRefreshSha ?? null;
|
|
5587
6421
|
const diff = collectDiff(lastSha);
|
|
@@ -5590,10 +6424,10 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
5590
6424
|
if (currentSha) {
|
|
5591
6425
|
writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
5592
6426
|
}
|
|
5593
|
-
log(quiet,
|
|
6427
|
+
log(quiet, chalk11.dim(`${prefix}No changes since last refresh.`));
|
|
5594
6428
|
return;
|
|
5595
6429
|
}
|
|
5596
|
-
const spinner = quiet ? null :
|
|
6430
|
+
const spinner = quiet ? null : ora6(`${prefix}Analyzing changes...`).start();
|
|
5597
6431
|
const existingDocs = readExistingConfigs(repoDir);
|
|
5598
6432
|
const fingerprint = collectFingerprint(repoDir);
|
|
5599
6433
|
const projectContext = {
|
|
@@ -5622,10 +6456,10 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
5622
6456
|
if (options.dryRun) {
|
|
5623
6457
|
spinner?.info(`${prefix}Dry run \u2014 would update:`);
|
|
5624
6458
|
for (const doc of response.docsUpdated) {
|
|
5625
|
-
console.log(` ${
|
|
6459
|
+
console.log(` ${chalk11.yellow("~")} ${doc}`);
|
|
5626
6460
|
}
|
|
5627
6461
|
if (response.changesSummary) {
|
|
5628
|
-
console.log(
|
|
6462
|
+
console.log(chalk11.dim(`
|
|
5629
6463
|
${response.changesSummary}`));
|
|
5630
6464
|
}
|
|
5631
6465
|
return;
|
|
@@ -5633,10 +6467,10 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
5633
6467
|
const written = writeRefreshDocs(response.updatedDocs);
|
|
5634
6468
|
spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
|
|
5635
6469
|
for (const file of written) {
|
|
5636
|
-
log(quiet, ` ${
|
|
6470
|
+
log(quiet, ` ${chalk11.green("\u2713")} ${file}`);
|
|
5637
6471
|
}
|
|
5638
6472
|
if (response.changesSummary) {
|
|
5639
|
-
log(quiet,
|
|
6473
|
+
log(quiet, chalk11.dim(`
|
|
5640
6474
|
${response.changesSummary}`));
|
|
5641
6475
|
}
|
|
5642
6476
|
if (currentSha) {
|
|
@@ -5649,7 +6483,7 @@ async function refreshCommand(options) {
|
|
|
5649
6483
|
const config = loadConfig();
|
|
5650
6484
|
if (!config) {
|
|
5651
6485
|
if (quiet) return;
|
|
5652
|
-
console.log(
|
|
6486
|
+
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."));
|
|
5653
6487
|
throw new Error("__exit__");
|
|
5654
6488
|
}
|
|
5655
6489
|
if (isGitRepo()) {
|
|
@@ -5659,20 +6493,20 @@ async function refreshCommand(options) {
|
|
|
5659
6493
|
const repos = discoverGitRepos(process.cwd());
|
|
5660
6494
|
if (repos.length === 0) {
|
|
5661
6495
|
if (quiet) return;
|
|
5662
|
-
console.log(
|
|
6496
|
+
console.log(chalk11.red("Not inside a git repository and no git repos found in child directories."));
|
|
5663
6497
|
throw new Error("__exit__");
|
|
5664
6498
|
}
|
|
5665
|
-
log(quiet,
|
|
6499
|
+
log(quiet, chalk11.dim(`Found ${repos.length} git repo${repos.length === 1 ? "" : "s"}
|
|
5666
6500
|
`));
|
|
5667
6501
|
const originalDir = process.cwd();
|
|
5668
6502
|
for (const repo of repos) {
|
|
5669
|
-
const repoName =
|
|
6503
|
+
const repoName = path21.basename(repo);
|
|
5670
6504
|
try {
|
|
5671
6505
|
process.chdir(repo);
|
|
5672
6506
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
5673
6507
|
} catch (err) {
|
|
5674
6508
|
if (err instanceof Error && err.message === "__exit__") continue;
|
|
5675
|
-
log(quiet,
|
|
6509
|
+
log(quiet, chalk11.yellow(`${repoName}: refresh failed \u2014 ${err instanceof Error ? err.message : "unknown error"}`));
|
|
5676
6510
|
}
|
|
5677
6511
|
}
|
|
5678
6512
|
process.chdir(originalDir);
|
|
@@ -5680,13 +6514,13 @@ async function refreshCommand(options) {
|
|
|
5680
6514
|
if (err instanceof Error && err.message === "__exit__") throw err;
|
|
5681
6515
|
if (quiet) return;
|
|
5682
6516
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
5683
|
-
console.log(
|
|
6517
|
+
console.log(chalk11.red(`Refresh failed: ${msg}`));
|
|
5684
6518
|
throw new Error("__exit__");
|
|
5685
6519
|
}
|
|
5686
6520
|
}
|
|
5687
6521
|
|
|
5688
6522
|
// src/commands/hooks.ts
|
|
5689
|
-
import
|
|
6523
|
+
import chalk12 from "chalk";
|
|
5690
6524
|
var HOOKS = [
|
|
5691
6525
|
{
|
|
5692
6526
|
id: "session-end",
|
|
@@ -5706,13 +6540,13 @@ var HOOKS = [
|
|
|
5706
6540
|
}
|
|
5707
6541
|
];
|
|
5708
6542
|
function printStatus() {
|
|
5709
|
-
console.log(
|
|
6543
|
+
console.log(chalk12.bold("\n Hooks\n"));
|
|
5710
6544
|
for (const hook of HOOKS) {
|
|
5711
6545
|
const installed = hook.isInstalled();
|
|
5712
|
-
const icon = installed ?
|
|
5713
|
-
const state = installed ?
|
|
6546
|
+
const icon = installed ? chalk12.green("\u2713") : chalk12.dim("\u2717");
|
|
6547
|
+
const state = installed ? chalk12.green("enabled") : chalk12.dim("disabled");
|
|
5714
6548
|
console.log(` ${icon} ${hook.label.padEnd(26)} ${state}`);
|
|
5715
|
-
console.log(
|
|
6549
|
+
console.log(chalk12.dim(` ${hook.description}`));
|
|
5716
6550
|
}
|
|
5717
6551
|
console.log("");
|
|
5718
6552
|
}
|
|
@@ -5721,9 +6555,9 @@ async function hooksCommand(options) {
|
|
|
5721
6555
|
for (const hook of HOOKS) {
|
|
5722
6556
|
const result = hook.install();
|
|
5723
6557
|
if (result.alreadyInstalled) {
|
|
5724
|
-
console.log(
|
|
6558
|
+
console.log(chalk12.dim(` ${hook.label} already enabled.`));
|
|
5725
6559
|
} else {
|
|
5726
|
-
console.log(
|
|
6560
|
+
console.log(chalk12.green(" \u2713") + ` ${hook.label} enabled`);
|
|
5727
6561
|
}
|
|
5728
6562
|
}
|
|
5729
6563
|
return;
|
|
@@ -5732,9 +6566,9 @@ async function hooksCommand(options) {
|
|
|
5732
6566
|
for (const hook of HOOKS) {
|
|
5733
6567
|
const result = hook.remove();
|
|
5734
6568
|
if (result.notFound) {
|
|
5735
|
-
console.log(
|
|
6569
|
+
console.log(chalk12.dim(` ${hook.label} already disabled.`));
|
|
5736
6570
|
} else {
|
|
5737
|
-
console.log(
|
|
6571
|
+
console.log(chalk12.green(" \u2713") + ` ${hook.label} removed`);
|
|
5738
6572
|
}
|
|
5739
6573
|
}
|
|
5740
6574
|
return;
|
|
@@ -5749,18 +6583,18 @@ async function hooksCommand(options) {
|
|
|
5749
6583
|
const states = HOOKS.map((h) => h.isInstalled());
|
|
5750
6584
|
function render() {
|
|
5751
6585
|
const lines = [];
|
|
5752
|
-
lines.push(
|
|
6586
|
+
lines.push(chalk12.bold(" Hooks"));
|
|
5753
6587
|
lines.push("");
|
|
5754
6588
|
for (let i = 0; i < HOOKS.length; i++) {
|
|
5755
6589
|
const hook = HOOKS[i];
|
|
5756
6590
|
const enabled = states[i];
|
|
5757
|
-
const toggle = enabled ?
|
|
5758
|
-
const ptr = i === cursor ?
|
|
6591
|
+
const toggle = enabled ? chalk12.green("[on] ") : chalk12.dim("[off]");
|
|
6592
|
+
const ptr = i === cursor ? chalk12.cyan(">") : " ";
|
|
5759
6593
|
lines.push(` ${ptr} ${toggle} ${hook.label}`);
|
|
5760
|
-
lines.push(
|
|
6594
|
+
lines.push(chalk12.dim(` ${hook.description}`));
|
|
5761
6595
|
}
|
|
5762
6596
|
lines.push("");
|
|
5763
|
-
lines.push(
|
|
6597
|
+
lines.push(chalk12.dim(" \u2191\u2193 navigate \u23B5 toggle a all on n all off \u23CE apply q cancel"));
|
|
5764
6598
|
return lines.join("\n");
|
|
5765
6599
|
}
|
|
5766
6600
|
function draw(initial) {
|
|
@@ -5791,16 +6625,16 @@ async function hooksCommand(options) {
|
|
|
5791
6625
|
const wantEnabled = states[i];
|
|
5792
6626
|
if (wantEnabled && !wasInstalled) {
|
|
5793
6627
|
hook.install();
|
|
5794
|
-
console.log(
|
|
6628
|
+
console.log(chalk12.green(" \u2713") + ` ${hook.label} enabled`);
|
|
5795
6629
|
changed++;
|
|
5796
6630
|
} else if (!wantEnabled && wasInstalled) {
|
|
5797
6631
|
hook.remove();
|
|
5798
|
-
console.log(
|
|
6632
|
+
console.log(chalk12.green(" \u2713") + ` ${hook.label} disabled`);
|
|
5799
6633
|
changed++;
|
|
5800
6634
|
}
|
|
5801
6635
|
}
|
|
5802
6636
|
if (changed === 0) {
|
|
5803
|
-
console.log(
|
|
6637
|
+
console.log(chalk12.dim(" No changes."));
|
|
5804
6638
|
}
|
|
5805
6639
|
console.log("");
|
|
5806
6640
|
}
|
|
@@ -5836,7 +6670,7 @@ async function hooksCommand(options) {
|
|
|
5836
6670
|
case "\x1B":
|
|
5837
6671
|
case "":
|
|
5838
6672
|
cleanup();
|
|
5839
|
-
console.log(
|
|
6673
|
+
console.log(chalk12.dim("\n Cancelled.\n"));
|
|
5840
6674
|
resolve2();
|
|
5841
6675
|
break;
|
|
5842
6676
|
}
|
|
@@ -5846,43 +6680,43 @@ async function hooksCommand(options) {
|
|
|
5846
6680
|
}
|
|
5847
6681
|
|
|
5848
6682
|
// src/commands/config.ts
|
|
5849
|
-
import
|
|
6683
|
+
import chalk13 from "chalk";
|
|
5850
6684
|
async function configCommand() {
|
|
5851
6685
|
const existing = loadConfig();
|
|
5852
6686
|
if (existing) {
|
|
5853
|
-
console.log(
|
|
5854
|
-
console.log(` Provider: ${
|
|
5855
|
-
console.log(` Model: ${
|
|
6687
|
+
console.log(chalk13.bold("\nCurrent Configuration\n"));
|
|
6688
|
+
console.log(` Provider: ${chalk13.cyan(existing.provider)}`);
|
|
6689
|
+
console.log(` Model: ${chalk13.cyan(existing.model)}`);
|
|
5856
6690
|
if (existing.apiKey) {
|
|
5857
6691
|
const masked = existing.apiKey.slice(0, 8) + "..." + existing.apiKey.slice(-4);
|
|
5858
|
-
console.log(` API Key: ${
|
|
6692
|
+
console.log(` API Key: ${chalk13.dim(masked)}`);
|
|
5859
6693
|
}
|
|
5860
6694
|
if (existing.provider === "cursor") {
|
|
5861
|
-
console.log(` Seat: ${
|
|
6695
|
+
console.log(` Seat: ${chalk13.dim("Cursor (agent acp)")}`);
|
|
5862
6696
|
}
|
|
5863
6697
|
if (existing.provider === "claude-cli") {
|
|
5864
|
-
console.log(` Seat: ${
|
|
6698
|
+
console.log(` Seat: ${chalk13.dim("Claude Code (claude -p)")}`);
|
|
5865
6699
|
}
|
|
5866
6700
|
if (existing.baseUrl) {
|
|
5867
|
-
console.log(` Base URL: ${
|
|
6701
|
+
console.log(` Base URL: ${chalk13.dim(existing.baseUrl)}`);
|
|
5868
6702
|
}
|
|
5869
6703
|
if (existing.vertexProjectId) {
|
|
5870
|
-
console.log(` Vertex Project: ${
|
|
5871
|
-
console.log(` Vertex Region: ${
|
|
6704
|
+
console.log(` Vertex Project: ${chalk13.dim(existing.vertexProjectId)}`);
|
|
6705
|
+
console.log(` Vertex Region: ${chalk13.dim(existing.vertexRegion || "us-east5")}`);
|
|
5872
6706
|
}
|
|
5873
|
-
console.log(` Source: ${
|
|
6707
|
+
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())}`);
|
|
5874
6708
|
console.log("");
|
|
5875
6709
|
}
|
|
5876
6710
|
await runInteractiveProviderSetup();
|
|
5877
|
-
console.log(
|
|
5878
|
-
console.log(
|
|
6711
|
+
console.log(chalk13.green("\n\u2713 Configuration saved"));
|
|
6712
|
+
console.log(chalk13.dim(` ${getConfigFilePath()}
|
|
5879
6713
|
`));
|
|
5880
|
-
console.log(
|
|
5881
|
-
console.log(
|
|
6714
|
+
console.log(chalk13.dim(" You can also set environment variables instead:"));
|
|
6715
|
+
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"));
|
|
5882
6716
|
}
|
|
5883
6717
|
|
|
5884
6718
|
// src/commands/learn.ts
|
|
5885
|
-
import
|
|
6719
|
+
import chalk14 from "chalk";
|
|
5886
6720
|
|
|
5887
6721
|
// src/learner/stdin.ts
|
|
5888
6722
|
var STDIN_TIMEOUT_MS = 5e3;
|
|
@@ -5913,8 +6747,8 @@ function readStdin() {
|
|
|
5913
6747
|
|
|
5914
6748
|
// src/learner/storage.ts
|
|
5915
6749
|
init_constants();
|
|
5916
|
-
import
|
|
5917
|
-
import
|
|
6750
|
+
import fs25 from "fs";
|
|
6751
|
+
import path22 from "path";
|
|
5918
6752
|
var MAX_RESPONSE_LENGTH = 2e3;
|
|
5919
6753
|
var DEFAULT_STATE = {
|
|
5920
6754
|
sessionId: null,
|
|
@@ -5922,15 +6756,15 @@ var DEFAULT_STATE = {
|
|
|
5922
6756
|
lastAnalysisTimestamp: null
|
|
5923
6757
|
};
|
|
5924
6758
|
function ensureLearningDir() {
|
|
5925
|
-
if (!
|
|
5926
|
-
|
|
6759
|
+
if (!fs25.existsSync(LEARNING_DIR)) {
|
|
6760
|
+
fs25.mkdirSync(LEARNING_DIR, { recursive: true });
|
|
5927
6761
|
}
|
|
5928
6762
|
}
|
|
5929
6763
|
function sessionFilePath() {
|
|
5930
|
-
return
|
|
6764
|
+
return path22.join(LEARNING_DIR, LEARNING_SESSION_FILE);
|
|
5931
6765
|
}
|
|
5932
6766
|
function stateFilePath() {
|
|
5933
|
-
return
|
|
6767
|
+
return path22.join(LEARNING_DIR, LEARNING_STATE_FILE);
|
|
5934
6768
|
}
|
|
5935
6769
|
function truncateResponse(response) {
|
|
5936
6770
|
const str = JSON.stringify(response);
|
|
@@ -5941,50 +6775,50 @@ function appendEvent(event) {
|
|
|
5941
6775
|
ensureLearningDir();
|
|
5942
6776
|
const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
|
|
5943
6777
|
const filePath = sessionFilePath();
|
|
5944
|
-
|
|
6778
|
+
fs25.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
5945
6779
|
const count = getEventCount();
|
|
5946
6780
|
if (count > LEARNING_MAX_EVENTS) {
|
|
5947
|
-
const lines =
|
|
6781
|
+
const lines = fs25.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
5948
6782
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
5949
|
-
|
|
6783
|
+
fs25.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
5950
6784
|
}
|
|
5951
6785
|
}
|
|
5952
6786
|
function readAllEvents() {
|
|
5953
6787
|
const filePath = sessionFilePath();
|
|
5954
|
-
if (!
|
|
5955
|
-
const lines =
|
|
6788
|
+
if (!fs25.existsSync(filePath)) return [];
|
|
6789
|
+
const lines = fs25.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
5956
6790
|
return lines.map((line) => JSON.parse(line));
|
|
5957
6791
|
}
|
|
5958
6792
|
function getEventCount() {
|
|
5959
6793
|
const filePath = sessionFilePath();
|
|
5960
|
-
if (!
|
|
5961
|
-
const content =
|
|
6794
|
+
if (!fs25.existsSync(filePath)) return 0;
|
|
6795
|
+
const content = fs25.readFileSync(filePath, "utf-8");
|
|
5962
6796
|
return content.split("\n").filter(Boolean).length;
|
|
5963
6797
|
}
|
|
5964
6798
|
function clearSession() {
|
|
5965
6799
|
const filePath = sessionFilePath();
|
|
5966
|
-
if (
|
|
6800
|
+
if (fs25.existsSync(filePath)) fs25.unlinkSync(filePath);
|
|
5967
6801
|
}
|
|
5968
6802
|
function readState2() {
|
|
5969
6803
|
const filePath = stateFilePath();
|
|
5970
|
-
if (!
|
|
6804
|
+
if (!fs25.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
5971
6805
|
try {
|
|
5972
|
-
return JSON.parse(
|
|
6806
|
+
return JSON.parse(fs25.readFileSync(filePath, "utf-8"));
|
|
5973
6807
|
} catch {
|
|
5974
6808
|
return { ...DEFAULT_STATE };
|
|
5975
6809
|
}
|
|
5976
6810
|
}
|
|
5977
6811
|
function writeState2(state) {
|
|
5978
6812
|
ensureLearningDir();
|
|
5979
|
-
|
|
6813
|
+
fs25.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
|
|
5980
6814
|
}
|
|
5981
6815
|
function resetState() {
|
|
5982
6816
|
writeState2({ ...DEFAULT_STATE });
|
|
5983
6817
|
}
|
|
5984
6818
|
|
|
5985
6819
|
// src/learner/writer.ts
|
|
5986
|
-
import
|
|
5987
|
-
import
|
|
6820
|
+
import fs26 from "fs";
|
|
6821
|
+
import path23 from "path";
|
|
5988
6822
|
var LEARNED_START = "<!-- caliber:learned -->";
|
|
5989
6823
|
var LEARNED_END = "<!-- /caliber:learned -->";
|
|
5990
6824
|
function writeLearnedContent(update) {
|
|
@@ -6004,8 +6838,8 @@ function writeLearnedContent(update) {
|
|
|
6004
6838
|
function writeLearnedSection(content) {
|
|
6005
6839
|
const claudeMdPath = "CLAUDE.md";
|
|
6006
6840
|
let existing = "";
|
|
6007
|
-
if (
|
|
6008
|
-
existing =
|
|
6841
|
+
if (fs26.existsSync(claudeMdPath)) {
|
|
6842
|
+
existing = fs26.readFileSync(claudeMdPath, "utf-8");
|
|
6009
6843
|
}
|
|
6010
6844
|
const section = `${LEARNED_START}
|
|
6011
6845
|
${content}
|
|
@@ -6019,15 +6853,15 @@ ${LEARNED_END}`;
|
|
|
6019
6853
|
const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
6020
6854
|
updated = existing + separator + "\n" + section + "\n";
|
|
6021
6855
|
}
|
|
6022
|
-
|
|
6856
|
+
fs26.writeFileSync(claudeMdPath, updated);
|
|
6023
6857
|
}
|
|
6024
6858
|
function writeLearnedSkill(skill) {
|
|
6025
|
-
const skillDir =
|
|
6026
|
-
if (!
|
|
6027
|
-
const skillPath =
|
|
6028
|
-
if (!skill.isNew &&
|
|
6029
|
-
const existing =
|
|
6030
|
-
|
|
6859
|
+
const skillDir = path23.join(".claude", "skills", skill.name);
|
|
6860
|
+
if (!fs26.existsSync(skillDir)) fs26.mkdirSync(skillDir, { recursive: true });
|
|
6861
|
+
const skillPath = path23.join(skillDir, "SKILL.md");
|
|
6862
|
+
if (!skill.isNew && fs26.existsSync(skillPath)) {
|
|
6863
|
+
const existing = fs26.readFileSync(skillPath, "utf-8");
|
|
6864
|
+
fs26.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
6031
6865
|
} else {
|
|
6032
6866
|
const frontmatter = [
|
|
6033
6867
|
"---",
|
|
@@ -6036,14 +6870,14 @@ function writeLearnedSkill(skill) {
|
|
|
6036
6870
|
"---",
|
|
6037
6871
|
""
|
|
6038
6872
|
].join("\n");
|
|
6039
|
-
|
|
6873
|
+
fs26.writeFileSync(skillPath, frontmatter + skill.content);
|
|
6040
6874
|
}
|
|
6041
6875
|
return skillPath;
|
|
6042
6876
|
}
|
|
6043
6877
|
function readLearnedSection() {
|
|
6044
6878
|
const claudeMdPath = "CLAUDE.md";
|
|
6045
|
-
if (!
|
|
6046
|
-
const content =
|
|
6879
|
+
if (!fs26.existsSync(claudeMdPath)) return null;
|
|
6880
|
+
const content = fs26.readFileSync(claudeMdPath, "utf-8");
|
|
6047
6881
|
const startIdx = content.indexOf(LEARNED_START);
|
|
6048
6882
|
const endIdx = content.indexOf(LEARNED_END);
|
|
6049
6883
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
@@ -6183,53 +7017,53 @@ async function learnFinalizeCommand() {
|
|
|
6183
7017
|
async function learnInstallCommand() {
|
|
6184
7018
|
const result = installLearningHooks();
|
|
6185
7019
|
if (result.alreadyInstalled) {
|
|
6186
|
-
console.log(
|
|
7020
|
+
console.log(chalk14.dim("Learning hooks already installed."));
|
|
6187
7021
|
return;
|
|
6188
7022
|
}
|
|
6189
|
-
console.log(
|
|
6190
|
-
console.log(
|
|
6191
|
-
console.log(
|
|
7023
|
+
console.log(chalk14.green("\u2713") + " Learning hooks installed in .claude/settings.json");
|
|
7024
|
+
console.log(chalk14.dim(" PostToolUse, PostToolUseFailure, and SessionEnd hooks active."));
|
|
7025
|
+
console.log(chalk14.dim(" Session learnings will be written to CLAUDE.md and skills."));
|
|
6192
7026
|
}
|
|
6193
7027
|
async function learnRemoveCommand() {
|
|
6194
7028
|
const result = removeLearningHooks();
|
|
6195
7029
|
if (result.notFound) {
|
|
6196
|
-
console.log(
|
|
7030
|
+
console.log(chalk14.dim("Learning hooks not found."));
|
|
6197
7031
|
return;
|
|
6198
7032
|
}
|
|
6199
|
-
console.log(
|
|
7033
|
+
console.log(chalk14.green("\u2713") + " Learning hooks removed from .claude/settings.json");
|
|
6200
7034
|
}
|
|
6201
7035
|
async function learnStatusCommand() {
|
|
6202
7036
|
const installed = areLearningHooksInstalled();
|
|
6203
7037
|
const state = readState2();
|
|
6204
7038
|
const eventCount = getEventCount();
|
|
6205
|
-
console.log(
|
|
7039
|
+
console.log(chalk14.bold("Session Learning Status"));
|
|
6206
7040
|
console.log();
|
|
6207
7041
|
if (installed) {
|
|
6208
|
-
console.log(
|
|
7042
|
+
console.log(chalk14.green("\u2713") + " Learning hooks are " + chalk14.green("installed"));
|
|
6209
7043
|
} else {
|
|
6210
|
-
console.log(
|
|
6211
|
-
console.log(
|
|
7044
|
+
console.log(chalk14.dim("\u2717") + " Learning hooks are " + chalk14.yellow("not installed"));
|
|
7045
|
+
console.log(chalk14.dim(" Run `caliber learn install` to enable session learning."));
|
|
6212
7046
|
}
|
|
6213
7047
|
console.log();
|
|
6214
|
-
console.log(`Events recorded: ${
|
|
6215
|
-
console.log(`Total this session: ${
|
|
7048
|
+
console.log(`Events recorded: ${chalk14.cyan(String(eventCount))}`);
|
|
7049
|
+
console.log(`Total this session: ${chalk14.cyan(String(state.eventCount))}`);
|
|
6216
7050
|
if (state.lastAnalysisTimestamp) {
|
|
6217
|
-
console.log(`Last analysis: ${
|
|
7051
|
+
console.log(`Last analysis: ${chalk14.cyan(state.lastAnalysisTimestamp)}`);
|
|
6218
7052
|
} else {
|
|
6219
|
-
console.log(`Last analysis: ${
|
|
7053
|
+
console.log(`Last analysis: ${chalk14.dim("none")}`);
|
|
6220
7054
|
}
|
|
6221
7055
|
const learnedSection = readLearnedSection();
|
|
6222
7056
|
if (learnedSection) {
|
|
6223
7057
|
const lineCount = learnedSection.split("\n").filter(Boolean).length;
|
|
6224
7058
|
console.log(`
|
|
6225
|
-
Learned items in CLAUDE.md: ${
|
|
7059
|
+
Learned items in CLAUDE.md: ${chalk14.cyan(String(lineCount))}`);
|
|
6226
7060
|
}
|
|
6227
7061
|
}
|
|
6228
7062
|
|
|
6229
7063
|
// src/cli.ts
|
|
6230
|
-
var __dirname =
|
|
7064
|
+
var __dirname = path24.dirname(fileURLToPath(import.meta.url));
|
|
6231
7065
|
var pkg = JSON.parse(
|
|
6232
|
-
|
|
7066
|
+
fs27.readFileSync(path24.resolve(__dirname, "..", "package.json"), "utf-8")
|
|
6233
7067
|
);
|
|
6234
7068
|
var program = new Command();
|
|
6235
7069
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
|
|
@@ -6251,22 +7085,22 @@ learn.command("remove").description("Remove learning hooks from .claude/settings
|
|
|
6251
7085
|
learn.command("status").description("Show learning system status").action(learnStatusCommand);
|
|
6252
7086
|
|
|
6253
7087
|
// src/utils/version-check.ts
|
|
6254
|
-
import
|
|
6255
|
-
import
|
|
7088
|
+
import fs28 from "fs";
|
|
7089
|
+
import path25 from "path";
|
|
6256
7090
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6257
7091
|
import { execSync as execSync9 } from "child_process";
|
|
6258
|
-
import
|
|
6259
|
-
import
|
|
7092
|
+
import chalk15 from "chalk";
|
|
7093
|
+
import ora7 from "ora";
|
|
6260
7094
|
import confirm2 from "@inquirer/confirm";
|
|
6261
|
-
var __dirname_vc =
|
|
7095
|
+
var __dirname_vc = path25.dirname(fileURLToPath2(import.meta.url));
|
|
6262
7096
|
var pkg2 = JSON.parse(
|
|
6263
|
-
|
|
7097
|
+
fs28.readFileSync(path25.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
6264
7098
|
);
|
|
6265
7099
|
function getInstalledVersion() {
|
|
6266
7100
|
try {
|
|
6267
7101
|
const globalRoot = execSync9("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
6268
|
-
const pkgPath =
|
|
6269
|
-
return JSON.parse(
|
|
7102
|
+
const pkgPath = path25.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
7103
|
+
return JSON.parse(fs28.readFileSync(pkgPath, "utf-8")).version;
|
|
6270
7104
|
} catch {
|
|
6271
7105
|
return null;
|
|
6272
7106
|
}
|
|
@@ -6289,17 +7123,17 @@ async function checkForUpdates() {
|
|
|
6289
7123
|
const isInteractive = process.stdin.isTTY === true;
|
|
6290
7124
|
if (!isInteractive) {
|
|
6291
7125
|
console.log(
|
|
6292
|
-
|
|
7126
|
+
chalk15.yellow(
|
|
6293
7127
|
`
|
|
6294
7128
|
Update available: ${current} -> ${latest}
|
|
6295
|
-
Run ${
|
|
7129
|
+
Run ${chalk15.bold("npm install -g @rely-ai/caliber")} to upgrade.
|
|
6296
7130
|
`
|
|
6297
7131
|
)
|
|
6298
7132
|
);
|
|
6299
7133
|
return;
|
|
6300
7134
|
}
|
|
6301
7135
|
console.log(
|
|
6302
|
-
|
|
7136
|
+
chalk15.yellow(`
|
|
6303
7137
|
Update available: ${current} -> ${latest}`)
|
|
6304
7138
|
);
|
|
6305
7139
|
const shouldUpdate = await confirm2({ message: "Would you like to update now? (Y/n)", default: true });
|
|
@@ -6307,7 +7141,7 @@ Update available: ${current} -> ${latest}`)
|
|
|
6307
7141
|
console.log();
|
|
6308
7142
|
return;
|
|
6309
7143
|
}
|
|
6310
|
-
const spinner =
|
|
7144
|
+
const spinner = ora7("Updating caliber...").start();
|
|
6311
7145
|
try {
|
|
6312
7146
|
execSync9(`npm install -g @rely-ai/caliber@${latest}`, {
|
|
6313
7147
|
stdio: "pipe",
|
|
@@ -6317,13 +7151,13 @@ Update available: ${current} -> ${latest}`)
|
|
|
6317
7151
|
const installed = getInstalledVersion();
|
|
6318
7152
|
if (installed !== latest) {
|
|
6319
7153
|
spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
|
|
6320
|
-
console.log(
|
|
7154
|
+
console.log(chalk15.yellow(`Run ${chalk15.bold(`npm install -g @rely-ai/caliber@${latest}`)} manually.
|
|
6321
7155
|
`));
|
|
6322
7156
|
return;
|
|
6323
7157
|
}
|
|
6324
|
-
spinner.succeed(
|
|
7158
|
+
spinner.succeed(chalk15.green(`Updated to ${latest}`));
|
|
6325
7159
|
const args = process.argv.slice(2);
|
|
6326
|
-
console.log(
|
|
7160
|
+
console.log(chalk15.dim(`
|
|
6327
7161
|
Restarting: caliber ${args.join(" ")}
|
|
6328
7162
|
`));
|
|
6329
7163
|
execSync9(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
@@ -6336,11 +7170,11 @@ Restarting: caliber ${args.join(" ")}
|
|
|
6336
7170
|
if (err instanceof Error) {
|
|
6337
7171
|
const stderr = err.stderr;
|
|
6338
7172
|
const errMsg = stderr ? String(stderr).trim().split("\n").pop() : err.message.split("\n")[0];
|
|
6339
|
-
if (errMsg && !errMsg.includes("SIGTERM")) console.log(
|
|
7173
|
+
if (errMsg && !errMsg.includes("SIGTERM")) console.log(chalk15.dim(` ${errMsg}`));
|
|
6340
7174
|
}
|
|
6341
7175
|
console.log(
|
|
6342
|
-
|
|
6343
|
-
`Run ${
|
|
7176
|
+
chalk15.yellow(
|
|
7177
|
+
`Run ${chalk15.bold(`npm install -g @rely-ai/caliber@${latest}`)} manually to upgrade.
|
|
6344
7178
|
`
|
|
6345
7179
|
)
|
|
6346
7180
|
);
|