@rely-ai/caliber 1.1.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 +1121 -361
- 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";
|
|
@@ -2724,15 +2724,15 @@ function computeGrade(score) {
|
|
|
2724
2724
|
// src/scoring/checks/coverage.ts
|
|
2725
2725
|
import { readFileSync, readdirSync } from "fs";
|
|
2726
2726
|
import { join } from "path";
|
|
2727
|
-
function readFileOrNull(
|
|
2727
|
+
function readFileOrNull(path26) {
|
|
2728
2728
|
try {
|
|
2729
|
-
return readFileSync(
|
|
2729
|
+
return readFileSync(path26, "utf-8");
|
|
2730
2730
|
} catch {
|
|
2731
2731
|
return null;
|
|
2732
2732
|
}
|
|
2733
2733
|
}
|
|
2734
|
-
function readJsonOrNull(
|
|
2735
|
-
const content = readFileOrNull(
|
|
2734
|
+
function readJsonOrNull(path26) {
|
|
2735
|
+
const content = readFileOrNull(path26);
|
|
2736
2736
|
if (!content) return null;
|
|
2737
2737
|
try {
|
|
2738
2738
|
return JSON.parse(content);
|
|
@@ -3088,9 +3088,9 @@ function checkExistence(dir) {
|
|
|
3088
3088
|
// src/scoring/checks/quality.ts
|
|
3089
3089
|
import { readFileSync as readFileSync3 } from "fs";
|
|
3090
3090
|
import { join as join3 } from "path";
|
|
3091
|
-
function readFileOrNull2(
|
|
3091
|
+
function readFileOrNull2(path26) {
|
|
3092
3092
|
try {
|
|
3093
|
-
return readFileSync3(
|
|
3093
|
+
return readFileSync3(path26, "utf-8");
|
|
3094
3094
|
} catch {
|
|
3095
3095
|
return null;
|
|
3096
3096
|
}
|
|
@@ -3241,15 +3241,15 @@ function checkQuality(dir) {
|
|
|
3241
3241
|
// src/scoring/checks/accuracy.ts
|
|
3242
3242
|
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync } from "fs";
|
|
3243
3243
|
import { join as join4 } from "path";
|
|
3244
|
-
function readFileOrNull3(
|
|
3244
|
+
function readFileOrNull3(path26) {
|
|
3245
3245
|
try {
|
|
3246
|
-
return readFileSync4(
|
|
3246
|
+
return readFileSync4(path26, "utf-8");
|
|
3247
3247
|
} catch {
|
|
3248
3248
|
return null;
|
|
3249
3249
|
}
|
|
3250
3250
|
}
|
|
3251
|
-
function readJsonOrNull2(
|
|
3252
|
-
const content = readFileOrNull3(
|
|
3251
|
+
function readJsonOrNull2(path26) {
|
|
3252
|
+
const content = readFileOrNull3(path26);
|
|
3253
3253
|
if (!content) return null;
|
|
3254
3254
|
try {
|
|
3255
3255
|
return JSON.parse(content);
|
|
@@ -3432,9 +3432,9 @@ function checkAccuracy(dir) {
|
|
|
3432
3432
|
// src/scoring/checks/freshness.ts
|
|
3433
3433
|
import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
3434
3434
|
import { join as join5 } from "path";
|
|
3435
|
-
function readFileOrNull4(
|
|
3435
|
+
function readFileOrNull4(path26) {
|
|
3436
3436
|
try {
|
|
3437
|
-
return readFileSync5(
|
|
3437
|
+
return readFileSync5(path26, "utf-8");
|
|
3438
3438
|
} catch {
|
|
3439
3439
|
return null;
|
|
3440
3440
|
}
|
|
@@ -3544,9 +3544,9 @@ function checkFreshness(dir) {
|
|
|
3544
3544
|
import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "fs";
|
|
3545
3545
|
import { execSync as execSync7 } from "child_process";
|
|
3546
3546
|
import { join as join6 } from "path";
|
|
3547
|
-
function readFileOrNull5(
|
|
3547
|
+
function readFileOrNull5(path26) {
|
|
3548
3548
|
try {
|
|
3549
|
-
return readFileSync6(
|
|
3549
|
+
return readFileSync6(path26, "utf-8");
|
|
3550
3550
|
} catch {
|
|
3551
3551
|
return null;
|
|
3552
3552
|
}
|
|
@@ -3853,10 +3853,758 @@ function displayScoreDelta(before, after) {
|
|
|
3853
3853
|
}
|
|
3854
3854
|
}
|
|
3855
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
|
+
|
|
3856
4604
|
// src/commands/onboard.ts
|
|
3857
4605
|
async function initCommand(options) {
|
|
3858
|
-
const brand =
|
|
3859
|
-
const title =
|
|
4606
|
+
const brand = chalk5.hex("#EB9D83");
|
|
4607
|
+
const title = chalk5.hex("#83D1EB");
|
|
3860
4608
|
console.log(brand.bold(`
|
|
3861
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
|
|
3862
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
|
|
@@ -3865,19 +4613,19 @@ async function initCommand(options) {
|
|
|
3865
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
|
|
3866
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
|
|
3867
4615
|
`));
|
|
3868
|
-
console.log(
|
|
4616
|
+
console.log(chalk5.dim(" Onboard your project for AI-assisted development\n"));
|
|
3869
4617
|
console.log(title.bold(" Welcome to Caliber\n"));
|
|
3870
|
-
console.log(
|
|
3871
|
-
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"));
|
|
3872
4620
|
console.log(title.bold(" How onboarding works:\n"));
|
|
3873
|
-
console.log(
|
|
3874
|
-
console.log(
|
|
3875
|
-
console.log(
|
|
3876
|
-
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"));
|
|
3877
4625
|
console.log(title.bold(" Step 1/4 \u2014 Connect your LLM\n"));
|
|
3878
4626
|
let config = loadConfig();
|
|
3879
4627
|
if (!config) {
|
|
3880
|
-
console.log(
|
|
4628
|
+
console.log(chalk5.dim(" No LLM provider set yet. Choose how to run Caliber:\n"));
|
|
3881
4629
|
try {
|
|
3882
4630
|
await runInteractiveProviderSetup({
|
|
3883
4631
|
selectMessage: "How do you want to use Caliber? (choose LLM provider)"
|
|
@@ -3888,23 +4636,23 @@ async function initCommand(options) {
|
|
|
3888
4636
|
}
|
|
3889
4637
|
config = loadConfig();
|
|
3890
4638
|
if (!config) {
|
|
3891
|
-
console.log(
|
|
4639
|
+
console.log(chalk5.red(" Setup was cancelled or failed.\n"));
|
|
3892
4640
|
throw new Error("__exit__");
|
|
3893
4641
|
}
|
|
3894
|
-
console.log(
|
|
4642
|
+
console.log(chalk5.green(" \u2713 Provider saved. Let's continue.\n"));
|
|
3895
4643
|
}
|
|
3896
4644
|
const displayModel = config.model === "default" && config.provider === "claude-cli" ? process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)" : config.model;
|
|
3897
4645
|
const fastModel = process.env.ANTHROPIC_SMALL_FAST_MODEL;
|
|
3898
4646
|
const modelLine = fastModel ? ` Provider: ${config.provider} | Model: ${displayModel} | Scan: ${fastModel}` : ` Provider: ${config.provider} | Model: ${displayModel}`;
|
|
3899
|
-
console.log(
|
|
4647
|
+
console.log(chalk5.dim(modelLine + "\n"));
|
|
3900
4648
|
console.log(title.bold(" Step 2/4 \u2014 Discover your project\n"));
|
|
3901
|
-
console.log(
|
|
3902
|
-
const spinner =
|
|
4649
|
+
console.log(chalk5.dim(" Learning about your languages, dependencies, structure, and existing configs.\n"));
|
|
4650
|
+
const spinner = ora2("Analyzing project...").start();
|
|
3903
4651
|
const fingerprint = collectFingerprint(process.cwd());
|
|
3904
4652
|
await enrichFingerprintWithLLM(fingerprint, process.cwd());
|
|
3905
4653
|
spinner.succeed("Project analyzed");
|
|
3906
|
-
console.log(
|
|
3907
|
-
console.log(
|
|
4654
|
+
console.log(chalk5.dim(` Languages: ${fingerprint.languages.join(", ") || "none detected"}`));
|
|
4655
|
+
console.log(chalk5.dim(` Files: ${fingerprint.fileTree.length} found
|
|
3908
4656
|
`));
|
|
3909
4657
|
const targetAgent = options.agent || await promptAgent();
|
|
3910
4658
|
const preScore = computeLocalScore(process.cwd(), targetAgent);
|
|
@@ -3922,13 +4670,13 @@ async function initCommand(options) {
|
|
|
3922
4670
|
displayScoreSummary(baselineScore);
|
|
3923
4671
|
const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length);
|
|
3924
4672
|
if (hasExistingConfig && baselineScore.score === 100) {
|
|
3925
|
-
console.log(
|
|
3926
|
-
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"));
|
|
3927
4675
|
if (!options.force) return;
|
|
3928
4676
|
}
|
|
3929
4677
|
const isEmpty = fingerprint.fileTree.length < 3;
|
|
3930
4678
|
if (isEmpty) {
|
|
3931
|
-
fingerprint.description = await
|
|
4679
|
+
fingerprint.description = await promptInput3("What will you build in this project?");
|
|
3932
4680
|
}
|
|
3933
4681
|
let failingChecks;
|
|
3934
4682
|
let passingChecks;
|
|
@@ -3939,24 +4687,24 @@ async function initCommand(options) {
|
|
|
3939
4687
|
currentScore = baselineScore.score;
|
|
3940
4688
|
if (failingChecks.length > 0) {
|
|
3941
4689
|
console.log(title.bold(" Step 3/4 \u2014 Fine-tuning\n"));
|
|
3942
|
-
console.log(
|
|
4690
|
+
console.log(chalk5.dim(` Your setup scores ${baselineScore.score}/100 \u2014 fixing ${failingChecks.length} remaining issue${failingChecks.length === 1 ? "" : "s"}:
|
|
3943
4691
|
`));
|
|
3944
4692
|
for (const check of failingChecks) {
|
|
3945
|
-
console.log(
|
|
4693
|
+
console.log(chalk5.dim(` \u2022 ${check.name}`));
|
|
3946
4694
|
}
|
|
3947
4695
|
console.log("");
|
|
3948
4696
|
}
|
|
3949
4697
|
} else if (hasExistingConfig) {
|
|
3950
4698
|
console.log(title.bold(" Step 3/4 \u2014 Improve your setup\n"));
|
|
3951
|
-
console.log(
|
|
3952
|
-
console.log(
|
|
4699
|
+
console.log(chalk5.dim(" Reviewing your existing configs against your codebase"));
|
|
4700
|
+
console.log(chalk5.dim(" and preparing improvements.\n"));
|
|
3953
4701
|
} else {
|
|
3954
4702
|
console.log(title.bold(" Step 3/4 \u2014 Build your agent setup\n"));
|
|
3955
|
-
console.log(
|
|
4703
|
+
console.log(chalk5.dim(" Creating config files tailored to your project.\n"));
|
|
3956
4704
|
}
|
|
3957
|
-
console.log(
|
|
4705
|
+
console.log(chalk5.dim(" This can take a couple of minutes depending on your model and provider.\n"));
|
|
3958
4706
|
const genStartTime = Date.now();
|
|
3959
|
-
const genSpinner =
|
|
4707
|
+
const genSpinner = ora2("Generating setup...").start();
|
|
3960
4708
|
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
|
|
3961
4709
|
genMessages.start();
|
|
3962
4710
|
let generatedSetup = null;
|
|
@@ -3996,8 +4744,8 @@ async function initCommand(options) {
|
|
|
3996
4744
|
if (!generatedSetup) {
|
|
3997
4745
|
genSpinner.fail("Failed to generate setup.");
|
|
3998
4746
|
if (rawOutput) {
|
|
3999
|
-
console.log(
|
|
4000
|
-
console.log(
|
|
4747
|
+
console.log(chalk5.dim("\nRaw LLM output (JSON parse failed):"));
|
|
4748
|
+
console.log(chalk5.dim(rawOutput.slice(0, 500)));
|
|
4001
4749
|
}
|
|
4002
4750
|
throw new Error("__exit__");
|
|
4003
4751
|
}
|
|
@@ -4005,7 +4753,7 @@ async function initCommand(options) {
|
|
|
4005
4753
|
const mins = Math.floor(elapsedMs / 6e4);
|
|
4006
4754
|
const secs = Math.floor(elapsedMs % 6e4 / 1e3);
|
|
4007
4755
|
const timeStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
|
|
4008
|
-
genSpinner.succeed(`Setup generated ${
|
|
4756
|
+
genSpinner.succeed(`Setup generated ${chalk5.dim(`in ${timeStr}`)}`);
|
|
4009
4757
|
printSetupSummary(generatedSetup);
|
|
4010
4758
|
const sessionHistory = [];
|
|
4011
4759
|
sessionHistory.push({
|
|
@@ -4015,7 +4763,7 @@ async function initCommand(options) {
|
|
|
4015
4763
|
console.log(title.bold(" Step 4/4 \u2014 Review and apply\n"));
|
|
4016
4764
|
const setupFiles = collectSetupFiles(generatedSetup);
|
|
4017
4765
|
const staged = stageFiles(setupFiles, process.cwd());
|
|
4018
|
-
console.log(
|
|
4766
|
+
console.log(chalk5.dim(` ${chalk5.green(`${staged.newFiles} new`)} / ${chalk5.yellow(`${staged.modifiedFiles} modified`)} file${staged.newFiles + staged.modifiedFiles !== 1 ? "s" : ""}
|
|
4019
4767
|
`));
|
|
4020
4768
|
const wantsReview = await promptWantsReview();
|
|
4021
4769
|
if (wantsReview) {
|
|
@@ -4027,12 +4775,12 @@ async function initCommand(options) {
|
|
|
4027
4775
|
generatedSetup = await refineLoop(generatedSetup, targetAgent, sessionHistory);
|
|
4028
4776
|
if (!generatedSetup) {
|
|
4029
4777
|
cleanupStaging();
|
|
4030
|
-
console.log(
|
|
4778
|
+
console.log(chalk5.dim("Refinement cancelled. No files were modified."));
|
|
4031
4779
|
return;
|
|
4032
4780
|
}
|
|
4033
4781
|
const updatedFiles = collectSetupFiles(generatedSetup);
|
|
4034
4782
|
const restaged = stageFiles(updatedFiles, process.cwd());
|
|
4035
|
-
console.log(
|
|
4783
|
+
console.log(chalk5.dim(` ${chalk5.green(`${restaged.newFiles} new`)} / ${chalk5.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
|
|
4036
4784
|
`));
|
|
4037
4785
|
printSetupSummary(generatedSetup);
|
|
4038
4786
|
await openReview("terminal", restaged.stagedFiles);
|
|
@@ -4040,37 +4788,49 @@ async function initCommand(options) {
|
|
|
4040
4788
|
}
|
|
4041
4789
|
cleanupStaging();
|
|
4042
4790
|
if (action === "decline") {
|
|
4043
|
-
console.log(
|
|
4791
|
+
console.log(chalk5.dim("Setup declined. No files were modified."));
|
|
4044
4792
|
return;
|
|
4045
4793
|
}
|
|
4046
4794
|
if (options.dryRun) {
|
|
4047
|
-
console.log(
|
|
4795
|
+
console.log(chalk5.yellow("\n[Dry run] Would write the following files:"));
|
|
4048
4796
|
console.log(JSON.stringify(generatedSetup, null, 2));
|
|
4049
4797
|
return;
|
|
4050
4798
|
}
|
|
4051
|
-
const writeSpinner =
|
|
4799
|
+
const writeSpinner = ora2("Writing config files...").start();
|
|
4052
4800
|
try {
|
|
4053
4801
|
const result = writeSetup(generatedSetup);
|
|
4054
4802
|
writeSpinner.succeed("Config files written");
|
|
4055
|
-
console.log(
|
|
4803
|
+
console.log(chalk5.bold("\nFiles created/updated:"));
|
|
4056
4804
|
for (const file of result.written) {
|
|
4057
|
-
console.log(` ${
|
|
4805
|
+
console.log(` ${chalk5.green("\u2713")} ${file}`);
|
|
4058
4806
|
}
|
|
4059
4807
|
if (result.deleted.length > 0) {
|
|
4060
|
-
console.log(
|
|
4808
|
+
console.log(chalk5.bold("\nFiles removed:"));
|
|
4061
4809
|
for (const file of result.deleted) {
|
|
4062
|
-
console.log(` ${
|
|
4810
|
+
console.log(` ${chalk5.red("\u2717")} ${file}`);
|
|
4063
4811
|
}
|
|
4064
4812
|
}
|
|
4065
4813
|
if (result.backupDir) {
|
|
4066
|
-
console.log(
|
|
4814
|
+
console.log(chalk5.dim(`
|
|
4067
4815
|
Backups saved to ${result.backupDir}`));
|
|
4068
4816
|
}
|
|
4069
4817
|
} catch (err) {
|
|
4070
4818
|
writeSpinner.fail("Failed to write files");
|
|
4071
|
-
console.error(
|
|
4819
|
+
console.error(chalk5.red(err instanceof Error ? err.message : "Unknown error"));
|
|
4072
4820
|
throw new Error("__exit__");
|
|
4073
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
|
+
}
|
|
4074
4834
|
ensurePermissions();
|
|
4075
4835
|
const sha = getCurrentHeadSha();
|
|
4076
4836
|
writeState({
|
|
@@ -4080,56 +4840,56 @@ async function initCommand(options) {
|
|
|
4080
4840
|
});
|
|
4081
4841
|
console.log("");
|
|
4082
4842
|
console.log(title.bold(" Keep your configs fresh\n"));
|
|
4083
|
-
console.log(
|
|
4843
|
+
console.log(chalk5.dim(" Caliber can automatically update your agent configs when your code changes.\n"));
|
|
4084
4844
|
const hookChoice = await promptHookType(targetAgent);
|
|
4085
4845
|
if (hookChoice === "claude" || hookChoice === "both") {
|
|
4086
4846
|
const hookResult = installHook();
|
|
4087
4847
|
if (hookResult.installed) {
|
|
4088
|
-
console.log(` ${
|
|
4089
|
-
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"));
|
|
4090
4850
|
} else if (hookResult.alreadyInstalled) {
|
|
4091
|
-
console.log(
|
|
4851
|
+
console.log(chalk5.dim(" Claude Code hook already installed"));
|
|
4092
4852
|
}
|
|
4093
4853
|
const learnResult = installLearningHooks();
|
|
4094
4854
|
if (learnResult.installed) {
|
|
4095
|
-
console.log(` ${
|
|
4096
|
-
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"));
|
|
4097
4857
|
} else if (learnResult.alreadyInstalled) {
|
|
4098
|
-
console.log(
|
|
4858
|
+
console.log(chalk5.dim(" Learning hooks already installed"));
|
|
4099
4859
|
}
|
|
4100
4860
|
}
|
|
4101
4861
|
if (hookChoice === "precommit" || hookChoice === "both") {
|
|
4102
4862
|
const precommitResult = installPreCommitHook();
|
|
4103
4863
|
if (precommitResult.installed) {
|
|
4104
|
-
console.log(` ${
|
|
4105
|
-
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"));
|
|
4106
4866
|
} else if (precommitResult.alreadyInstalled) {
|
|
4107
|
-
console.log(
|
|
4867
|
+
console.log(chalk5.dim(" Pre-commit hook already installed"));
|
|
4108
4868
|
} else {
|
|
4109
|
-
console.log(
|
|
4869
|
+
console.log(chalk5.yellow(" Could not install pre-commit hook (not a git repository?)"));
|
|
4110
4870
|
}
|
|
4111
4871
|
}
|
|
4112
4872
|
if (hookChoice === "skip") {
|
|
4113
|
-
console.log(
|
|
4873
|
+
console.log(chalk5.dim(" Skipped auto-refresh hooks. Run ") + chalk5.hex("#83D1EB")("caliber hooks install") + chalk5.dim(" later to enable."));
|
|
4114
4874
|
}
|
|
4115
4875
|
const afterScore = computeLocalScore(process.cwd(), targetAgent);
|
|
4116
4876
|
if (afterScore.score < baselineScore.score) {
|
|
4117
4877
|
console.log("");
|
|
4118
|
-
console.log(
|
|
4878
|
+
console.log(chalk5.yellow(` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`));
|
|
4119
4879
|
try {
|
|
4120
4880
|
const { restored, removed } = undoSetup();
|
|
4121
4881
|
if (restored.length > 0 || removed.length > 0) {
|
|
4122
|
-
console.log(
|
|
4882
|
+
console.log(chalk5.dim(` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`));
|
|
4123
4883
|
}
|
|
4124
4884
|
} catch {
|
|
4125
4885
|
}
|
|
4126
|
-
console.log(
|
|
4886
|
+
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber onboard --force") + chalk5.dim(" to override.\n"));
|
|
4127
4887
|
return;
|
|
4128
4888
|
}
|
|
4129
4889
|
displayScoreDelta(baselineScore, afterScore);
|
|
4130
|
-
console.log(
|
|
4131
|
-
console.log(
|
|
4132
|
-
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"));
|
|
4133
4893
|
console.log(` ${title("caliber score")} See your full config breakdown`);
|
|
4134
4894
|
console.log(` ${title("caliber recommend")} Discover community skills for your stack`);
|
|
4135
4895
|
console.log(` ${title("caliber undo")} Revert all changes from this run`);
|
|
@@ -4137,7 +4897,7 @@ async function initCommand(options) {
|
|
|
4137
4897
|
}
|
|
4138
4898
|
async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
4139
4899
|
while (true) {
|
|
4140
|
-
const message = await
|
|
4900
|
+
const message = await promptInput3("\nWhat would you like to change?");
|
|
4141
4901
|
if (!message || message.toLowerCase() === "done" || message.toLowerCase() === "accept") {
|
|
4142
4902
|
return currentSetup;
|
|
4143
4903
|
}
|
|
@@ -4146,12 +4906,12 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
|
4146
4906
|
}
|
|
4147
4907
|
const isValid = await classifyRefineIntent(message);
|
|
4148
4908
|
if (!isValid) {
|
|
4149
|
-
console.log(
|
|
4150
|
-
console.log(
|
|
4151
|
-
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'));
|
|
4152
4912
|
continue;
|
|
4153
4913
|
}
|
|
4154
|
-
const refineSpinner =
|
|
4914
|
+
const refineSpinner = ora2("Refining setup...").start();
|
|
4155
4915
|
const refineMessages = new SpinnerMessages(refineSpinner, REFINE_MESSAGES);
|
|
4156
4916
|
refineMessages.start();
|
|
4157
4917
|
const refined = await refineSetup(
|
|
@@ -4169,16 +4929,16 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
|
4169
4929
|
});
|
|
4170
4930
|
refineSpinner.succeed("Setup updated");
|
|
4171
4931
|
printSetupSummary(refined);
|
|
4172
|
-
console.log(
|
|
4932
|
+
console.log(chalk5.dim('Type "done" to accept, or describe more changes.'));
|
|
4173
4933
|
} else {
|
|
4174
4934
|
refineSpinner.fail("Refinement failed \u2014 could not parse AI response.");
|
|
4175
|
-
console.log(
|
|
4935
|
+
console.log(chalk5.dim('Try rephrasing your request, or type "done" to keep the current setup.'));
|
|
4176
4936
|
}
|
|
4177
4937
|
}
|
|
4178
4938
|
}
|
|
4179
4939
|
function summarizeSetup(action, setup) {
|
|
4180
4940
|
const descriptions = setup.fileDescriptions;
|
|
4181
|
-
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(", ");
|
|
4182
4942
|
return `${action}. Files:
|
|
4183
4943
|
${files}`;
|
|
4184
4944
|
}
|
|
@@ -4229,10 +4989,10 @@ ${JSON.stringify(checkList, null, 2)}`,
|
|
|
4229
4989
|
return [];
|
|
4230
4990
|
}
|
|
4231
4991
|
}
|
|
4232
|
-
function
|
|
4233
|
-
const rl =
|
|
4992
|
+
function promptInput3(question) {
|
|
4993
|
+
const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
|
|
4234
4994
|
return new Promise((resolve2) => {
|
|
4235
|
-
rl.question(
|
|
4995
|
+
rl.question(chalk5.cyan(`${question} `), (answer) => {
|
|
4236
4996
|
rl.close();
|
|
4237
4997
|
resolve2(answer.trim());
|
|
4238
4998
|
});
|
|
@@ -4294,12 +5054,12 @@ async function openReview(method, stagedFiles) {
|
|
|
4294
5054
|
originalPath: f.originalPath,
|
|
4295
5055
|
proposedPath: f.proposedPath
|
|
4296
5056
|
})));
|
|
4297
|
-
console.log(
|
|
5057
|
+
console.log(chalk5.dim(" Diffs opened in your editor.\n"));
|
|
4298
5058
|
return;
|
|
4299
5059
|
}
|
|
4300
5060
|
const fileInfos = stagedFiles.map((file) => {
|
|
4301
|
-
const proposed =
|
|
4302
|
-
const current = file.currentPath ?
|
|
5061
|
+
const proposed = fs20.readFileSync(file.proposedPath, "utf-8");
|
|
5062
|
+
const current = file.currentPath ? fs20.readFileSync(file.currentPath, "utf-8") : "";
|
|
4303
5063
|
const patch = createTwoFilesPatch(
|
|
4304
5064
|
file.isNew ? "/dev/null" : file.relativePath,
|
|
4305
5065
|
file.relativePath,
|
|
@@ -4325,8 +5085,8 @@ async function openReview(method, stagedFiles) {
|
|
|
4325
5085
|
async function interactiveDiffExplorer(files) {
|
|
4326
5086
|
if (!process.stdin.isTTY) {
|
|
4327
5087
|
for (const f of files) {
|
|
4328
|
-
const icon = f.isNew ?
|
|
4329
|
-
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}`)}`;
|
|
4330
5090
|
console.log(` ${icon} ${f.relativePath} ${stats}`);
|
|
4331
5091
|
}
|
|
4332
5092
|
console.log("");
|
|
@@ -4342,47 +5102,47 @@ async function interactiveDiffExplorer(files) {
|
|
|
4342
5102
|
}
|
|
4343
5103
|
function renderFileList() {
|
|
4344
5104
|
const lines = [];
|
|
4345
|
-
lines.push(
|
|
5105
|
+
lines.push(chalk5.bold(" Review changes"));
|
|
4346
5106
|
lines.push("");
|
|
4347
5107
|
for (let i = 0; i < files.length; i++) {
|
|
4348
5108
|
const f = files[i];
|
|
4349
|
-
const ptr = i === cursor ?
|
|
4350
|
-
const icon = f.isNew ?
|
|
4351
|
-
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}`)}`;
|
|
4352
5112
|
lines.push(` ${ptr} ${icon} ${f.relativePath} ${stats}`);
|
|
4353
5113
|
}
|
|
4354
5114
|
lines.push("");
|
|
4355
|
-
lines.push(
|
|
5115
|
+
lines.push(chalk5.dim(" \u2191\u2193 navigate \u23CE view diff q done"));
|
|
4356
5116
|
return lines.join("\n");
|
|
4357
5117
|
}
|
|
4358
5118
|
function renderDiff(index) {
|
|
4359
5119
|
const f = files[index];
|
|
4360
5120
|
const lines = [];
|
|
4361
|
-
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}`)}`;
|
|
4362
5122
|
lines.push(header);
|
|
4363
|
-
lines.push(
|
|
5123
|
+
lines.push(chalk5.dim(" " + "\u2500".repeat(60)));
|
|
4364
5124
|
const patchLines = f.patch.split("\n");
|
|
4365
5125
|
const bodyLines = patchLines.slice(4);
|
|
4366
5126
|
const maxVisible = getTermHeight() - 4;
|
|
4367
5127
|
const visibleLines = bodyLines.slice(scrollOffset, scrollOffset + maxVisible);
|
|
4368
5128
|
for (const line of visibleLines) {
|
|
4369
5129
|
if (line.startsWith("+")) {
|
|
4370
|
-
lines.push(
|
|
5130
|
+
lines.push(chalk5.green(" " + line));
|
|
4371
5131
|
} else if (line.startsWith("-")) {
|
|
4372
|
-
lines.push(
|
|
5132
|
+
lines.push(chalk5.red(" " + line));
|
|
4373
5133
|
} else if (line.startsWith("@@")) {
|
|
4374
|
-
lines.push(
|
|
5134
|
+
lines.push(chalk5.cyan(" " + line));
|
|
4375
5135
|
} else {
|
|
4376
|
-
lines.push(
|
|
5136
|
+
lines.push(chalk5.dim(" " + line));
|
|
4377
5137
|
}
|
|
4378
5138
|
}
|
|
4379
5139
|
const totalBody = bodyLines.length;
|
|
4380
5140
|
if (totalBody > maxVisible) {
|
|
4381
5141
|
const pct = Math.round((scrollOffset + maxVisible) / totalBody * 100);
|
|
4382
|
-
lines.push(
|
|
5142
|
+
lines.push(chalk5.dim(` \u2500\u2500 ${Math.min(pct, 100)}% \u2500\u2500`));
|
|
4383
5143
|
}
|
|
4384
5144
|
lines.push("");
|
|
4385
|
-
lines.push(
|
|
5145
|
+
lines.push(chalk5.dim(" \u2191\u2193 scroll \u23B5/esc back to file list"));
|
|
4386
5146
|
return lines.join("\n");
|
|
4387
5147
|
}
|
|
4388
5148
|
function draw(initial) {
|
|
@@ -4476,46 +5236,46 @@ function printSetupSummary(setup) {
|
|
|
4476
5236
|
const fileDescriptions = setup.fileDescriptions;
|
|
4477
5237
|
const deletions = setup.deletions;
|
|
4478
5238
|
console.log("");
|
|
4479
|
-
console.log(
|
|
5239
|
+
console.log(chalk5.bold(" Proposed changes:\n"));
|
|
4480
5240
|
const getDescription = (filePath) => {
|
|
4481
5241
|
return fileDescriptions?.[filePath];
|
|
4482
5242
|
};
|
|
4483
5243
|
if (claude) {
|
|
4484
5244
|
if (claude.claudeMd) {
|
|
4485
|
-
const icon =
|
|
5245
|
+
const icon = fs20.existsSync("CLAUDE.md") ? chalk5.yellow("~") : chalk5.green("+");
|
|
4486
5246
|
const desc = getDescription("CLAUDE.md");
|
|
4487
|
-
console.log(` ${icon} ${
|
|
4488
|
-
if (desc) console.log(
|
|
5247
|
+
console.log(` ${icon} ${chalk5.bold("CLAUDE.md")}`);
|
|
5248
|
+
if (desc) console.log(chalk5.dim(` ${desc}`));
|
|
4489
5249
|
console.log("");
|
|
4490
5250
|
}
|
|
4491
5251
|
const skills = claude.skills;
|
|
4492
5252
|
if (Array.isArray(skills) && skills.length > 0) {
|
|
4493
5253
|
for (const skill of skills) {
|
|
4494
5254
|
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
4495
|
-
const icon =
|
|
5255
|
+
const icon = fs20.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
4496
5256
|
const desc = getDescription(skillPath);
|
|
4497
|
-
console.log(` ${icon} ${
|
|
4498
|
-
console.log(
|
|
5257
|
+
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
5258
|
+
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
4499
5259
|
console.log("");
|
|
4500
5260
|
}
|
|
4501
5261
|
}
|
|
4502
5262
|
}
|
|
4503
5263
|
if (cursor) {
|
|
4504
5264
|
if (cursor.cursorrules) {
|
|
4505
|
-
const icon =
|
|
5265
|
+
const icon = fs20.existsSync(".cursorrules") ? chalk5.yellow("~") : chalk5.green("+");
|
|
4506
5266
|
const desc = getDescription(".cursorrules");
|
|
4507
|
-
console.log(` ${icon} ${
|
|
4508
|
-
if (desc) console.log(
|
|
5267
|
+
console.log(` ${icon} ${chalk5.bold(".cursorrules")}`);
|
|
5268
|
+
if (desc) console.log(chalk5.dim(` ${desc}`));
|
|
4509
5269
|
console.log("");
|
|
4510
5270
|
}
|
|
4511
5271
|
const cursorSkills = cursor.skills;
|
|
4512
5272
|
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
4513
5273
|
for (const skill of cursorSkills) {
|
|
4514
5274
|
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
4515
|
-
const icon =
|
|
5275
|
+
const icon = fs20.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
4516
5276
|
const desc = getDescription(skillPath);
|
|
4517
|
-
console.log(` ${icon} ${
|
|
4518
|
-
console.log(
|
|
5277
|
+
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
5278
|
+
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
4519
5279
|
console.log("");
|
|
4520
5280
|
}
|
|
4521
5281
|
}
|
|
@@ -4523,32 +5283,32 @@ function printSetupSummary(setup) {
|
|
|
4523
5283
|
if (Array.isArray(rules) && rules.length > 0) {
|
|
4524
5284
|
for (const rule of rules) {
|
|
4525
5285
|
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
4526
|
-
const icon =
|
|
5286
|
+
const icon = fs20.existsSync(rulePath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
4527
5287
|
const desc = getDescription(rulePath);
|
|
4528
|
-
console.log(` ${icon} ${
|
|
5288
|
+
console.log(` ${icon} ${chalk5.bold(rulePath)}`);
|
|
4529
5289
|
if (desc) {
|
|
4530
|
-
console.log(
|
|
5290
|
+
console.log(chalk5.dim(` ${desc}`));
|
|
4531
5291
|
} else {
|
|
4532
5292
|
const firstLine = rule.content.split("\n").filter((l) => l.trim() && !l.trim().startsWith("#"))[0];
|
|
4533
|
-
if (firstLine) console.log(
|
|
5293
|
+
if (firstLine) console.log(chalk5.dim(` ${firstLine.trim().slice(0, 80)}`));
|
|
4534
5294
|
}
|
|
4535
5295
|
console.log("");
|
|
4536
5296
|
}
|
|
4537
5297
|
}
|
|
4538
5298
|
}
|
|
4539
|
-
if (!
|
|
4540
|
-
console.log(` ${
|
|
4541
|
-
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"));
|
|
4542
5302
|
console.log("");
|
|
4543
5303
|
}
|
|
4544
5304
|
if (Array.isArray(deletions) && deletions.length > 0) {
|
|
4545
5305
|
for (const del of deletions) {
|
|
4546
|
-
console.log(` ${
|
|
4547
|
-
console.log(
|
|
5306
|
+
console.log(` ${chalk5.red("-")} ${chalk5.bold(del.filePath)}`);
|
|
5307
|
+
console.log(chalk5.dim(` ${del.reason}`));
|
|
4548
5308
|
console.log("");
|
|
4549
5309
|
}
|
|
4550
5310
|
}
|
|
4551
|
-
console.log(` ${
|
|
5311
|
+
console.log(` ${chalk5.green("+")} ${chalk5.dim("new")} ${chalk5.yellow("~")} ${chalk5.dim("modified")} ${chalk5.red("-")} ${chalk5.dim("removed")}`);
|
|
4552
5312
|
console.log("");
|
|
4553
5313
|
}
|
|
4554
5314
|
function buildSkillContent(skill) {
|
|
@@ -4564,8 +5324,8 @@ function ensurePermissions() {
|
|
|
4564
5324
|
const settingsPath = ".claude/settings.json";
|
|
4565
5325
|
let settings = {};
|
|
4566
5326
|
try {
|
|
4567
|
-
if (
|
|
4568
|
-
settings = JSON.parse(
|
|
5327
|
+
if (fs20.existsSync(settingsPath)) {
|
|
5328
|
+
settings = JSON.parse(fs20.readFileSync(settingsPath, "utf-8"));
|
|
4569
5329
|
}
|
|
4570
5330
|
} catch {
|
|
4571
5331
|
}
|
|
@@ -4579,8 +5339,8 @@ function ensurePermissions() {
|
|
|
4579
5339
|
"Bash(git *)"
|
|
4580
5340
|
];
|
|
4581
5341
|
settings.permissions = permissions;
|
|
4582
|
-
if (!
|
|
4583
|
-
|
|
5342
|
+
if (!fs20.existsSync(".claude")) fs20.mkdirSync(".claude", { recursive: true });
|
|
5343
|
+
fs20.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4584
5344
|
}
|
|
4585
5345
|
function collectSetupFiles(setup) {
|
|
4586
5346
|
const files = [];
|
|
@@ -4610,7 +5370,7 @@ function collectSetupFiles(setup) {
|
|
|
4610
5370
|
}
|
|
4611
5371
|
}
|
|
4612
5372
|
}
|
|
4613
|
-
if (!
|
|
5373
|
+
if (!fs20.existsSync("AGENTS.md")) {
|
|
4614
5374
|
const agentRefs = [];
|
|
4615
5375
|
if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
|
|
4616
5376
|
if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
|
|
@@ -4629,10 +5389,10 @@ ${agentRefs.join(" ")}
|
|
|
4629
5389
|
}
|
|
4630
5390
|
|
|
4631
5391
|
// src/commands/undo.ts
|
|
4632
|
-
import
|
|
4633
|
-
import
|
|
5392
|
+
import chalk6 from "chalk";
|
|
5393
|
+
import ora3 from "ora";
|
|
4634
5394
|
function undoCommand() {
|
|
4635
|
-
const spinner =
|
|
5395
|
+
const spinner = ora3("Reverting setup...").start();
|
|
4636
5396
|
try {
|
|
4637
5397
|
const { restored, removed } = undoSetup();
|
|
4638
5398
|
if (restored.length === 0 && removed.length === 0) {
|
|
@@ -4641,27 +5401,27 @@ function undoCommand() {
|
|
|
4641
5401
|
}
|
|
4642
5402
|
spinner.succeed("Setup reverted successfully.\n");
|
|
4643
5403
|
if (restored.length > 0) {
|
|
4644
|
-
console.log(
|
|
5404
|
+
console.log(chalk6.cyan(" Restored from backup:"));
|
|
4645
5405
|
for (const file of restored) {
|
|
4646
|
-
console.log(` ${
|
|
5406
|
+
console.log(` ${chalk6.green("\u21A9")} ${file}`);
|
|
4647
5407
|
}
|
|
4648
5408
|
}
|
|
4649
5409
|
if (removed.length > 0) {
|
|
4650
|
-
console.log(
|
|
5410
|
+
console.log(chalk6.cyan(" Removed:"));
|
|
4651
5411
|
for (const file of removed) {
|
|
4652
|
-
console.log(` ${
|
|
5412
|
+
console.log(` ${chalk6.red("\u2717")} ${file}`);
|
|
4653
5413
|
}
|
|
4654
5414
|
}
|
|
4655
5415
|
console.log("");
|
|
4656
5416
|
} catch (err) {
|
|
4657
|
-
spinner.fail(
|
|
5417
|
+
spinner.fail(chalk6.red(err instanceof Error ? err.message : "Undo failed"));
|
|
4658
5418
|
throw new Error("__exit__");
|
|
4659
5419
|
}
|
|
4660
5420
|
}
|
|
4661
5421
|
|
|
4662
5422
|
// src/commands/status.ts
|
|
4663
|
-
import
|
|
4664
|
-
import
|
|
5423
|
+
import chalk7 from "chalk";
|
|
5424
|
+
import fs21 from "fs";
|
|
4665
5425
|
async function statusCommand(options) {
|
|
4666
5426
|
const config = loadConfig();
|
|
4667
5427
|
const manifest = readManifest();
|
|
@@ -4674,45 +5434,45 @@ async function statusCommand(options) {
|
|
|
4674
5434
|
}, null, 2));
|
|
4675
5435
|
return;
|
|
4676
5436
|
}
|
|
4677
|
-
console.log(
|
|
5437
|
+
console.log(chalk7.bold("\nCaliber Status\n"));
|
|
4678
5438
|
if (config) {
|
|
4679
|
-
console.log(` LLM: ${
|
|
5439
|
+
console.log(` LLM: ${chalk7.green(config.provider)} (${config.model})`);
|
|
4680
5440
|
} else {
|
|
4681
|
-
console.log(` LLM: ${
|
|
5441
|
+
console.log(` LLM: ${chalk7.yellow("Not configured")} \u2014 run ${chalk7.hex("#83D1EB")("caliber config")}`);
|
|
4682
5442
|
}
|
|
4683
5443
|
if (!manifest) {
|
|
4684
|
-
console.log(` Setup: ${
|
|
4685
|
-
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"));
|
|
4686
5446
|
return;
|
|
4687
5447
|
}
|
|
4688
|
-
console.log(` Files managed: ${
|
|
5448
|
+
console.log(` Files managed: ${chalk7.cyan(manifest.entries.length.toString())}`);
|
|
4689
5449
|
for (const entry of manifest.entries) {
|
|
4690
|
-
const exists =
|
|
4691
|
-
const icon = exists ?
|
|
5450
|
+
const exists = fs21.existsSync(entry.path);
|
|
5451
|
+
const icon = exists ? chalk7.green("\u2713") : chalk7.red("\u2717");
|
|
4692
5452
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
4693
5453
|
}
|
|
4694
5454
|
console.log("");
|
|
4695
5455
|
}
|
|
4696
5456
|
|
|
4697
5457
|
// src/commands/regenerate.ts
|
|
4698
|
-
import
|
|
4699
|
-
import
|
|
5458
|
+
import chalk8 from "chalk";
|
|
5459
|
+
import ora4 from "ora";
|
|
4700
5460
|
import confirm from "@inquirer/confirm";
|
|
4701
5461
|
async function regenerateCommand(options) {
|
|
4702
5462
|
const config = loadConfig();
|
|
4703
5463
|
if (!config) {
|
|
4704
|
-
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."));
|
|
4705
5465
|
throw new Error("__exit__");
|
|
4706
5466
|
}
|
|
4707
5467
|
const manifest = readManifest();
|
|
4708
5468
|
if (!manifest) {
|
|
4709
|
-
console.log(
|
|
5469
|
+
console.log(chalk8.yellow("No existing setup found. Run ") + chalk8.hex("#83D1EB")("caliber onboard") + chalk8.yellow(" first."));
|
|
4710
5470
|
throw new Error("__exit__");
|
|
4711
5471
|
}
|
|
4712
|
-
const spinner =
|
|
5472
|
+
const spinner = ora4("Re-analyzing project...").start();
|
|
4713
5473
|
const fingerprint = collectFingerprint(process.cwd());
|
|
4714
5474
|
spinner.succeed("Project re-analyzed");
|
|
4715
|
-
const genSpinner =
|
|
5475
|
+
const genSpinner = ora4("Regenerating setup...").start();
|
|
4716
5476
|
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES);
|
|
4717
5477
|
genMessages.start();
|
|
4718
5478
|
let generatedSetup = null;
|
|
@@ -4749,38 +5509,38 @@ async function regenerateCommand(options) {
|
|
|
4749
5509
|
}
|
|
4750
5510
|
genSpinner.succeed("Setup regenerated");
|
|
4751
5511
|
if (options.dryRun) {
|
|
4752
|
-
console.log(
|
|
5512
|
+
console.log(chalk8.yellow("\n[Dry run] Would write:"));
|
|
4753
5513
|
console.log(JSON.stringify(generatedSetup, null, 2));
|
|
4754
5514
|
return;
|
|
4755
5515
|
}
|
|
4756
5516
|
const shouldApply = await confirm({ message: "Apply regenerated setup?", default: true });
|
|
4757
5517
|
if (!shouldApply) {
|
|
4758
|
-
console.log(
|
|
5518
|
+
console.log(chalk8.dim("Regeneration cancelled."));
|
|
4759
5519
|
return;
|
|
4760
5520
|
}
|
|
4761
|
-
const writeSpinner =
|
|
5521
|
+
const writeSpinner = ora4("Updating config files...").start();
|
|
4762
5522
|
const result = writeSetup(generatedSetup);
|
|
4763
5523
|
writeSpinner.succeed("Config files updated");
|
|
4764
5524
|
for (const file of result.written) {
|
|
4765
|
-
console.log(` ${
|
|
5525
|
+
console.log(` ${chalk8.green("\u2713")} ${file}`);
|
|
4766
5526
|
}
|
|
4767
5527
|
console.log("");
|
|
4768
5528
|
}
|
|
4769
5529
|
|
|
4770
5530
|
// src/commands/recommend.ts
|
|
4771
|
-
import
|
|
4772
|
-
import
|
|
5531
|
+
import chalk9 from "chalk";
|
|
5532
|
+
import ora5 from "ora";
|
|
4773
5533
|
import { mkdirSync, readFileSync as readFileSync7, readdirSync as readdirSync5, existsSync as existsSync9, writeFileSync } from "fs";
|
|
4774
5534
|
import { join as join8, dirname as dirname2 } from "path";
|
|
4775
5535
|
|
|
4776
5536
|
// src/scanner/index.ts
|
|
4777
|
-
import
|
|
4778
|
-
import
|
|
5537
|
+
import fs22 from "fs";
|
|
5538
|
+
import path19 from "path";
|
|
4779
5539
|
import crypto2 from "crypto";
|
|
4780
5540
|
function scanLocalState(dir) {
|
|
4781
5541
|
const items = [];
|
|
4782
|
-
const claudeMdPath =
|
|
4783
|
-
if (
|
|
5542
|
+
const claudeMdPath = path19.join(dir, "CLAUDE.md");
|
|
5543
|
+
if (fs22.existsSync(claudeMdPath)) {
|
|
4784
5544
|
items.push({
|
|
4785
5545
|
type: "rule",
|
|
4786
5546
|
platform: "claude",
|
|
@@ -4789,10 +5549,10 @@ function scanLocalState(dir) {
|
|
|
4789
5549
|
path: claudeMdPath
|
|
4790
5550
|
});
|
|
4791
5551
|
}
|
|
4792
|
-
const skillsDir =
|
|
4793
|
-
if (
|
|
4794
|
-
for (const file of
|
|
4795
|
-
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);
|
|
4796
5556
|
items.push({
|
|
4797
5557
|
type: "skill",
|
|
4798
5558
|
platform: "claude",
|
|
@@ -4802,10 +5562,10 @@ function scanLocalState(dir) {
|
|
|
4802
5562
|
});
|
|
4803
5563
|
}
|
|
4804
5564
|
}
|
|
4805
|
-
const mcpJsonPath =
|
|
4806
|
-
if (
|
|
5565
|
+
const mcpJsonPath = path19.join(dir, ".mcp.json");
|
|
5566
|
+
if (fs22.existsSync(mcpJsonPath)) {
|
|
4807
5567
|
try {
|
|
4808
|
-
const mcpJson = JSON.parse(
|
|
5568
|
+
const mcpJson = JSON.parse(fs22.readFileSync(mcpJsonPath, "utf-8"));
|
|
4809
5569
|
if (mcpJson.mcpServers) {
|
|
4810
5570
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
4811
5571
|
items.push({
|
|
@@ -4820,8 +5580,8 @@ function scanLocalState(dir) {
|
|
|
4820
5580
|
} catch {
|
|
4821
5581
|
}
|
|
4822
5582
|
}
|
|
4823
|
-
const cursorrulesPath =
|
|
4824
|
-
if (
|
|
5583
|
+
const cursorrulesPath = path19.join(dir, ".cursorrules");
|
|
5584
|
+
if (fs22.existsSync(cursorrulesPath)) {
|
|
4825
5585
|
items.push({
|
|
4826
5586
|
type: "rule",
|
|
4827
5587
|
platform: "cursor",
|
|
@@ -4830,10 +5590,10 @@ function scanLocalState(dir) {
|
|
|
4830
5590
|
path: cursorrulesPath
|
|
4831
5591
|
});
|
|
4832
5592
|
}
|
|
4833
|
-
const cursorRulesDir =
|
|
4834
|
-
if (
|
|
4835
|
-
for (const file of
|
|
4836
|
-
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);
|
|
4837
5597
|
items.push({
|
|
4838
5598
|
type: "rule",
|
|
4839
5599
|
platform: "cursor",
|
|
@@ -4843,12 +5603,12 @@ function scanLocalState(dir) {
|
|
|
4843
5603
|
});
|
|
4844
5604
|
}
|
|
4845
5605
|
}
|
|
4846
|
-
const cursorSkillsDir =
|
|
4847
|
-
if (
|
|
5606
|
+
const cursorSkillsDir = path19.join(dir, ".cursor", "skills");
|
|
5607
|
+
if (fs22.existsSync(cursorSkillsDir)) {
|
|
4848
5608
|
try {
|
|
4849
|
-
for (const name of
|
|
4850
|
-
const skillFile =
|
|
4851
|
-
if (
|
|
5609
|
+
for (const name of fs22.readdirSync(cursorSkillsDir)) {
|
|
5610
|
+
const skillFile = path19.join(cursorSkillsDir, name, "SKILL.md");
|
|
5611
|
+
if (fs22.existsSync(skillFile)) {
|
|
4852
5612
|
items.push({
|
|
4853
5613
|
type: "skill",
|
|
4854
5614
|
platform: "cursor",
|
|
@@ -4861,10 +5621,10 @@ function scanLocalState(dir) {
|
|
|
4861
5621
|
} catch {
|
|
4862
5622
|
}
|
|
4863
5623
|
}
|
|
4864
|
-
const cursorMcpPath =
|
|
4865
|
-
if (
|
|
5624
|
+
const cursorMcpPath = path19.join(dir, ".cursor", "mcp.json");
|
|
5625
|
+
if (fs22.existsSync(cursorMcpPath)) {
|
|
4866
5626
|
try {
|
|
4867
|
-
const mcpJson = JSON.parse(
|
|
5627
|
+
const mcpJson = JSON.parse(fs22.readFileSync(cursorMcpPath, "utf-8"));
|
|
4868
5628
|
if (mcpJson.mcpServers) {
|
|
4869
5629
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
4870
5630
|
items.push({
|
|
@@ -4882,7 +5642,7 @@ function scanLocalState(dir) {
|
|
|
4882
5642
|
return items;
|
|
4883
5643
|
}
|
|
4884
5644
|
function hashFile(filePath) {
|
|
4885
|
-
const text =
|
|
5645
|
+
const text = fs22.readFileSync(filePath, "utf-8");
|
|
4886
5646
|
return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
|
|
4887
5647
|
}
|
|
4888
5648
|
function hashJson(obj) {
|
|
@@ -5040,7 +5800,7 @@ async function searchAllProviders(technologies, platform) {
|
|
|
5040
5800
|
}
|
|
5041
5801
|
return combined;
|
|
5042
5802
|
}
|
|
5043
|
-
async function
|
|
5803
|
+
async function scoreWithLLM2(candidates, projectContext, technologies) {
|
|
5044
5804
|
const candidateList = candidates.map((c, i) => `${i}. "${c.name}" \u2014 ${c.reason || "no description"}`).join("\n");
|
|
5045
5805
|
const scored = await llmJsonCall({
|
|
5046
5806
|
system: `You evaluate whether AI agent skills and tools are relevant to a specific software project.
|
|
@@ -5193,11 +5953,11 @@ async function recommendCommand(options) {
|
|
|
5193
5953
|
...extractTopDeps()
|
|
5194
5954
|
].filter(Boolean))];
|
|
5195
5955
|
if (technologies.length === 0) {
|
|
5196
|
-
console.log(
|
|
5956
|
+
console.log(chalk9.yellow("Could not detect any languages or dependencies. Try running from a project root."));
|
|
5197
5957
|
throw new Error("__exit__");
|
|
5198
5958
|
}
|
|
5199
5959
|
const primaryPlatform = platforms.includes("claude") ? "claude" : platforms[0];
|
|
5200
|
-
const searchSpinner =
|
|
5960
|
+
const searchSpinner = ora5("Searching skill registries...").start();
|
|
5201
5961
|
const allCandidates = await searchAllProviders(technologies, primaryPlatform);
|
|
5202
5962
|
if (!allCandidates.length) {
|
|
5203
5963
|
searchSpinner.succeed("No skills found matching your tech stack.");
|
|
@@ -5210,15 +5970,15 @@ async function recommendCommand(options) {
|
|
|
5210
5970
|
return;
|
|
5211
5971
|
}
|
|
5212
5972
|
searchSpinner.succeed(
|
|
5213
|
-
`Found ${allCandidates.length} skills` + (filteredCount > 0 ?
|
|
5973
|
+
`Found ${allCandidates.length} skills` + (filteredCount > 0 ? chalk9.dim(` (${filteredCount} already installed)`) : "")
|
|
5214
5974
|
);
|
|
5215
5975
|
let results;
|
|
5216
5976
|
const config = loadConfig();
|
|
5217
5977
|
if (config) {
|
|
5218
|
-
const scoreSpinner =
|
|
5978
|
+
const scoreSpinner = ora5("Scoring relevance for your project...").start();
|
|
5219
5979
|
try {
|
|
5220
5980
|
const projectContext = buildProjectContext(process.cwd());
|
|
5221
|
-
results = await
|
|
5981
|
+
results = await scoreWithLLM2(newCandidates, projectContext, technologies);
|
|
5222
5982
|
if (results.length === 0) {
|
|
5223
5983
|
scoreSpinner.succeed("No highly relevant skills found for your specific project.");
|
|
5224
5984
|
return;
|
|
@@ -5231,7 +5991,7 @@ async function recommendCommand(options) {
|
|
|
5231
5991
|
} else {
|
|
5232
5992
|
results = newCandidates.slice(0, 20);
|
|
5233
5993
|
}
|
|
5234
|
-
const fetchSpinner =
|
|
5994
|
+
const fetchSpinner = ora5("Verifying skill availability...").start();
|
|
5235
5995
|
const contentMap = /* @__PURE__ */ new Map();
|
|
5236
5996
|
await Promise.all(results.map(async (rec) => {
|
|
5237
5997
|
const content = await fetchSkillContent(rec);
|
|
@@ -5244,14 +6004,14 @@ async function recommendCommand(options) {
|
|
|
5244
6004
|
}
|
|
5245
6005
|
const unavailableCount = results.length - available.length;
|
|
5246
6006
|
fetchSpinner.succeed(
|
|
5247
|
-
`${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)`) : "")
|
|
5248
6008
|
);
|
|
5249
|
-
const selected = await
|
|
6009
|
+
const selected = await interactiveSelect2(available);
|
|
5250
6010
|
if (selected?.length) {
|
|
5251
6011
|
await installSkills(selected, platforms, contentMap);
|
|
5252
6012
|
}
|
|
5253
6013
|
}
|
|
5254
|
-
async function
|
|
6014
|
+
async function interactiveSelect2(recs) {
|
|
5255
6015
|
if (!process.stdin.isTTY) {
|
|
5256
6016
|
printRecommendations(recs);
|
|
5257
6017
|
return null;
|
|
@@ -5263,27 +6023,27 @@ async function interactiveSelect(recs) {
|
|
|
5263
6023
|
const hasScores = recs.some((r) => r.score > 0);
|
|
5264
6024
|
function render() {
|
|
5265
6025
|
const lines = [];
|
|
5266
|
-
lines.push(
|
|
6026
|
+
lines.push(chalk9.bold(" Recommendations"));
|
|
5267
6027
|
lines.push("");
|
|
5268
6028
|
if (hasScores) {
|
|
5269
|
-
lines.push(` ${
|
|
6029
|
+
lines.push(` ${chalk9.dim("Score".padEnd(7))} ${chalk9.dim("Name".padEnd(28))} ${chalk9.dim("Why")}`);
|
|
5270
6030
|
} else {
|
|
5271
|
-
lines.push(` ${
|
|
6031
|
+
lines.push(` ${chalk9.dim("Name".padEnd(30))} ${chalk9.dim("Technology".padEnd(18))} ${chalk9.dim("Source")}`);
|
|
5272
6032
|
}
|
|
5273
|
-
lines.push(
|
|
6033
|
+
lines.push(chalk9.dim(" " + "\u2500".repeat(70)));
|
|
5274
6034
|
for (let i = 0; i < recs.length; i++) {
|
|
5275
6035
|
const rec = recs[i];
|
|
5276
|
-
const check = selected.has(i) ?
|
|
5277
|
-
const ptr = i === cursor ?
|
|
6036
|
+
const check = selected.has(i) ? chalk9.green("[x]") : "[ ]";
|
|
6037
|
+
const ptr = i === cursor ? chalk9.cyan(">") : " ";
|
|
5278
6038
|
if (hasScores) {
|
|
5279
|
-
const scoreColor = rec.score >= 90 ?
|
|
5280
|
-
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))}`);
|
|
5281
6041
|
} else {
|
|
5282
|
-
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 || "")}`);
|
|
5283
6043
|
}
|
|
5284
6044
|
}
|
|
5285
6045
|
lines.push("");
|
|
5286
|
-
lines.push(
|
|
6046
|
+
lines.push(chalk9.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q cancel"));
|
|
5287
6047
|
return lines.join("\n");
|
|
5288
6048
|
}
|
|
5289
6049
|
function draw(initial) {
|
|
@@ -5332,7 +6092,7 @@ async function interactiveSelect(recs) {
|
|
|
5332
6092
|
case "\n":
|
|
5333
6093
|
cleanup();
|
|
5334
6094
|
if (selected.size === 0) {
|
|
5335
|
-
console.log(
|
|
6095
|
+
console.log(chalk9.dim("\n No skills selected.\n"));
|
|
5336
6096
|
resolve2(null);
|
|
5337
6097
|
} else {
|
|
5338
6098
|
resolve2(Array.from(selected).sort().map((i) => recs[i]));
|
|
@@ -5342,7 +6102,7 @@ async function interactiveSelect(recs) {
|
|
|
5342
6102
|
case "\x1B":
|
|
5343
6103
|
case "":
|
|
5344
6104
|
cleanup();
|
|
5345
|
-
console.log(
|
|
6105
|
+
console.log(chalk9.dim("\n Cancelled.\n"));
|
|
5346
6106
|
resolve2(null);
|
|
5347
6107
|
break;
|
|
5348
6108
|
}
|
|
@@ -5388,7 +6148,7 @@ async function fetchSkillContent(rec) {
|
|
|
5388
6148
|
return null;
|
|
5389
6149
|
}
|
|
5390
6150
|
async function installSkills(recs, platforms, contentMap) {
|
|
5391
|
-
const spinner =
|
|
6151
|
+
const spinner = ora5(`Installing ${recs.length} skill${recs.length > 1 ? "s" : ""}...`).start();
|
|
5392
6152
|
const installed = [];
|
|
5393
6153
|
for (const rec of recs) {
|
|
5394
6154
|
const content = contentMap.get(rec.slug);
|
|
@@ -5404,7 +6164,7 @@ async function installSkills(recs, platforms, contentMap) {
|
|
|
5404
6164
|
if (installed.length > 0) {
|
|
5405
6165
|
spinner.succeed(`Installed ${installed.length} file${installed.length > 1 ? "s" : ""}`);
|
|
5406
6166
|
for (const p of installed) {
|
|
5407
|
-
console.log(
|
|
6167
|
+
console.log(chalk9.green(` \u2713 ${p}`));
|
|
5408
6168
|
}
|
|
5409
6169
|
} else {
|
|
5410
6170
|
spinner.fail("No skills were installed");
|
|
@@ -5413,25 +6173,25 @@ async function installSkills(recs, platforms, contentMap) {
|
|
|
5413
6173
|
}
|
|
5414
6174
|
function printRecommendations(recs) {
|
|
5415
6175
|
const hasScores = recs.some((r) => r.score > 0);
|
|
5416
|
-
console.log(
|
|
6176
|
+
console.log(chalk9.bold("\n Recommendations\n"));
|
|
5417
6177
|
if (hasScores) {
|
|
5418
|
-
console.log(` ${
|
|
6178
|
+
console.log(` ${chalk9.dim("Score".padEnd(7))} ${chalk9.dim("Name".padEnd(28))} ${chalk9.dim("Why")}`);
|
|
5419
6179
|
} else {
|
|
5420
|
-
console.log(` ${
|
|
6180
|
+
console.log(` ${chalk9.dim("Name".padEnd(30))} ${chalk9.dim("Technology".padEnd(18))} ${chalk9.dim("Source")}`);
|
|
5421
6181
|
}
|
|
5422
|
-
console.log(
|
|
6182
|
+
console.log(chalk9.dim(" " + "\u2500".repeat(70)));
|
|
5423
6183
|
for (const rec of recs) {
|
|
5424
6184
|
if (hasScores) {
|
|
5425
|
-
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))}`);
|
|
5426
6186
|
} else {
|
|
5427
|
-
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 || "")}`);
|
|
5428
6188
|
}
|
|
5429
6189
|
}
|
|
5430
6190
|
console.log("");
|
|
5431
6191
|
}
|
|
5432
6192
|
|
|
5433
6193
|
// src/commands/score.ts
|
|
5434
|
-
import
|
|
6194
|
+
import chalk10 from "chalk";
|
|
5435
6195
|
async function scoreCommand(options) {
|
|
5436
6196
|
const dir = process.cwd();
|
|
5437
6197
|
const target = options.agent ?? readState()?.targetAgent;
|
|
@@ -5445,23 +6205,23 @@ async function scoreCommand(options) {
|
|
|
5445
6205
|
return;
|
|
5446
6206
|
}
|
|
5447
6207
|
displayScore(result);
|
|
5448
|
-
const separator =
|
|
6208
|
+
const separator = chalk10.gray(" " + "\u2500".repeat(53));
|
|
5449
6209
|
console.log(separator);
|
|
5450
6210
|
if (result.score < 40) {
|
|
5451
|
-
console.log(
|
|
6211
|
+
console.log(chalk10.gray(" Run ") + chalk10.hex("#83D1EB")("caliber onboard") + chalk10.gray(" to generate a complete, optimized setup."));
|
|
5452
6212
|
} else if (result.score < 70) {
|
|
5453
|
-
console.log(
|
|
6213
|
+
console.log(chalk10.gray(" Run ") + chalk10.hex("#83D1EB")("caliber onboard") + chalk10.gray(" to improve your setup."));
|
|
5454
6214
|
} else {
|
|
5455
|
-
console.log(
|
|
6215
|
+
console.log(chalk10.green(" Looking good!") + chalk10.gray(" Run ") + chalk10.hex("#83D1EB")("caliber update") + chalk10.gray(" to keep it fresh."));
|
|
5456
6216
|
}
|
|
5457
6217
|
console.log("");
|
|
5458
6218
|
}
|
|
5459
6219
|
|
|
5460
6220
|
// src/commands/refresh.ts
|
|
5461
|
-
import
|
|
5462
|
-
import
|
|
5463
|
-
import
|
|
5464
|
-
import
|
|
6221
|
+
import fs24 from "fs";
|
|
6222
|
+
import path21 from "path";
|
|
6223
|
+
import chalk11 from "chalk";
|
|
6224
|
+
import ora6 from "ora";
|
|
5465
6225
|
|
|
5466
6226
|
// src/lib/git-diff.ts
|
|
5467
6227
|
import { execSync as execSync8 } from "child_process";
|
|
@@ -5536,37 +6296,37 @@ function collectDiff(lastSha) {
|
|
|
5536
6296
|
}
|
|
5537
6297
|
|
|
5538
6298
|
// src/writers/refresh.ts
|
|
5539
|
-
import
|
|
5540
|
-
import
|
|
6299
|
+
import fs23 from "fs";
|
|
6300
|
+
import path20 from "path";
|
|
5541
6301
|
function writeRefreshDocs(docs) {
|
|
5542
6302
|
const written = [];
|
|
5543
6303
|
if (docs.claudeMd) {
|
|
5544
|
-
|
|
6304
|
+
fs23.writeFileSync("CLAUDE.md", docs.claudeMd);
|
|
5545
6305
|
written.push("CLAUDE.md");
|
|
5546
6306
|
}
|
|
5547
6307
|
if (docs.readmeMd) {
|
|
5548
|
-
|
|
6308
|
+
fs23.writeFileSync("README.md", docs.readmeMd);
|
|
5549
6309
|
written.push("README.md");
|
|
5550
6310
|
}
|
|
5551
6311
|
if (docs.cursorrules) {
|
|
5552
|
-
|
|
6312
|
+
fs23.writeFileSync(".cursorrules", docs.cursorrules);
|
|
5553
6313
|
written.push(".cursorrules");
|
|
5554
6314
|
}
|
|
5555
6315
|
if (docs.cursorRules) {
|
|
5556
|
-
const rulesDir =
|
|
5557
|
-
if (!
|
|
6316
|
+
const rulesDir = path20.join(".cursor", "rules");
|
|
6317
|
+
if (!fs23.existsSync(rulesDir)) fs23.mkdirSync(rulesDir, { recursive: true });
|
|
5558
6318
|
for (const rule of docs.cursorRules) {
|
|
5559
|
-
const filePath =
|
|
5560
|
-
|
|
6319
|
+
const filePath = path20.join(rulesDir, rule.filename);
|
|
6320
|
+
fs23.writeFileSync(filePath, rule.content);
|
|
5561
6321
|
written.push(filePath);
|
|
5562
6322
|
}
|
|
5563
6323
|
}
|
|
5564
6324
|
if (docs.claudeSkills) {
|
|
5565
|
-
const skillsDir =
|
|
5566
|
-
if (!
|
|
6325
|
+
const skillsDir = path20.join(".claude", "skills");
|
|
6326
|
+
if (!fs23.existsSync(skillsDir)) fs23.mkdirSync(skillsDir, { recursive: true });
|
|
5567
6327
|
for (const skill of docs.claudeSkills) {
|
|
5568
|
-
const filePath =
|
|
5569
|
-
|
|
6328
|
+
const filePath = path20.join(skillsDir, skill.filename);
|
|
6329
|
+
fs23.writeFileSync(filePath, skill.content);
|
|
5570
6330
|
written.push(filePath);
|
|
5571
6331
|
}
|
|
5572
6332
|
}
|
|
@@ -5641,11 +6401,11 @@ function log(quiet, ...args) {
|
|
|
5641
6401
|
function discoverGitRepos(parentDir) {
|
|
5642
6402
|
const repos = [];
|
|
5643
6403
|
try {
|
|
5644
|
-
const entries =
|
|
6404
|
+
const entries = fs24.readdirSync(parentDir, { withFileTypes: true });
|
|
5645
6405
|
for (const entry of entries) {
|
|
5646
6406
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
5647
|
-
const childPath =
|
|
5648
|
-
if (
|
|
6407
|
+
const childPath = path21.join(parentDir, entry.name);
|
|
6408
|
+
if (fs24.existsSync(path21.join(childPath, ".git"))) {
|
|
5649
6409
|
repos.push(childPath);
|
|
5650
6410
|
}
|
|
5651
6411
|
}
|
|
@@ -5655,7 +6415,7 @@ function discoverGitRepos(parentDir) {
|
|
|
5655
6415
|
}
|
|
5656
6416
|
async function refreshSingleRepo(repoDir, options) {
|
|
5657
6417
|
const quiet = !!options.quiet;
|
|
5658
|
-
const prefix = options.label ? `${
|
|
6418
|
+
const prefix = options.label ? `${chalk11.bold(options.label)} ` : "";
|
|
5659
6419
|
const state = readState();
|
|
5660
6420
|
const lastSha = state?.lastRefreshSha ?? null;
|
|
5661
6421
|
const diff = collectDiff(lastSha);
|
|
@@ -5664,10 +6424,10 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
5664
6424
|
if (currentSha) {
|
|
5665
6425
|
writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
5666
6426
|
}
|
|
5667
|
-
log(quiet,
|
|
6427
|
+
log(quiet, chalk11.dim(`${prefix}No changes since last refresh.`));
|
|
5668
6428
|
return;
|
|
5669
6429
|
}
|
|
5670
|
-
const spinner = quiet ? null :
|
|
6430
|
+
const spinner = quiet ? null : ora6(`${prefix}Analyzing changes...`).start();
|
|
5671
6431
|
const existingDocs = readExistingConfigs(repoDir);
|
|
5672
6432
|
const fingerprint = collectFingerprint(repoDir);
|
|
5673
6433
|
const projectContext = {
|
|
@@ -5696,10 +6456,10 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
5696
6456
|
if (options.dryRun) {
|
|
5697
6457
|
spinner?.info(`${prefix}Dry run \u2014 would update:`);
|
|
5698
6458
|
for (const doc of response.docsUpdated) {
|
|
5699
|
-
console.log(` ${
|
|
6459
|
+
console.log(` ${chalk11.yellow("~")} ${doc}`);
|
|
5700
6460
|
}
|
|
5701
6461
|
if (response.changesSummary) {
|
|
5702
|
-
console.log(
|
|
6462
|
+
console.log(chalk11.dim(`
|
|
5703
6463
|
${response.changesSummary}`));
|
|
5704
6464
|
}
|
|
5705
6465
|
return;
|
|
@@ -5707,10 +6467,10 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
5707
6467
|
const written = writeRefreshDocs(response.updatedDocs);
|
|
5708
6468
|
spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
|
|
5709
6469
|
for (const file of written) {
|
|
5710
|
-
log(quiet, ` ${
|
|
6470
|
+
log(quiet, ` ${chalk11.green("\u2713")} ${file}`);
|
|
5711
6471
|
}
|
|
5712
6472
|
if (response.changesSummary) {
|
|
5713
|
-
log(quiet,
|
|
6473
|
+
log(quiet, chalk11.dim(`
|
|
5714
6474
|
${response.changesSummary}`));
|
|
5715
6475
|
}
|
|
5716
6476
|
if (currentSha) {
|
|
@@ -5723,7 +6483,7 @@ async function refreshCommand(options) {
|
|
|
5723
6483
|
const config = loadConfig();
|
|
5724
6484
|
if (!config) {
|
|
5725
6485
|
if (quiet) return;
|
|
5726
|
-
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."));
|
|
5727
6487
|
throw new Error("__exit__");
|
|
5728
6488
|
}
|
|
5729
6489
|
if (isGitRepo()) {
|
|
@@ -5733,20 +6493,20 @@ async function refreshCommand(options) {
|
|
|
5733
6493
|
const repos = discoverGitRepos(process.cwd());
|
|
5734
6494
|
if (repos.length === 0) {
|
|
5735
6495
|
if (quiet) return;
|
|
5736
|
-
console.log(
|
|
6496
|
+
console.log(chalk11.red("Not inside a git repository and no git repos found in child directories."));
|
|
5737
6497
|
throw new Error("__exit__");
|
|
5738
6498
|
}
|
|
5739
|
-
log(quiet,
|
|
6499
|
+
log(quiet, chalk11.dim(`Found ${repos.length} git repo${repos.length === 1 ? "" : "s"}
|
|
5740
6500
|
`));
|
|
5741
6501
|
const originalDir = process.cwd();
|
|
5742
6502
|
for (const repo of repos) {
|
|
5743
|
-
const repoName =
|
|
6503
|
+
const repoName = path21.basename(repo);
|
|
5744
6504
|
try {
|
|
5745
6505
|
process.chdir(repo);
|
|
5746
6506
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
5747
6507
|
} catch (err) {
|
|
5748
6508
|
if (err instanceof Error && err.message === "__exit__") continue;
|
|
5749
|
-
log(quiet,
|
|
6509
|
+
log(quiet, chalk11.yellow(`${repoName}: refresh failed \u2014 ${err instanceof Error ? err.message : "unknown error"}`));
|
|
5750
6510
|
}
|
|
5751
6511
|
}
|
|
5752
6512
|
process.chdir(originalDir);
|
|
@@ -5754,13 +6514,13 @@ async function refreshCommand(options) {
|
|
|
5754
6514
|
if (err instanceof Error && err.message === "__exit__") throw err;
|
|
5755
6515
|
if (quiet) return;
|
|
5756
6516
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
5757
|
-
console.log(
|
|
6517
|
+
console.log(chalk11.red(`Refresh failed: ${msg}`));
|
|
5758
6518
|
throw new Error("__exit__");
|
|
5759
6519
|
}
|
|
5760
6520
|
}
|
|
5761
6521
|
|
|
5762
6522
|
// src/commands/hooks.ts
|
|
5763
|
-
import
|
|
6523
|
+
import chalk12 from "chalk";
|
|
5764
6524
|
var HOOKS = [
|
|
5765
6525
|
{
|
|
5766
6526
|
id: "session-end",
|
|
@@ -5780,13 +6540,13 @@ var HOOKS = [
|
|
|
5780
6540
|
}
|
|
5781
6541
|
];
|
|
5782
6542
|
function printStatus() {
|
|
5783
|
-
console.log(
|
|
6543
|
+
console.log(chalk12.bold("\n Hooks\n"));
|
|
5784
6544
|
for (const hook of HOOKS) {
|
|
5785
6545
|
const installed = hook.isInstalled();
|
|
5786
|
-
const icon = installed ?
|
|
5787
|
-
const state = installed ?
|
|
6546
|
+
const icon = installed ? chalk12.green("\u2713") : chalk12.dim("\u2717");
|
|
6547
|
+
const state = installed ? chalk12.green("enabled") : chalk12.dim("disabled");
|
|
5788
6548
|
console.log(` ${icon} ${hook.label.padEnd(26)} ${state}`);
|
|
5789
|
-
console.log(
|
|
6549
|
+
console.log(chalk12.dim(` ${hook.description}`));
|
|
5790
6550
|
}
|
|
5791
6551
|
console.log("");
|
|
5792
6552
|
}
|
|
@@ -5795,9 +6555,9 @@ async function hooksCommand(options) {
|
|
|
5795
6555
|
for (const hook of HOOKS) {
|
|
5796
6556
|
const result = hook.install();
|
|
5797
6557
|
if (result.alreadyInstalled) {
|
|
5798
|
-
console.log(
|
|
6558
|
+
console.log(chalk12.dim(` ${hook.label} already enabled.`));
|
|
5799
6559
|
} else {
|
|
5800
|
-
console.log(
|
|
6560
|
+
console.log(chalk12.green(" \u2713") + ` ${hook.label} enabled`);
|
|
5801
6561
|
}
|
|
5802
6562
|
}
|
|
5803
6563
|
return;
|
|
@@ -5806,9 +6566,9 @@ async function hooksCommand(options) {
|
|
|
5806
6566
|
for (const hook of HOOKS) {
|
|
5807
6567
|
const result = hook.remove();
|
|
5808
6568
|
if (result.notFound) {
|
|
5809
|
-
console.log(
|
|
6569
|
+
console.log(chalk12.dim(` ${hook.label} already disabled.`));
|
|
5810
6570
|
} else {
|
|
5811
|
-
console.log(
|
|
6571
|
+
console.log(chalk12.green(" \u2713") + ` ${hook.label} removed`);
|
|
5812
6572
|
}
|
|
5813
6573
|
}
|
|
5814
6574
|
return;
|
|
@@ -5823,18 +6583,18 @@ async function hooksCommand(options) {
|
|
|
5823
6583
|
const states = HOOKS.map((h) => h.isInstalled());
|
|
5824
6584
|
function render() {
|
|
5825
6585
|
const lines = [];
|
|
5826
|
-
lines.push(
|
|
6586
|
+
lines.push(chalk12.bold(" Hooks"));
|
|
5827
6587
|
lines.push("");
|
|
5828
6588
|
for (let i = 0; i < HOOKS.length; i++) {
|
|
5829
6589
|
const hook = HOOKS[i];
|
|
5830
6590
|
const enabled = states[i];
|
|
5831
|
-
const toggle = enabled ?
|
|
5832
|
-
const ptr = i === cursor ?
|
|
6591
|
+
const toggle = enabled ? chalk12.green("[on] ") : chalk12.dim("[off]");
|
|
6592
|
+
const ptr = i === cursor ? chalk12.cyan(">") : " ";
|
|
5833
6593
|
lines.push(` ${ptr} ${toggle} ${hook.label}`);
|
|
5834
|
-
lines.push(
|
|
6594
|
+
lines.push(chalk12.dim(` ${hook.description}`));
|
|
5835
6595
|
}
|
|
5836
6596
|
lines.push("");
|
|
5837
|
-
lines.push(
|
|
6597
|
+
lines.push(chalk12.dim(" \u2191\u2193 navigate \u23B5 toggle a all on n all off \u23CE apply q cancel"));
|
|
5838
6598
|
return lines.join("\n");
|
|
5839
6599
|
}
|
|
5840
6600
|
function draw(initial) {
|
|
@@ -5865,16 +6625,16 @@ async function hooksCommand(options) {
|
|
|
5865
6625
|
const wantEnabled = states[i];
|
|
5866
6626
|
if (wantEnabled && !wasInstalled) {
|
|
5867
6627
|
hook.install();
|
|
5868
|
-
console.log(
|
|
6628
|
+
console.log(chalk12.green(" \u2713") + ` ${hook.label} enabled`);
|
|
5869
6629
|
changed++;
|
|
5870
6630
|
} else if (!wantEnabled && wasInstalled) {
|
|
5871
6631
|
hook.remove();
|
|
5872
|
-
console.log(
|
|
6632
|
+
console.log(chalk12.green(" \u2713") + ` ${hook.label} disabled`);
|
|
5873
6633
|
changed++;
|
|
5874
6634
|
}
|
|
5875
6635
|
}
|
|
5876
6636
|
if (changed === 0) {
|
|
5877
|
-
console.log(
|
|
6637
|
+
console.log(chalk12.dim(" No changes."));
|
|
5878
6638
|
}
|
|
5879
6639
|
console.log("");
|
|
5880
6640
|
}
|
|
@@ -5910,7 +6670,7 @@ async function hooksCommand(options) {
|
|
|
5910
6670
|
case "\x1B":
|
|
5911
6671
|
case "":
|
|
5912
6672
|
cleanup();
|
|
5913
|
-
console.log(
|
|
6673
|
+
console.log(chalk12.dim("\n Cancelled.\n"));
|
|
5914
6674
|
resolve2();
|
|
5915
6675
|
break;
|
|
5916
6676
|
}
|
|
@@ -5920,43 +6680,43 @@ async function hooksCommand(options) {
|
|
|
5920
6680
|
}
|
|
5921
6681
|
|
|
5922
6682
|
// src/commands/config.ts
|
|
5923
|
-
import
|
|
6683
|
+
import chalk13 from "chalk";
|
|
5924
6684
|
async function configCommand() {
|
|
5925
6685
|
const existing = loadConfig();
|
|
5926
6686
|
if (existing) {
|
|
5927
|
-
console.log(
|
|
5928
|
-
console.log(` Provider: ${
|
|
5929
|
-
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)}`);
|
|
5930
6690
|
if (existing.apiKey) {
|
|
5931
6691
|
const masked = existing.apiKey.slice(0, 8) + "..." + existing.apiKey.slice(-4);
|
|
5932
|
-
console.log(` API Key: ${
|
|
6692
|
+
console.log(` API Key: ${chalk13.dim(masked)}`);
|
|
5933
6693
|
}
|
|
5934
6694
|
if (existing.provider === "cursor") {
|
|
5935
|
-
console.log(` Seat: ${
|
|
6695
|
+
console.log(` Seat: ${chalk13.dim("Cursor (agent acp)")}`);
|
|
5936
6696
|
}
|
|
5937
6697
|
if (existing.provider === "claude-cli") {
|
|
5938
|
-
console.log(` Seat: ${
|
|
6698
|
+
console.log(` Seat: ${chalk13.dim("Claude Code (claude -p)")}`);
|
|
5939
6699
|
}
|
|
5940
6700
|
if (existing.baseUrl) {
|
|
5941
|
-
console.log(` Base URL: ${
|
|
6701
|
+
console.log(` Base URL: ${chalk13.dim(existing.baseUrl)}`);
|
|
5942
6702
|
}
|
|
5943
6703
|
if (existing.vertexProjectId) {
|
|
5944
|
-
console.log(` Vertex Project: ${
|
|
5945
|
-
console.log(` Vertex Region: ${
|
|
6704
|
+
console.log(` Vertex Project: ${chalk13.dim(existing.vertexProjectId)}`);
|
|
6705
|
+
console.log(` Vertex Region: ${chalk13.dim(existing.vertexRegion || "us-east5")}`);
|
|
5946
6706
|
}
|
|
5947
|
-
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())}`);
|
|
5948
6708
|
console.log("");
|
|
5949
6709
|
}
|
|
5950
6710
|
await runInteractiveProviderSetup();
|
|
5951
|
-
console.log(
|
|
5952
|
-
console.log(
|
|
6711
|
+
console.log(chalk13.green("\n\u2713 Configuration saved"));
|
|
6712
|
+
console.log(chalk13.dim(` ${getConfigFilePath()}
|
|
5953
6713
|
`));
|
|
5954
|
-
console.log(
|
|
5955
|
-
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"));
|
|
5956
6716
|
}
|
|
5957
6717
|
|
|
5958
6718
|
// src/commands/learn.ts
|
|
5959
|
-
import
|
|
6719
|
+
import chalk14 from "chalk";
|
|
5960
6720
|
|
|
5961
6721
|
// src/learner/stdin.ts
|
|
5962
6722
|
var STDIN_TIMEOUT_MS = 5e3;
|
|
@@ -5987,8 +6747,8 @@ function readStdin() {
|
|
|
5987
6747
|
|
|
5988
6748
|
// src/learner/storage.ts
|
|
5989
6749
|
init_constants();
|
|
5990
|
-
import
|
|
5991
|
-
import
|
|
6750
|
+
import fs25 from "fs";
|
|
6751
|
+
import path22 from "path";
|
|
5992
6752
|
var MAX_RESPONSE_LENGTH = 2e3;
|
|
5993
6753
|
var DEFAULT_STATE = {
|
|
5994
6754
|
sessionId: null,
|
|
@@ -5996,15 +6756,15 @@ var DEFAULT_STATE = {
|
|
|
5996
6756
|
lastAnalysisTimestamp: null
|
|
5997
6757
|
};
|
|
5998
6758
|
function ensureLearningDir() {
|
|
5999
|
-
if (!
|
|
6000
|
-
|
|
6759
|
+
if (!fs25.existsSync(LEARNING_DIR)) {
|
|
6760
|
+
fs25.mkdirSync(LEARNING_DIR, { recursive: true });
|
|
6001
6761
|
}
|
|
6002
6762
|
}
|
|
6003
6763
|
function sessionFilePath() {
|
|
6004
|
-
return
|
|
6764
|
+
return path22.join(LEARNING_DIR, LEARNING_SESSION_FILE);
|
|
6005
6765
|
}
|
|
6006
6766
|
function stateFilePath() {
|
|
6007
|
-
return
|
|
6767
|
+
return path22.join(LEARNING_DIR, LEARNING_STATE_FILE);
|
|
6008
6768
|
}
|
|
6009
6769
|
function truncateResponse(response) {
|
|
6010
6770
|
const str = JSON.stringify(response);
|
|
@@ -6015,50 +6775,50 @@ function appendEvent(event) {
|
|
|
6015
6775
|
ensureLearningDir();
|
|
6016
6776
|
const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
|
|
6017
6777
|
const filePath = sessionFilePath();
|
|
6018
|
-
|
|
6778
|
+
fs25.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
6019
6779
|
const count = getEventCount();
|
|
6020
6780
|
if (count > LEARNING_MAX_EVENTS) {
|
|
6021
|
-
const lines =
|
|
6781
|
+
const lines = fs25.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
6022
6782
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
6023
|
-
|
|
6783
|
+
fs25.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
6024
6784
|
}
|
|
6025
6785
|
}
|
|
6026
6786
|
function readAllEvents() {
|
|
6027
6787
|
const filePath = sessionFilePath();
|
|
6028
|
-
if (!
|
|
6029
|
-
const lines =
|
|
6788
|
+
if (!fs25.existsSync(filePath)) return [];
|
|
6789
|
+
const lines = fs25.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
6030
6790
|
return lines.map((line) => JSON.parse(line));
|
|
6031
6791
|
}
|
|
6032
6792
|
function getEventCount() {
|
|
6033
6793
|
const filePath = sessionFilePath();
|
|
6034
|
-
if (!
|
|
6035
|
-
const content =
|
|
6794
|
+
if (!fs25.existsSync(filePath)) return 0;
|
|
6795
|
+
const content = fs25.readFileSync(filePath, "utf-8");
|
|
6036
6796
|
return content.split("\n").filter(Boolean).length;
|
|
6037
6797
|
}
|
|
6038
6798
|
function clearSession() {
|
|
6039
6799
|
const filePath = sessionFilePath();
|
|
6040
|
-
if (
|
|
6800
|
+
if (fs25.existsSync(filePath)) fs25.unlinkSync(filePath);
|
|
6041
6801
|
}
|
|
6042
6802
|
function readState2() {
|
|
6043
6803
|
const filePath = stateFilePath();
|
|
6044
|
-
if (!
|
|
6804
|
+
if (!fs25.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
6045
6805
|
try {
|
|
6046
|
-
return JSON.parse(
|
|
6806
|
+
return JSON.parse(fs25.readFileSync(filePath, "utf-8"));
|
|
6047
6807
|
} catch {
|
|
6048
6808
|
return { ...DEFAULT_STATE };
|
|
6049
6809
|
}
|
|
6050
6810
|
}
|
|
6051
6811
|
function writeState2(state) {
|
|
6052
6812
|
ensureLearningDir();
|
|
6053
|
-
|
|
6813
|
+
fs25.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
|
|
6054
6814
|
}
|
|
6055
6815
|
function resetState() {
|
|
6056
6816
|
writeState2({ ...DEFAULT_STATE });
|
|
6057
6817
|
}
|
|
6058
6818
|
|
|
6059
6819
|
// src/learner/writer.ts
|
|
6060
|
-
import
|
|
6061
|
-
import
|
|
6820
|
+
import fs26 from "fs";
|
|
6821
|
+
import path23 from "path";
|
|
6062
6822
|
var LEARNED_START = "<!-- caliber:learned -->";
|
|
6063
6823
|
var LEARNED_END = "<!-- /caliber:learned -->";
|
|
6064
6824
|
function writeLearnedContent(update) {
|
|
@@ -6078,8 +6838,8 @@ function writeLearnedContent(update) {
|
|
|
6078
6838
|
function writeLearnedSection(content) {
|
|
6079
6839
|
const claudeMdPath = "CLAUDE.md";
|
|
6080
6840
|
let existing = "";
|
|
6081
|
-
if (
|
|
6082
|
-
existing =
|
|
6841
|
+
if (fs26.existsSync(claudeMdPath)) {
|
|
6842
|
+
existing = fs26.readFileSync(claudeMdPath, "utf-8");
|
|
6083
6843
|
}
|
|
6084
6844
|
const section = `${LEARNED_START}
|
|
6085
6845
|
${content}
|
|
@@ -6093,15 +6853,15 @@ ${LEARNED_END}`;
|
|
|
6093
6853
|
const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
6094
6854
|
updated = existing + separator + "\n" + section + "\n";
|
|
6095
6855
|
}
|
|
6096
|
-
|
|
6856
|
+
fs26.writeFileSync(claudeMdPath, updated);
|
|
6097
6857
|
}
|
|
6098
6858
|
function writeLearnedSkill(skill) {
|
|
6099
|
-
const skillDir =
|
|
6100
|
-
if (!
|
|
6101
|
-
const skillPath =
|
|
6102
|
-
if (!skill.isNew &&
|
|
6103
|
-
const existing =
|
|
6104
|
-
|
|
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);
|
|
6105
6865
|
} else {
|
|
6106
6866
|
const frontmatter = [
|
|
6107
6867
|
"---",
|
|
@@ -6110,14 +6870,14 @@ function writeLearnedSkill(skill) {
|
|
|
6110
6870
|
"---",
|
|
6111
6871
|
""
|
|
6112
6872
|
].join("\n");
|
|
6113
|
-
|
|
6873
|
+
fs26.writeFileSync(skillPath, frontmatter + skill.content);
|
|
6114
6874
|
}
|
|
6115
6875
|
return skillPath;
|
|
6116
6876
|
}
|
|
6117
6877
|
function readLearnedSection() {
|
|
6118
6878
|
const claudeMdPath = "CLAUDE.md";
|
|
6119
|
-
if (!
|
|
6120
|
-
const content =
|
|
6879
|
+
if (!fs26.existsSync(claudeMdPath)) return null;
|
|
6880
|
+
const content = fs26.readFileSync(claudeMdPath, "utf-8");
|
|
6121
6881
|
const startIdx = content.indexOf(LEARNED_START);
|
|
6122
6882
|
const endIdx = content.indexOf(LEARNED_END);
|
|
6123
6883
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
@@ -6257,53 +7017,53 @@ async function learnFinalizeCommand() {
|
|
|
6257
7017
|
async function learnInstallCommand() {
|
|
6258
7018
|
const result = installLearningHooks();
|
|
6259
7019
|
if (result.alreadyInstalled) {
|
|
6260
|
-
console.log(
|
|
7020
|
+
console.log(chalk14.dim("Learning hooks already installed."));
|
|
6261
7021
|
return;
|
|
6262
7022
|
}
|
|
6263
|
-
console.log(
|
|
6264
|
-
console.log(
|
|
6265
|
-
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."));
|
|
6266
7026
|
}
|
|
6267
7027
|
async function learnRemoveCommand() {
|
|
6268
7028
|
const result = removeLearningHooks();
|
|
6269
7029
|
if (result.notFound) {
|
|
6270
|
-
console.log(
|
|
7030
|
+
console.log(chalk14.dim("Learning hooks not found."));
|
|
6271
7031
|
return;
|
|
6272
7032
|
}
|
|
6273
|
-
console.log(
|
|
7033
|
+
console.log(chalk14.green("\u2713") + " Learning hooks removed from .claude/settings.json");
|
|
6274
7034
|
}
|
|
6275
7035
|
async function learnStatusCommand() {
|
|
6276
7036
|
const installed = areLearningHooksInstalled();
|
|
6277
7037
|
const state = readState2();
|
|
6278
7038
|
const eventCount = getEventCount();
|
|
6279
|
-
console.log(
|
|
7039
|
+
console.log(chalk14.bold("Session Learning Status"));
|
|
6280
7040
|
console.log();
|
|
6281
7041
|
if (installed) {
|
|
6282
|
-
console.log(
|
|
7042
|
+
console.log(chalk14.green("\u2713") + " Learning hooks are " + chalk14.green("installed"));
|
|
6283
7043
|
} else {
|
|
6284
|
-
console.log(
|
|
6285
|
-
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."));
|
|
6286
7046
|
}
|
|
6287
7047
|
console.log();
|
|
6288
|
-
console.log(`Events recorded: ${
|
|
6289
|
-
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))}`);
|
|
6290
7050
|
if (state.lastAnalysisTimestamp) {
|
|
6291
|
-
console.log(`Last analysis: ${
|
|
7051
|
+
console.log(`Last analysis: ${chalk14.cyan(state.lastAnalysisTimestamp)}`);
|
|
6292
7052
|
} else {
|
|
6293
|
-
console.log(`Last analysis: ${
|
|
7053
|
+
console.log(`Last analysis: ${chalk14.dim("none")}`);
|
|
6294
7054
|
}
|
|
6295
7055
|
const learnedSection = readLearnedSection();
|
|
6296
7056
|
if (learnedSection) {
|
|
6297
7057
|
const lineCount = learnedSection.split("\n").filter(Boolean).length;
|
|
6298
7058
|
console.log(`
|
|
6299
|
-
Learned items in CLAUDE.md: ${
|
|
7059
|
+
Learned items in CLAUDE.md: ${chalk14.cyan(String(lineCount))}`);
|
|
6300
7060
|
}
|
|
6301
7061
|
}
|
|
6302
7062
|
|
|
6303
7063
|
// src/cli.ts
|
|
6304
|
-
var __dirname =
|
|
7064
|
+
var __dirname = path24.dirname(fileURLToPath(import.meta.url));
|
|
6305
7065
|
var pkg = JSON.parse(
|
|
6306
|
-
|
|
7066
|
+
fs27.readFileSync(path24.resolve(__dirname, "..", "package.json"), "utf-8")
|
|
6307
7067
|
);
|
|
6308
7068
|
var program = new Command();
|
|
6309
7069
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
|
|
@@ -6325,22 +7085,22 @@ learn.command("remove").description("Remove learning hooks from .claude/settings
|
|
|
6325
7085
|
learn.command("status").description("Show learning system status").action(learnStatusCommand);
|
|
6326
7086
|
|
|
6327
7087
|
// src/utils/version-check.ts
|
|
6328
|
-
import
|
|
6329
|
-
import
|
|
7088
|
+
import fs28 from "fs";
|
|
7089
|
+
import path25 from "path";
|
|
6330
7090
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6331
7091
|
import { execSync as execSync9 } from "child_process";
|
|
6332
|
-
import
|
|
6333
|
-
import
|
|
7092
|
+
import chalk15 from "chalk";
|
|
7093
|
+
import ora7 from "ora";
|
|
6334
7094
|
import confirm2 from "@inquirer/confirm";
|
|
6335
|
-
var __dirname_vc =
|
|
7095
|
+
var __dirname_vc = path25.dirname(fileURLToPath2(import.meta.url));
|
|
6336
7096
|
var pkg2 = JSON.parse(
|
|
6337
|
-
|
|
7097
|
+
fs28.readFileSync(path25.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
6338
7098
|
);
|
|
6339
7099
|
function getInstalledVersion() {
|
|
6340
7100
|
try {
|
|
6341
7101
|
const globalRoot = execSync9("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
6342
|
-
const pkgPath =
|
|
6343
|
-
return JSON.parse(
|
|
7102
|
+
const pkgPath = path25.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
7103
|
+
return JSON.parse(fs28.readFileSync(pkgPath, "utf-8")).version;
|
|
6344
7104
|
} catch {
|
|
6345
7105
|
return null;
|
|
6346
7106
|
}
|
|
@@ -6363,17 +7123,17 @@ async function checkForUpdates() {
|
|
|
6363
7123
|
const isInteractive = process.stdin.isTTY === true;
|
|
6364
7124
|
if (!isInteractive) {
|
|
6365
7125
|
console.log(
|
|
6366
|
-
|
|
7126
|
+
chalk15.yellow(
|
|
6367
7127
|
`
|
|
6368
7128
|
Update available: ${current} -> ${latest}
|
|
6369
|
-
Run ${
|
|
7129
|
+
Run ${chalk15.bold("npm install -g @rely-ai/caliber")} to upgrade.
|
|
6370
7130
|
`
|
|
6371
7131
|
)
|
|
6372
7132
|
);
|
|
6373
7133
|
return;
|
|
6374
7134
|
}
|
|
6375
7135
|
console.log(
|
|
6376
|
-
|
|
7136
|
+
chalk15.yellow(`
|
|
6377
7137
|
Update available: ${current} -> ${latest}`)
|
|
6378
7138
|
);
|
|
6379
7139
|
const shouldUpdate = await confirm2({ message: "Would you like to update now? (Y/n)", default: true });
|
|
@@ -6381,7 +7141,7 @@ Update available: ${current} -> ${latest}`)
|
|
|
6381
7141
|
console.log();
|
|
6382
7142
|
return;
|
|
6383
7143
|
}
|
|
6384
|
-
const spinner =
|
|
7144
|
+
const spinner = ora7("Updating caliber...").start();
|
|
6385
7145
|
try {
|
|
6386
7146
|
execSync9(`npm install -g @rely-ai/caliber@${latest}`, {
|
|
6387
7147
|
stdio: "pipe",
|
|
@@ -6391,13 +7151,13 @@ Update available: ${current} -> ${latest}`)
|
|
|
6391
7151
|
const installed = getInstalledVersion();
|
|
6392
7152
|
if (installed !== latest) {
|
|
6393
7153
|
spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
|
|
6394
|
-
console.log(
|
|
7154
|
+
console.log(chalk15.yellow(`Run ${chalk15.bold(`npm install -g @rely-ai/caliber@${latest}`)} manually.
|
|
6395
7155
|
`));
|
|
6396
7156
|
return;
|
|
6397
7157
|
}
|
|
6398
|
-
spinner.succeed(
|
|
7158
|
+
spinner.succeed(chalk15.green(`Updated to ${latest}`));
|
|
6399
7159
|
const args = process.argv.slice(2);
|
|
6400
|
-
console.log(
|
|
7160
|
+
console.log(chalk15.dim(`
|
|
6401
7161
|
Restarting: caliber ${args.join(" ")}
|
|
6402
7162
|
`));
|
|
6403
7163
|
execSync9(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
@@ -6410,11 +7170,11 @@ Restarting: caliber ${args.join(" ")}
|
|
|
6410
7170
|
if (err instanceof Error) {
|
|
6411
7171
|
const stderr = err.stderr;
|
|
6412
7172
|
const errMsg = stderr ? String(stderr).trim().split("\n").pop() : err.message.split("\n")[0];
|
|
6413
|
-
if (errMsg && !errMsg.includes("SIGTERM")) console.log(
|
|
7173
|
+
if (errMsg && !errMsg.includes("SIGTERM")) console.log(chalk15.dim(` ${errMsg}`));
|
|
6414
7174
|
}
|
|
6415
7175
|
console.log(
|
|
6416
|
-
|
|
6417
|
-
`Run ${
|
|
7176
|
+
chalk15.yellow(
|
|
7177
|
+
`Run ${chalk15.bold(`npm install -g @rely-ai/caliber@${latest}`)} manually to upgrade.
|
|
6418
7178
|
`
|
|
6419
7179
|
)
|
|
6420
7180
|
);
|