@rely-ai/caliber 1.4.2 → 1.5.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/README.md +1 -1
- package/dist/bin.js +1339 -1303
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -56,12 +56,12 @@ import path22 from "path";
|
|
|
56
56
|
import { fileURLToPath } from "url";
|
|
57
57
|
|
|
58
58
|
// src/commands/onboard.ts
|
|
59
|
-
import
|
|
60
|
-
import
|
|
59
|
+
import chalk7 from "chalk";
|
|
60
|
+
import ora3 from "ora";
|
|
61
61
|
import readline4 from "readline";
|
|
62
|
-
import
|
|
62
|
+
import select4 from "@inquirer/select";
|
|
63
63
|
import checkbox from "@inquirer/checkbox";
|
|
64
|
-
import
|
|
64
|
+
import fs22 from "fs";
|
|
65
65
|
|
|
66
66
|
// src/fingerprint/index.ts
|
|
67
67
|
import fs6 from "fs";
|
|
@@ -1242,11 +1242,23 @@ AgentSetup schema:
|
|
|
1242
1242
|
|
|
1243
1243
|
Do NOT generate mcpServers \u2014 MCP configuration is managed separately.
|
|
1244
1244
|
|
|
1245
|
-
All skills follow the OpenSkills standard (agentskills.io):
|
|
1246
|
-
-
|
|
1247
|
-
-
|
|
1248
|
-
-
|
|
1249
|
-
|
|
1245
|
+
All skills follow the OpenSkills standard (agentskills.io). Anthropic's official skill guide defines three levels of progressive disclosure:
|
|
1246
|
+
- Level 1 (YAML frontmatter): Always loaded. Must have enough info for the agent to decide when to activate the skill.
|
|
1247
|
+
- Level 2 (SKILL.md body): Loaded when the skill is relevant. Contains full instructions.
|
|
1248
|
+
- Level 3 (references/): Only loaded on demand for deep detail.
|
|
1249
|
+
|
|
1250
|
+
Skill field requirements:
|
|
1251
|
+
- "name": kebab-case (lowercase letters, numbers, hyphens only). Becomes the directory name.
|
|
1252
|
+
- "description": MUST include WHAT it does + WHEN to use it with specific trigger phrases. Example: "Manages database migrations. Use when user says 'run migration', 'create migration', 'db schema change', or modifies files in db/migrations/."
|
|
1253
|
+
- "content": markdown body only \u2014 do NOT include YAML frontmatter, it is generated from name+description.
|
|
1254
|
+
|
|
1255
|
+
Skill content structure \u2014 follow this template:
|
|
1256
|
+
1. A heading with the skill name
|
|
1257
|
+
2. "## Instructions" \u2014 clear, numbered steps. Be specific: include exact commands, file paths, parameter names.
|
|
1258
|
+
3. "## Examples" \u2014 at least one example showing: User says \u2192 Actions taken \u2192 Result
|
|
1259
|
+
4. "## Troubleshooting" (optional) \u2014 common errors and how to fix them
|
|
1260
|
+
|
|
1261
|
+
Keep skill content under 200 lines. Focus on actionable instructions, not documentation prose.
|
|
1250
1262
|
|
|
1251
1263
|
The "fileDescriptions" object MUST include a one-liner for every file that will be created or modified. Use actual file paths as keys (e.g. "CLAUDE.md", "AGENTS.md", ".claude/skills/my-skill/SKILL.md", ".agents/skills/my-skill/SKILL.md", ".cursor/skills/my-skill/SKILL.md", ".cursor/rules/my-rule.mdc"). Each description should explain why the change is needed, be concise and lowercase.
|
|
1252
1264
|
|
|
@@ -1457,13 +1469,13 @@ async function detectProjectStack(fileTree, fileContents) {
|
|
|
1457
1469
|
}
|
|
1458
1470
|
|
|
1459
1471
|
// src/fingerprint/index.ts
|
|
1460
|
-
function collectFingerprint(dir) {
|
|
1472
|
+
async function collectFingerprint(dir) {
|
|
1461
1473
|
const gitRemoteUrl = getGitRemoteUrl();
|
|
1462
1474
|
const fileTree = getFileTree(dir);
|
|
1463
1475
|
const existingConfigs = readExistingConfigs(dir);
|
|
1464
1476
|
const codeAnalysis = analyzeCode(dir);
|
|
1465
1477
|
const packageName = readPackageName(dir);
|
|
1466
|
-
|
|
1478
|
+
const fingerprint = {
|
|
1467
1479
|
gitRemoteUrl,
|
|
1468
1480
|
packageName,
|
|
1469
1481
|
languages: [],
|
|
@@ -1473,6 +1485,8 @@ function collectFingerprint(dir) {
|
|
|
1473
1485
|
existingConfigs,
|
|
1474
1486
|
codeAnalysis
|
|
1475
1487
|
};
|
|
1488
|
+
await enrichWithLLM(fingerprint, dir);
|
|
1489
|
+
return fingerprint;
|
|
1476
1490
|
}
|
|
1477
1491
|
function readPackageName(dir) {
|
|
1478
1492
|
try {
|
|
@@ -1498,7 +1512,7 @@ var DEP_FILE_PATTERNS = [
|
|
|
1498
1512
|
"composer.json"
|
|
1499
1513
|
];
|
|
1500
1514
|
var MAX_CONTENT_SIZE = 50 * 1024;
|
|
1501
|
-
async function
|
|
1515
|
+
async function enrichWithLLM(fingerprint, dir) {
|
|
1502
1516
|
try {
|
|
1503
1517
|
const config = loadConfig();
|
|
1504
1518
|
if (!config) return;
|
|
@@ -1520,21 +1534,9 @@ async function enrichFingerprintWithLLM(fingerprint, dir) {
|
|
|
1520
1534
|
}
|
|
1521
1535
|
if (Object.keys(fileContents).length === 0 && fingerprint.fileTree.length === 0) return;
|
|
1522
1536
|
const result = await detectProjectStack(fingerprint.fileTree, fileContents);
|
|
1523
|
-
if (result.languages?.length)
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
fingerprint.languages = [...langSet];
|
|
1527
|
-
}
|
|
1528
|
-
if (result.frameworks?.length) {
|
|
1529
|
-
const fwSet = new Set(fingerprint.frameworks);
|
|
1530
|
-
for (const fw of result.frameworks) fwSet.add(fw);
|
|
1531
|
-
fingerprint.frameworks = [...fwSet];
|
|
1532
|
-
}
|
|
1533
|
-
if (result.tools?.length) {
|
|
1534
|
-
const toolSet = new Set(fingerprint.tools);
|
|
1535
|
-
for (const tool of result.tools) toolSet.add(tool);
|
|
1536
|
-
fingerprint.tools = [...toolSet];
|
|
1537
|
-
}
|
|
1537
|
+
if (result.languages?.length) fingerprint.languages = result.languages;
|
|
1538
|
+
if (result.frameworks?.length) fingerprint.frameworks = result.frameworks;
|
|
1539
|
+
if (result.tools?.length) fingerprint.tools = result.tools;
|
|
1538
1540
|
} catch {
|
|
1539
1541
|
}
|
|
1540
1542
|
}
|
|
@@ -4632,702 +4634,846 @@ async function interactiveSelect(candidates) {
|
|
|
4632
4634
|
});
|
|
4633
4635
|
}
|
|
4634
4636
|
|
|
4635
|
-
// src/commands/
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
if (
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4637
|
+
// src/commands/recommend.ts
|
|
4638
|
+
import chalk6 from "chalk";
|
|
4639
|
+
import ora2 from "ora";
|
|
4640
|
+
import select3 from "@inquirer/select";
|
|
4641
|
+
import { mkdirSync, readFileSync as readFileSync7, readdirSync as readdirSync5, existsSync as existsSync9, writeFileSync } from "fs";
|
|
4642
|
+
import { join as join8, dirname as dirname2 } from "path";
|
|
4643
|
+
|
|
4644
|
+
// src/scanner/index.ts
|
|
4645
|
+
import fs21 from "fs";
|
|
4646
|
+
import path17 from "path";
|
|
4647
|
+
import crypto2 from "crypto";
|
|
4648
|
+
function scanLocalState(dir) {
|
|
4649
|
+
const items = [];
|
|
4650
|
+
const claudeMdPath = path17.join(dir, "CLAUDE.md");
|
|
4651
|
+
if (fs21.existsSync(claudeMdPath)) {
|
|
4652
|
+
items.push({
|
|
4653
|
+
type: "rule",
|
|
4654
|
+
platform: "claude",
|
|
4655
|
+
name: "CLAUDE.md",
|
|
4656
|
+
contentHash: hashFile(claudeMdPath),
|
|
4657
|
+
path: claudeMdPath
|
|
4658
|
+
});
|
|
4659
|
+
}
|
|
4660
|
+
const skillsDir = path17.join(dir, ".claude", "skills");
|
|
4661
|
+
if (fs21.existsSync(skillsDir)) {
|
|
4662
|
+
for (const file of fs21.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
4663
|
+
const filePath = path17.join(skillsDir, file);
|
|
4664
|
+
items.push({
|
|
4665
|
+
type: "skill",
|
|
4666
|
+
platform: "claude",
|
|
4667
|
+
name: file,
|
|
4668
|
+
contentHash: hashFile(filePath),
|
|
4669
|
+
path: filePath
|
|
4664
4670
|
});
|
|
4665
|
-
} catch (err) {
|
|
4666
|
-
if (err.message === "__exit__") throw err;
|
|
4667
|
-
throw err;
|
|
4668
|
-
}
|
|
4669
|
-
config = loadConfig();
|
|
4670
|
-
if (!config) {
|
|
4671
|
-
console.log(chalk6.red(" Setup was cancelled or failed.\n"));
|
|
4672
|
-
throw new Error("__exit__");
|
|
4673
4671
|
}
|
|
4674
|
-
console.log(chalk6.green(" \u2713 Provider saved. Let's continue.\n"));
|
|
4675
4672
|
}
|
|
4676
|
-
const
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
if (failingForDismissal.length > 0) {
|
|
4693
|
-
const newDismissals = await evaluateDismissals(failingForDismissal, fingerprint);
|
|
4694
|
-
if (newDismissals.length > 0) {
|
|
4695
|
-
const existing = readDismissedChecks();
|
|
4696
|
-
const existingIds = new Set(existing.map((d) => d.id));
|
|
4697
|
-
const merged = [...existing, ...newDismissals.filter((d) => !existingIds.has(d.id))];
|
|
4698
|
-
writeDismissedChecks(merged);
|
|
4673
|
+
const mcpJsonPath = path17.join(dir, ".mcp.json");
|
|
4674
|
+
if (fs21.existsSync(mcpJsonPath)) {
|
|
4675
|
+
try {
|
|
4676
|
+
const mcpJson = JSON.parse(fs21.readFileSync(mcpJsonPath, "utf-8"));
|
|
4677
|
+
if (mcpJson.mcpServers) {
|
|
4678
|
+
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
4679
|
+
items.push({
|
|
4680
|
+
type: "mcp",
|
|
4681
|
+
platform: "claude",
|
|
4682
|
+
name,
|
|
4683
|
+
contentHash: hashJson(mcpJson.mcpServers[name]),
|
|
4684
|
+
path: mcpJsonPath
|
|
4685
|
+
});
|
|
4686
|
+
}
|
|
4687
|
+
}
|
|
4688
|
+
} catch {
|
|
4699
4689
|
}
|
|
4700
4690
|
}
|
|
4701
|
-
const
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4691
|
+
const agentsMdPath = path17.join(dir, "AGENTS.md");
|
|
4692
|
+
if (fs21.existsSync(agentsMdPath)) {
|
|
4693
|
+
items.push({
|
|
4694
|
+
type: "rule",
|
|
4695
|
+
platform: "codex",
|
|
4696
|
+
name: "AGENTS.md",
|
|
4697
|
+
contentHash: hashFile(agentsMdPath),
|
|
4698
|
+
path: agentsMdPath
|
|
4699
|
+
});
|
|
4709
4700
|
}
|
|
4710
|
-
const
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4701
|
+
const codexSkillsDir = path17.join(dir, ".agents", "skills");
|
|
4702
|
+
if (fs21.existsSync(codexSkillsDir)) {
|
|
4703
|
+
try {
|
|
4704
|
+
for (const name of fs21.readdirSync(codexSkillsDir)) {
|
|
4705
|
+
const skillFile = path17.join(codexSkillsDir, name, "SKILL.md");
|
|
4706
|
+
if (fs21.existsSync(skillFile)) {
|
|
4707
|
+
items.push({
|
|
4708
|
+
type: "skill",
|
|
4709
|
+
platform: "codex",
|
|
4710
|
+
name: `${name}/SKILL.md`,
|
|
4711
|
+
contentHash: hashFile(skillFile),
|
|
4712
|
+
path: skillFile
|
|
4713
|
+
});
|
|
4714
|
+
}
|
|
4719
4715
|
}
|
|
4716
|
+
} catch {
|
|
4720
4717
|
}
|
|
4721
|
-
console.log("");
|
|
4722
|
-
console.log(chalk6.dim(" Run ") + chalk6.hex("#83D1EB")("caliber onboard --force") + chalk6.dim(" to regenerate anyway.\n"));
|
|
4723
|
-
return;
|
|
4724
4718
|
}
|
|
4725
|
-
const
|
|
4726
|
-
if (
|
|
4727
|
-
|
|
4719
|
+
const cursorrulesPath = path17.join(dir, ".cursorrules");
|
|
4720
|
+
if (fs21.existsSync(cursorrulesPath)) {
|
|
4721
|
+
items.push({
|
|
4722
|
+
type: "rule",
|
|
4723
|
+
platform: "cursor",
|
|
4724
|
+
name: ".cursorrules",
|
|
4725
|
+
contentHash: hashFile(cursorrulesPath),
|
|
4726
|
+
path: cursorrulesPath
|
|
4727
|
+
});
|
|
4728
4728
|
}
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
for (const check of failingChecks) {
|
|
4741
|
-
console.log(chalk6.dim(` \u2022 ${check.name}`));
|
|
4742
|
-
}
|
|
4743
|
-
console.log("");
|
|
4729
|
+
const cursorRulesDir = path17.join(dir, ".cursor", "rules");
|
|
4730
|
+
if (fs21.existsSync(cursorRulesDir)) {
|
|
4731
|
+
for (const file of fs21.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
4732
|
+
const filePath = path17.join(cursorRulesDir, file);
|
|
4733
|
+
items.push({
|
|
4734
|
+
type: "rule",
|
|
4735
|
+
platform: "cursor",
|
|
4736
|
+
name: file,
|
|
4737
|
+
contentHash: hashFile(filePath),
|
|
4738
|
+
path: filePath
|
|
4739
|
+
});
|
|
4744
4740
|
}
|
|
4745
|
-
} else if (hasExistingConfig) {
|
|
4746
|
-
console.log(title.bold(" Step 3/5 \u2014 Improve your setup\n"));
|
|
4747
|
-
console.log(chalk6.dim(" Reviewing your existing configs against your codebase"));
|
|
4748
|
-
console.log(chalk6.dim(" and preparing improvements.\n"));
|
|
4749
|
-
} else {
|
|
4750
|
-
console.log(title.bold(" Step 3/5 \u2014 Build your agent setup\n"));
|
|
4751
|
-
console.log(chalk6.dim(" Creating config files tailored to your project.\n"));
|
|
4752
4741
|
}
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
onStatus: (status) => {
|
|
4767
|
-
genMessages.handleServerStatus(status);
|
|
4768
|
-
},
|
|
4769
|
-
onComplete: (setup) => {
|
|
4770
|
-
generatedSetup = setup;
|
|
4771
|
-
},
|
|
4772
|
-
onError: (error) => {
|
|
4773
|
-
genMessages.stop();
|
|
4774
|
-
genSpinner.fail(`Generation error: ${error}`);
|
|
4742
|
+
const cursorSkillsDir = path17.join(dir, ".cursor", "skills");
|
|
4743
|
+
if (fs21.existsSync(cursorSkillsDir)) {
|
|
4744
|
+
try {
|
|
4745
|
+
for (const name of fs21.readdirSync(cursorSkillsDir)) {
|
|
4746
|
+
const skillFile = path17.join(cursorSkillsDir, name, "SKILL.md");
|
|
4747
|
+
if (fs21.existsSync(skillFile)) {
|
|
4748
|
+
items.push({
|
|
4749
|
+
type: "skill",
|
|
4750
|
+
platform: "cursor",
|
|
4751
|
+
name: `${name}/SKILL.md`,
|
|
4752
|
+
contentHash: hashFile(skillFile),
|
|
4753
|
+
path: skillFile
|
|
4754
|
+
});
|
|
4775
4755
|
}
|
|
4776
|
-
}
|
|
4777
|
-
|
|
4778
|
-
currentScore,
|
|
4779
|
-
passingChecks
|
|
4780
|
-
);
|
|
4781
|
-
if (!generatedSetup) {
|
|
4782
|
-
generatedSetup = result.setup;
|
|
4783
|
-
rawOutput = result.raw;
|
|
4784
|
-
}
|
|
4785
|
-
} catch (err) {
|
|
4786
|
-
genMessages.stop();
|
|
4787
|
-
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
4788
|
-
genSpinner.fail(`Generation failed: ${msg}`);
|
|
4789
|
-
throw new Error("__exit__");
|
|
4790
|
-
}
|
|
4791
|
-
genMessages.stop();
|
|
4792
|
-
if (!generatedSetup) {
|
|
4793
|
-
genSpinner.fail("Failed to generate setup.");
|
|
4794
|
-
if (rawOutput) {
|
|
4795
|
-
console.log(chalk6.dim("\nRaw LLM output (JSON parse failed):"));
|
|
4796
|
-
console.log(chalk6.dim(rawOutput.slice(0, 500)));
|
|
4756
|
+
}
|
|
4757
|
+
} catch {
|
|
4797
4758
|
}
|
|
4798
|
-
throw new Error("__exit__");
|
|
4799
4759
|
}
|
|
4800
|
-
const
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
`));
|
|
4817
|
-
let action;
|
|
4818
|
-
if (totalChanges === 0) {
|
|
4819
|
-
console.log(chalk6.dim(" No changes needed \u2014 your configs are already up to date.\n"));
|
|
4820
|
-
cleanupStaging();
|
|
4821
|
-
action = "accept";
|
|
4822
|
-
} else {
|
|
4823
|
-
const wantsReview = await promptWantsReview();
|
|
4824
|
-
if (wantsReview) {
|
|
4825
|
-
const reviewMethod = await promptReviewMethod();
|
|
4826
|
-
await openReview(reviewMethod, staged.stagedFiles);
|
|
4760
|
+
const cursorMcpPath = path17.join(dir, ".cursor", "mcp.json");
|
|
4761
|
+
if (fs21.existsSync(cursorMcpPath)) {
|
|
4762
|
+
try {
|
|
4763
|
+
const mcpJson = JSON.parse(fs21.readFileSync(cursorMcpPath, "utf-8"));
|
|
4764
|
+
if (mcpJson.mcpServers) {
|
|
4765
|
+
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
4766
|
+
items.push({
|
|
4767
|
+
type: "mcp",
|
|
4768
|
+
platform: "cursor",
|
|
4769
|
+
name,
|
|
4770
|
+
contentHash: hashJson(mcpJson.mcpServers[name]),
|
|
4771
|
+
path: cursorMcpPath
|
|
4772
|
+
});
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
} catch {
|
|
4827
4776
|
}
|
|
4828
|
-
action = await promptReviewAction();
|
|
4829
4777
|
}
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
if (action === "decline") {
|
|
4847
|
-
console.log(chalk6.dim("Setup declined. No files were modified."));
|
|
4848
|
-
return;
|
|
4778
|
+
return items;
|
|
4779
|
+
}
|
|
4780
|
+
function hashFile(filePath) {
|
|
4781
|
+
const text = fs21.readFileSync(filePath, "utf-8");
|
|
4782
|
+
return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
|
|
4783
|
+
}
|
|
4784
|
+
function hashJson(obj) {
|
|
4785
|
+
return crypto2.createHash("sha256").update(JSON.stringify(obj)).digest("hex");
|
|
4786
|
+
}
|
|
4787
|
+
|
|
4788
|
+
// src/commands/recommend.ts
|
|
4789
|
+
function detectLocalPlatforms() {
|
|
4790
|
+
const items = scanLocalState(process.cwd());
|
|
4791
|
+
const platforms = /* @__PURE__ */ new Set();
|
|
4792
|
+
for (const item of items) {
|
|
4793
|
+
platforms.add(item.platform);
|
|
4849
4794
|
}
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4795
|
+
return platforms.size > 0 ? Array.from(platforms) : ["claude"];
|
|
4796
|
+
}
|
|
4797
|
+
function getSkillPath(platform, slug) {
|
|
4798
|
+
if (platform === "cursor") {
|
|
4799
|
+
return join8(".cursor", "skills", slug, "SKILL.md");
|
|
4854
4800
|
}
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
const result = writeSetup(generatedSetup);
|
|
4858
|
-
writeSpinner.succeed("Config files written");
|
|
4859
|
-
console.log(chalk6.bold("\nFiles created/updated:"));
|
|
4860
|
-
for (const file of result.written) {
|
|
4861
|
-
console.log(` ${chalk6.green("\u2713")} ${file}`);
|
|
4862
|
-
}
|
|
4863
|
-
if (result.deleted.length > 0) {
|
|
4864
|
-
console.log(chalk6.bold("\nFiles removed:"));
|
|
4865
|
-
for (const file of result.deleted) {
|
|
4866
|
-
console.log(` ${chalk6.red("\u2717")} ${file}`);
|
|
4867
|
-
}
|
|
4868
|
-
}
|
|
4869
|
-
if (result.backupDir) {
|
|
4870
|
-
console.log(chalk6.dim(`
|
|
4871
|
-
Backups saved to ${result.backupDir}`));
|
|
4872
|
-
}
|
|
4873
|
-
} catch (err) {
|
|
4874
|
-
writeSpinner.fail("Failed to write files");
|
|
4875
|
-
console.error(chalk6.red(err instanceof Error ? err.message : "Unknown error"));
|
|
4876
|
-
throw new Error("__exit__");
|
|
4801
|
+
if (platform === "codex") {
|
|
4802
|
+
return join8(".agents", "skills", slug, "SKILL.md");
|
|
4877
4803
|
}
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4804
|
+
return join8(".claude", "skills", slug, "SKILL.md");
|
|
4805
|
+
}
|
|
4806
|
+
function getInstalledSkills() {
|
|
4807
|
+
const installed = /* @__PURE__ */ new Set();
|
|
4808
|
+
const dirs = [
|
|
4809
|
+
join8(process.cwd(), ".claude", "skills"),
|
|
4810
|
+
join8(process.cwd(), ".cursor", "skills"),
|
|
4811
|
+
join8(process.cwd(), ".agents", "skills")
|
|
4812
|
+
];
|
|
4813
|
+
for (const dir of dirs) {
|
|
4882
4814
|
try {
|
|
4883
|
-
const
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
for (const name of mcpResult.names) {
|
|
4888
|
-
console.log(` ${chalk6.green("\u2713")} ${name}`);
|
|
4815
|
+
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
4816
|
+
for (const entry of entries) {
|
|
4817
|
+
if (entry.isDirectory()) {
|
|
4818
|
+
installed.add(entry.name.toLowerCase());
|
|
4889
4819
|
}
|
|
4890
4820
|
}
|
|
4891
|
-
} catch
|
|
4892
|
-
console.log(chalk6.dim(" MCP discovery skipped: " + (err instanceof Error ? err.message : "unknown error")));
|
|
4893
|
-
}
|
|
4894
|
-
} else {
|
|
4895
|
-
console.log(chalk6.dim(" No external tools or services detected \u2014 skipping MCP discovery.\n"));
|
|
4896
|
-
}
|
|
4897
|
-
ensurePermissions();
|
|
4898
|
-
const sha = getCurrentHeadSha();
|
|
4899
|
-
writeState({
|
|
4900
|
-
lastRefreshSha: sha ?? "",
|
|
4901
|
-
lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4902
|
-
targetAgent
|
|
4903
|
-
});
|
|
4904
|
-
console.log("");
|
|
4905
|
-
console.log(title.bold(" Keep your configs fresh\n"));
|
|
4906
|
-
console.log(chalk6.dim(" Caliber can automatically update your agent configs when your code changes.\n"));
|
|
4907
|
-
const hookChoice = await promptHookType(targetAgent);
|
|
4908
|
-
if (hookChoice === "claude" || hookChoice === "both") {
|
|
4909
|
-
const hookResult = installHook();
|
|
4910
|
-
if (hookResult.installed) {
|
|
4911
|
-
console.log(` ${chalk6.green("\u2713")} Claude Code hook installed \u2014 docs update on session end`);
|
|
4912
|
-
console.log(chalk6.dim(" Run ") + chalk6.hex("#83D1EB")("caliber hooks --remove") + chalk6.dim(" to disable"));
|
|
4913
|
-
} else if (hookResult.alreadyInstalled) {
|
|
4914
|
-
console.log(chalk6.dim(" Claude Code hook already installed"));
|
|
4915
|
-
}
|
|
4916
|
-
const learnResult = installLearningHooks();
|
|
4917
|
-
if (learnResult.installed) {
|
|
4918
|
-
console.log(` ${chalk6.green("\u2713")} Learning hooks installed \u2014 session insights captured automatically`);
|
|
4919
|
-
console.log(chalk6.dim(" Run ") + chalk6.hex("#83D1EB")("caliber learn remove") + chalk6.dim(" to disable"));
|
|
4920
|
-
} else if (learnResult.alreadyInstalled) {
|
|
4921
|
-
console.log(chalk6.dim(" Learning hooks already installed"));
|
|
4922
|
-
}
|
|
4923
|
-
}
|
|
4924
|
-
if (hookChoice === "precommit" || hookChoice === "both") {
|
|
4925
|
-
const precommitResult = installPreCommitHook();
|
|
4926
|
-
if (precommitResult.installed) {
|
|
4927
|
-
console.log(` ${chalk6.green("\u2713")} Pre-commit hook installed \u2014 docs refresh before each commit`);
|
|
4928
|
-
console.log(chalk6.dim(" Run ") + chalk6.hex("#83D1EB")("caliber hooks --remove") + chalk6.dim(" to disable"));
|
|
4929
|
-
} else if (precommitResult.alreadyInstalled) {
|
|
4930
|
-
console.log(chalk6.dim(" Pre-commit hook already installed"));
|
|
4931
|
-
} else {
|
|
4932
|
-
console.log(chalk6.yellow(" Could not install pre-commit hook (not a git repository?)"));
|
|
4821
|
+
} catch {
|
|
4933
4822
|
}
|
|
4934
4823
|
}
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
const
|
|
4939
|
-
|
|
4940
|
-
console.log("");
|
|
4941
|
-
console.log(chalk6.yellow(` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`));
|
|
4824
|
+
return installed;
|
|
4825
|
+
}
|
|
4826
|
+
async function searchSkillsSh(technologies) {
|
|
4827
|
+
const bestBySlug = /* @__PURE__ */ new Map();
|
|
4828
|
+
for (const tech of technologies) {
|
|
4942
4829
|
try {
|
|
4943
|
-
const
|
|
4944
|
-
|
|
4945
|
-
|
|
4830
|
+
const resp = await fetch(`https://skills.sh/api/search?q=${encodeURIComponent(tech)}&limit=10`, {
|
|
4831
|
+
signal: AbortSignal.timeout(1e4)
|
|
4832
|
+
});
|
|
4833
|
+
if (!resp.ok) continue;
|
|
4834
|
+
const data = await resp.json();
|
|
4835
|
+
if (!data.skills?.length) continue;
|
|
4836
|
+
for (const skill of data.skills) {
|
|
4837
|
+
const existing = bestBySlug.get(skill.skillId);
|
|
4838
|
+
if (existing && existing.installs >= (skill.installs ?? 0)) continue;
|
|
4839
|
+
bestBySlug.set(skill.skillId, {
|
|
4840
|
+
name: skill.name,
|
|
4841
|
+
slug: skill.skillId,
|
|
4842
|
+
source_url: skill.source ? `https://github.com/${skill.source}` : "",
|
|
4843
|
+
score: 0,
|
|
4844
|
+
reason: skill.description || "",
|
|
4845
|
+
detected_technology: tech,
|
|
4846
|
+
item_type: "skill",
|
|
4847
|
+
installs: skill.installs ?? 0
|
|
4848
|
+
});
|
|
4946
4849
|
}
|
|
4947
4850
|
} catch {
|
|
4851
|
+
continue;
|
|
4948
4852
|
}
|
|
4949
|
-
console.log(chalk6.dim(" Run ") + chalk6.hex("#83D1EB")("caliber onboard --force") + chalk6.dim(" to override.\n"));
|
|
4950
|
-
return;
|
|
4951
4853
|
}
|
|
4952
|
-
|
|
4953
|
-
console.log(chalk6.bold.green(" Onboarding complete! Your project is ready for AI-assisted development."));
|
|
4954
|
-
console.log(chalk6.dim(" Run ") + chalk6.hex("#83D1EB")("caliber undo") + chalk6.dim(" to revert changes.\n"));
|
|
4955
|
-
console.log(chalk6.bold(" Next steps:\n"));
|
|
4956
|
-
console.log(` ${title("caliber score")} See your full config breakdown`);
|
|
4957
|
-
console.log(` ${title("caliber recommend")} Discover community skills for your stack`);
|
|
4958
|
-
console.log(` ${title("caliber undo")} Revert all changes from this run`);
|
|
4959
|
-
console.log("");
|
|
4854
|
+
return Array.from(bestBySlug.values());
|
|
4960
4855
|
}
|
|
4961
|
-
async function
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
return null;
|
|
4969
|
-
}
|
|
4970
|
-
const isValid = await classifyRefineIntent(message);
|
|
4971
|
-
if (!isValid) {
|
|
4972
|
-
console.log(chalk6.dim(" This doesn't look like a config change request."));
|
|
4973
|
-
console.log(chalk6.dim(" Describe what to add, remove, or modify in your configs."));
|
|
4974
|
-
console.log(chalk6.dim(' Type "done" to accept the current setup.\n'));
|
|
4975
|
-
continue;
|
|
4976
|
-
}
|
|
4977
|
-
const refineSpinner = ora2("Refining setup...").start();
|
|
4978
|
-
const refineMessages = new SpinnerMessages(refineSpinner, REFINE_MESSAGES);
|
|
4979
|
-
refineMessages.start();
|
|
4980
|
-
const refined = await refineSetup(
|
|
4981
|
-
currentSetup,
|
|
4982
|
-
message,
|
|
4983
|
-
sessionHistory
|
|
4984
|
-
);
|
|
4985
|
-
refineMessages.stop();
|
|
4986
|
-
if (refined) {
|
|
4987
|
-
currentSetup = refined;
|
|
4988
|
-
sessionHistory.push({ role: "user", content: message });
|
|
4989
|
-
sessionHistory.push({
|
|
4990
|
-
role: "assistant",
|
|
4991
|
-
content: summarizeSetup("Applied changes", refined)
|
|
4856
|
+
async function searchTessl(technologies) {
|
|
4857
|
+
const results = [];
|
|
4858
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4859
|
+
for (const tech of technologies) {
|
|
4860
|
+
try {
|
|
4861
|
+
const resp = await fetch(`https://tessl.io/registry?q=${encodeURIComponent(tech)}`, {
|
|
4862
|
+
signal: AbortSignal.timeout(1e4)
|
|
4992
4863
|
});
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4864
|
+
if (!resp.ok) continue;
|
|
4865
|
+
const html = await resp.text();
|
|
4866
|
+
const linkMatches = html.matchAll(/\/registry\/skills\/github\/([^/]+)\/([^/]+)\/([^/"]+)/g);
|
|
4867
|
+
for (const match of linkMatches) {
|
|
4868
|
+
const [, org, repo, skillName] = match;
|
|
4869
|
+
const slug = `${org}-${repo}-${skillName}`.toLowerCase();
|
|
4870
|
+
if (seen.has(slug)) continue;
|
|
4871
|
+
seen.add(slug);
|
|
4872
|
+
results.push({
|
|
4873
|
+
name: skillName,
|
|
4874
|
+
slug,
|
|
4875
|
+
source_url: `https://github.com/${org}/${repo}`,
|
|
4876
|
+
score: 0,
|
|
4877
|
+
reason: `Skill from ${org}/${repo}`,
|
|
4878
|
+
detected_technology: tech,
|
|
4879
|
+
item_type: "skill"
|
|
4880
|
+
});
|
|
4881
|
+
}
|
|
4882
|
+
} catch {
|
|
4883
|
+
continue;
|
|
4999
4884
|
}
|
|
5000
4885
|
}
|
|
4886
|
+
return results;
|
|
5001
4887
|
}
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
const files = descriptions ? Object.entries(descriptions).map(([path24, desc]) => ` ${path24}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
|
|
5005
|
-
return `${action}. Files:
|
|
5006
|
-
${files}`;
|
|
5007
|
-
}
|
|
5008
|
-
async function classifyRefineIntent(message) {
|
|
5009
|
-
const fastModel = getFastModel();
|
|
4888
|
+
var AWESOME_CLAUDE_CODE_URL = "https://raw.githubusercontent.com/hesreallyhim/awesome-claude-code/main/README.md";
|
|
4889
|
+
async function searchAwesomeClaudeCode(technologies) {
|
|
5010
4890
|
try {
|
|
5011
|
-
const
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
4891
|
+
const resp = await fetch(AWESOME_CLAUDE_CODE_URL, {
|
|
4892
|
+
signal: AbortSignal.timeout(1e4)
|
|
4893
|
+
});
|
|
4894
|
+
if (!resp.ok) return [];
|
|
4895
|
+
const markdown = await resp.text();
|
|
4896
|
+
const items = [];
|
|
4897
|
+
const itemPattern = /^[-*]\s+\[([^\]]+)\]\(([^)]+)\)(?:\s+by\s+\[[^\]]*\]\([^)]*\))?\s*[-–—:]\s*(.*)/gm;
|
|
4898
|
+
let match;
|
|
4899
|
+
while ((match = itemPattern.exec(markdown)) !== null) {
|
|
4900
|
+
const [, name, url, description] = match;
|
|
4901
|
+
if (url.startsWith("#")) continue;
|
|
4902
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4903
|
+
items.push({
|
|
4904
|
+
name: name.trim(),
|
|
4905
|
+
slug,
|
|
4906
|
+
source_url: url.trim(),
|
|
4907
|
+
score: 0,
|
|
4908
|
+
reason: description.trim().slice(0, 150),
|
|
4909
|
+
detected_technology: "claude-code",
|
|
4910
|
+
item_type: "skill"
|
|
4911
|
+
});
|
|
4912
|
+
}
|
|
4913
|
+
const techLower = technologies.map((t) => t.toLowerCase());
|
|
4914
|
+
return items.filter((item) => {
|
|
4915
|
+
const text = `${item.name} ${item.reason}`.toLowerCase();
|
|
4916
|
+
return techLower.some((t) => text.includes(t));
|
|
5019
4917
|
});
|
|
5020
|
-
return result.valid === true;
|
|
5021
4918
|
} catch {
|
|
5022
|
-
return
|
|
4919
|
+
return [];
|
|
5023
4920
|
}
|
|
5024
4921
|
}
|
|
5025
|
-
async function
|
|
5026
|
-
const
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
4922
|
+
async function searchAllProviders(technologies, platform) {
|
|
4923
|
+
const searches = [
|
|
4924
|
+
searchSkillsSh(technologies),
|
|
4925
|
+
searchTessl(technologies)
|
|
4926
|
+
];
|
|
4927
|
+
if (platform === "claude" || !platform) {
|
|
4928
|
+
searches.push(searchAwesomeClaudeCode(technologies));
|
|
4929
|
+
}
|
|
4930
|
+
const results = await Promise.all(searches);
|
|
4931
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4932
|
+
const combined = [];
|
|
4933
|
+
for (const batch of results) {
|
|
4934
|
+
for (const result of batch) {
|
|
4935
|
+
const key = result.name.toLowerCase().replace(/[-_]/g, "");
|
|
4936
|
+
if (seen.has(key)) continue;
|
|
4937
|
+
seen.add(key);
|
|
4938
|
+
combined.push(result);
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
return combined;
|
|
4942
|
+
}
|
|
4943
|
+
async function scoreWithLLM2(candidates, projectContext, technologies) {
|
|
4944
|
+
const candidateList = candidates.map((c, i) => `${i}. "${c.name}" \u2014 ${c.reason || "no description"}`).join("\n");
|
|
4945
|
+
const scored = await llmJsonCall({
|
|
4946
|
+
system: `You evaluate whether AI agent skills and tools are relevant to a specific software project.
|
|
4947
|
+
Given a project context and a list of candidates, score each one's relevance from 0-100 and provide a brief reason (max 80 chars).
|
|
5036
4948
|
|
|
5037
|
-
|
|
5038
|
-
|
|
4949
|
+
Return a JSON array where each element has:
|
|
4950
|
+
- "index": the candidate's index number
|
|
4951
|
+
- "score": relevance score 0-100
|
|
4952
|
+
- "reason": one-liner explaining why it fits or doesn't
|
|
5039
4953
|
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
4954
|
+
Scoring guidelines:
|
|
4955
|
+
- 90-100: Directly matches a core technology or workflow in the project
|
|
4956
|
+
- 70-89: Relevant to the project's stack, patterns, or development workflow
|
|
4957
|
+
- 50-69: Tangentially related or generic but useful
|
|
4958
|
+
- 0-49: Not relevant to this project
|
|
5043
4959
|
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
return new Promise((resolve2) => {
|
|
5058
|
-
rl.question(chalk6.cyan(`${question} `), (answer) => {
|
|
5059
|
-
rl.close();
|
|
5060
|
-
resolve2(answer.trim());
|
|
5061
|
-
});
|
|
5062
|
-
});
|
|
5063
|
-
}
|
|
5064
|
-
async function promptAgent() {
|
|
5065
|
-
const selected = await checkbox({
|
|
5066
|
-
message: "Which coding agents do you use? (toggle with space)",
|
|
5067
|
-
choices: [
|
|
5068
|
-
{ name: "Claude Code", value: "claude" },
|
|
5069
|
-
{ name: "Cursor", value: "cursor" },
|
|
5070
|
-
{ name: "Codex (OpenAI)", value: "codex" }
|
|
5071
|
-
],
|
|
5072
|
-
validate: (items) => {
|
|
5073
|
-
if (items.length === 0) return "At least one agent must be selected";
|
|
5074
|
-
return true;
|
|
5075
|
-
}
|
|
4960
|
+
Be selective. Prefer specific, high-quality matches over generic ones.
|
|
4961
|
+
A skill for "React testing" is only relevant if the project uses React.
|
|
4962
|
+
A generic "TypeScript best practices" skill is less valuable than one targeting the project's actual framework.
|
|
4963
|
+
Return ONLY the JSON array.`,
|
|
4964
|
+
prompt: `PROJECT CONTEXT:
|
|
4965
|
+
${projectContext}
|
|
4966
|
+
|
|
4967
|
+
DETECTED TECHNOLOGIES:
|
|
4968
|
+
${technologies.join(", ")}
|
|
4969
|
+
|
|
4970
|
+
CANDIDATES:
|
|
4971
|
+
${candidateList}`,
|
|
4972
|
+
maxTokens: 8e3
|
|
5076
4973
|
});
|
|
5077
|
-
return
|
|
4974
|
+
if (!Array.isArray(scored)) return [];
|
|
4975
|
+
return scored.filter((s) => s.score >= 60 && s.index >= 0 && s.index < candidates.length).sort((a, b) => b.score - a.score).slice(0, 20).map((s) => ({
|
|
4976
|
+
...candidates[s.index],
|
|
4977
|
+
score: s.score,
|
|
4978
|
+
reason: s.reason || candidates[s.index].reason
|
|
4979
|
+
}));
|
|
5078
4980
|
}
|
|
5079
|
-
|
|
5080
|
-
const
|
|
5081
|
-
|
|
5082
|
-
if (
|
|
5083
|
-
|
|
4981
|
+
function buildProjectContext(fingerprint) {
|
|
4982
|
+
const parts = [];
|
|
4983
|
+
if (fingerprint.packageName) parts.push(`Package: ${fingerprint.packageName}`);
|
|
4984
|
+
if (fingerprint.languages.length > 0) parts.push(`Languages: ${fingerprint.languages.join(", ")}`);
|
|
4985
|
+
if (fingerprint.frameworks.length > 0) parts.push(`Frameworks: ${fingerprint.frameworks.join(", ")}`);
|
|
4986
|
+
if (fingerprint.description) parts.push(`Description: ${fingerprint.description}`);
|
|
4987
|
+
if (fingerprint.fileTree.length > 0) {
|
|
4988
|
+
parts.push(`
|
|
4989
|
+
File tree (${fingerprint.fileTree.length} files):
|
|
4990
|
+
${fingerprint.fileTree.slice(0, 50).join("\n")}`);
|
|
5084
4991
|
}
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
4992
|
+
if (fingerprint.existingConfigs.claudeMd) {
|
|
4993
|
+
parts.push(`
|
|
4994
|
+
Existing CLAUDE.md (first 500 chars):
|
|
4995
|
+
${fingerprint.existingConfigs.claudeMd.slice(0, 500)}`);
|
|
5088
4996
|
}
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
}
|
|
4997
|
+
const deps = extractTopDeps();
|
|
4998
|
+
if (deps.length > 0) {
|
|
4999
|
+
parts.push(`
|
|
5000
|
+
Dependencies: ${deps.slice(0, 30).join(", ")}`);
|
|
5001
|
+
}
|
|
5002
|
+
const installed = getInstalledSkills();
|
|
5003
|
+
if (installed.size > 0) {
|
|
5004
|
+
parts.push(`
|
|
5005
|
+
Already installed skills: ${Array.from(installed).join(", ")}`);
|
|
5006
|
+
}
|
|
5007
|
+
return parts.join("\n");
|
|
5094
5008
|
}
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5009
|
+
function extractTopDeps() {
|
|
5010
|
+
const pkgPath = join8(process.cwd(), "package.json");
|
|
5011
|
+
if (!existsSync9(pkgPath)) return [];
|
|
5012
|
+
try {
|
|
5013
|
+
const pkg3 = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
5014
|
+
const deps = Object.keys(pkg3.dependencies ?? {});
|
|
5015
|
+
const trivial = /* @__PURE__ */ new Set([
|
|
5016
|
+
"typescript",
|
|
5017
|
+
"tslib",
|
|
5018
|
+
"ts-node",
|
|
5019
|
+
"tsx",
|
|
5020
|
+
"prettier",
|
|
5021
|
+
"eslint",
|
|
5022
|
+
"@eslint/js",
|
|
5023
|
+
"rimraf",
|
|
5024
|
+
"cross-env",
|
|
5025
|
+
"dotenv",
|
|
5026
|
+
"nodemon",
|
|
5027
|
+
"husky",
|
|
5028
|
+
"lint-staged",
|
|
5029
|
+
"commitlint",
|
|
5030
|
+
"chalk",
|
|
5031
|
+
"ora",
|
|
5032
|
+
"commander",
|
|
5033
|
+
"yargs",
|
|
5034
|
+
"meow",
|
|
5035
|
+
"inquirer",
|
|
5036
|
+
"@inquirer/confirm",
|
|
5037
|
+
"@inquirer/select",
|
|
5038
|
+
"@inquirer/prompts",
|
|
5039
|
+
"glob",
|
|
5040
|
+
"minimatch",
|
|
5041
|
+
"micromatch",
|
|
5042
|
+
"diff",
|
|
5043
|
+
"semver",
|
|
5044
|
+
"uuid",
|
|
5045
|
+
"nanoid",
|
|
5046
|
+
"debug",
|
|
5047
|
+
"ms",
|
|
5048
|
+
"lodash",
|
|
5049
|
+
"underscore",
|
|
5050
|
+
"tsup",
|
|
5051
|
+
"esbuild",
|
|
5052
|
+
"rollup",
|
|
5053
|
+
"webpack",
|
|
5054
|
+
"vite",
|
|
5055
|
+
"vitest",
|
|
5056
|
+
"jest",
|
|
5057
|
+
"mocha",
|
|
5058
|
+
"chai",
|
|
5059
|
+
"ava",
|
|
5060
|
+
"fs-extra",
|
|
5061
|
+
"mkdirp",
|
|
5062
|
+
"del",
|
|
5063
|
+
"rimraf",
|
|
5064
|
+
"path-to-regexp",
|
|
5065
|
+
"strip-ansi",
|
|
5066
|
+
"ansi-colors"
|
|
5067
|
+
]);
|
|
5068
|
+
const trivialPatterns = [
|
|
5069
|
+
/^@types\//,
|
|
5070
|
+
/^@rely-ai\//,
|
|
5071
|
+
/^@caliber-ai\//,
|
|
5072
|
+
/^eslint-/,
|
|
5073
|
+
/^@eslint\//,
|
|
5074
|
+
/^prettier-/,
|
|
5075
|
+
/^@typescript-eslint\//,
|
|
5076
|
+
/^@commitlint\//
|
|
5077
|
+
];
|
|
5078
|
+
return deps.filter(
|
|
5079
|
+
(d) => !trivial.has(d) && !trivialPatterns.some((p) => p.test(d))
|
|
5080
|
+
);
|
|
5081
|
+
} catch {
|
|
5082
|
+
return [];
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
5085
|
+
async function recommendCommand() {
|
|
5086
|
+
const proceed = await select3({
|
|
5087
|
+
message: "Search public repos for relevant skills to add to this project?",
|
|
5098
5088
|
choices: [
|
|
5099
|
-
{ name: "
|
|
5100
|
-
{ name: "
|
|
5101
|
-
{ name: "Decline", value: "decline" }
|
|
5089
|
+
{ name: "Yes, find skills for my project", value: true },
|
|
5090
|
+
{ name: "No, cancel", value: false }
|
|
5102
5091
|
]
|
|
5103
5092
|
});
|
|
5093
|
+
if (!proceed) {
|
|
5094
|
+
console.log(chalk6.dim(" Cancelled.\n"));
|
|
5095
|
+
return;
|
|
5096
|
+
}
|
|
5097
|
+
await searchAndInstallSkills();
|
|
5104
5098
|
}
|
|
5105
|
-
function
|
|
5106
|
-
const
|
|
5107
|
-
const
|
|
5108
|
-
const
|
|
5109
|
-
const
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5099
|
+
async function searchAndInstallSkills() {
|
|
5100
|
+
const fingerprint = await collectFingerprint(process.cwd());
|
|
5101
|
+
const platforms = detectLocalPlatforms();
|
|
5102
|
+
const installedSkills = getInstalledSkills();
|
|
5103
|
+
const technologies = [...new Set([
|
|
5104
|
+
...fingerprint.languages,
|
|
5105
|
+
...fingerprint.frameworks,
|
|
5106
|
+
...extractTopDeps()
|
|
5107
|
+
].filter(Boolean))];
|
|
5108
|
+
if (technologies.length === 0) {
|
|
5109
|
+
console.log(chalk6.yellow("Could not detect any languages or dependencies. Try running from a project root."));
|
|
5110
|
+
throw new Error("__exit__");
|
|
5111
|
+
}
|
|
5112
|
+
const primaryPlatform = platforms.includes("claude") ? "claude" : platforms[0];
|
|
5113
|
+
const searchSpinner = ora2("Searching skill registries...").start();
|
|
5114
|
+
const allCandidates = await searchAllProviders(technologies, primaryPlatform);
|
|
5115
|
+
if (!allCandidates.length) {
|
|
5116
|
+
searchSpinner.succeed("No skills found matching your tech stack.");
|
|
5117
|
+
return;
|
|
5118
|
+
}
|
|
5119
|
+
const newCandidates = allCandidates.filter((c) => !installedSkills.has(c.slug.toLowerCase()));
|
|
5120
|
+
const filteredCount = allCandidates.length - newCandidates.length;
|
|
5121
|
+
if (!newCandidates.length) {
|
|
5122
|
+
searchSpinner.succeed(`Found ${allCandidates.length} skills \u2014 all already installed.`);
|
|
5123
|
+
return;
|
|
5124
|
+
}
|
|
5125
|
+
searchSpinner.succeed(
|
|
5126
|
+
`Found ${allCandidates.length} skills` + (filteredCount > 0 ? chalk6.dim(` (${filteredCount} already installed)`) : "")
|
|
5127
|
+
);
|
|
5128
|
+
let results;
|
|
5129
|
+
const config = loadConfig();
|
|
5130
|
+
if (config) {
|
|
5131
|
+
const scoreSpinner = ora2("Scoring relevance for your project...").start();
|
|
5132
|
+
try {
|
|
5133
|
+
const projectContext = buildProjectContext(fingerprint);
|
|
5134
|
+
results = await scoreWithLLM2(newCandidates, projectContext, technologies);
|
|
5135
|
+
if (results.length === 0) {
|
|
5136
|
+
scoreSpinner.succeed("No highly relevant skills found for your specific project.");
|
|
5137
|
+
return;
|
|
5132
5138
|
}
|
|
5139
|
+
scoreSpinner.succeed(`${results.length} relevant skill${results.length > 1 ? "s" : ""} for your project`);
|
|
5140
|
+
} catch {
|
|
5141
|
+
scoreSpinner.warn("Could not score relevance \u2014 showing top results");
|
|
5142
|
+
results = newCandidates.slice(0, 20);
|
|
5133
5143
|
}
|
|
5144
|
+
} else {
|
|
5145
|
+
results = newCandidates.slice(0, 20);
|
|
5134
5146
|
}
|
|
5135
|
-
const
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5147
|
+
const fetchSpinner = ora2("Verifying skill availability...").start();
|
|
5148
|
+
const contentMap = /* @__PURE__ */ new Map();
|
|
5149
|
+
await Promise.all(results.map(async (rec) => {
|
|
5150
|
+
const content = await fetchSkillContent(rec);
|
|
5151
|
+
if (content) contentMap.set(rec.slug, content);
|
|
5152
|
+
}));
|
|
5153
|
+
const available = results.filter((r) => contentMap.has(r.slug));
|
|
5154
|
+
if (!available.length) {
|
|
5155
|
+
fetchSpinner.fail("No installable skills found \u2014 content could not be fetched.");
|
|
5156
|
+
return;
|
|
5157
|
+
}
|
|
5158
|
+
const unavailableCount = results.length - available.length;
|
|
5159
|
+
fetchSpinner.succeed(
|
|
5160
|
+
`${available.length} installable skill${available.length > 1 ? "s" : ""}` + (unavailableCount > 0 ? chalk6.dim(` (${unavailableCount} unavailable)`) : "")
|
|
5161
|
+
);
|
|
5162
|
+
const selected = await interactiveSelect2(available);
|
|
5163
|
+
if (selected?.length) {
|
|
5164
|
+
await installSkills(selected, platforms, contentMap);
|
|
5165
|
+
}
|
|
5166
|
+
}
|
|
5167
|
+
async function interactiveSelect2(recs) {
|
|
5168
|
+
if (!process.stdin.isTTY) {
|
|
5169
|
+
printSkills(recs);
|
|
5170
|
+
return null;
|
|
5171
|
+
}
|
|
5172
|
+
const selected = /* @__PURE__ */ new Set();
|
|
5173
|
+
let cursor = 0;
|
|
5174
|
+
const { stdin, stdout } = process;
|
|
5175
|
+
let lineCount = 0;
|
|
5176
|
+
const hasScores = recs.some((r) => r.score > 0);
|
|
5177
|
+
function render() {
|
|
5178
|
+
const lines = [];
|
|
5179
|
+
lines.push(chalk6.bold(" Skills"));
|
|
5180
|
+
lines.push("");
|
|
5181
|
+
if (hasScores) {
|
|
5182
|
+
lines.push(` ${chalk6.dim("Score".padEnd(7))} ${chalk6.dim("Name".padEnd(28))} ${chalk6.dim("Why")}`);
|
|
5183
|
+
} else {
|
|
5184
|
+
lines.push(` ${chalk6.dim("Name".padEnd(30))} ${chalk6.dim("Technology".padEnd(18))} ${chalk6.dim("Source")}`);
|
|
5143
5185
|
}
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5186
|
+
lines.push(chalk6.dim(" " + "\u2500".repeat(70)));
|
|
5187
|
+
for (let i = 0; i < recs.length; i++) {
|
|
5188
|
+
const rec = recs[i];
|
|
5189
|
+
const check = selected.has(i) ? chalk6.green("[x]") : "[ ]";
|
|
5190
|
+
const ptr = i === cursor ? chalk6.cyan(">") : " ";
|
|
5191
|
+
if (hasScores) {
|
|
5192
|
+
const scoreColor = rec.score >= 90 ? chalk6.green : rec.score >= 70 ? chalk6.yellow : chalk6.dim;
|
|
5193
|
+
lines.push(` ${ptr} ${check} ${scoreColor(String(rec.score).padStart(3))} ${rec.name.padEnd(26)} ${chalk6.dim(rec.reason.slice(0, 40))}`);
|
|
5194
|
+
} else {
|
|
5195
|
+
lines.push(` ${ptr} ${check} ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk6.dim(rec.source_url || "")}`);
|
|
5153
5196
|
}
|
|
5154
5197
|
}
|
|
5198
|
+
lines.push("");
|
|
5199
|
+
lines.push(chalk6.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q cancel"));
|
|
5200
|
+
return lines.join("\n");
|
|
5155
5201
|
}
|
|
5156
|
-
|
|
5157
|
-
if (
|
|
5158
|
-
|
|
5159
|
-
const desc = getDescription(".cursorrules");
|
|
5160
|
-
console.log(` ${icon} ${chalk6.bold(".cursorrules")}`);
|
|
5161
|
-
if (desc) console.log(chalk6.dim(` ${desc}`));
|
|
5162
|
-
console.log("");
|
|
5202
|
+
function draw(initial) {
|
|
5203
|
+
if (!initial && lineCount > 0) {
|
|
5204
|
+
stdout.write(`\x1B[${lineCount}A`);
|
|
5163
5205
|
}
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5206
|
+
stdout.write("\x1B[0J");
|
|
5207
|
+
const output = render();
|
|
5208
|
+
stdout.write(output + "\n");
|
|
5209
|
+
lineCount = output.split("\n").length;
|
|
5210
|
+
}
|
|
5211
|
+
return new Promise((resolve2) => {
|
|
5212
|
+
console.log("");
|
|
5213
|
+
draw(true);
|
|
5214
|
+
stdin.setRawMode(true);
|
|
5215
|
+
stdin.resume();
|
|
5216
|
+
stdin.setEncoding("utf8");
|
|
5217
|
+
function cleanup() {
|
|
5218
|
+
stdin.removeListener("data", onData);
|
|
5219
|
+
stdin.setRawMode(false);
|
|
5220
|
+
stdin.pause();
|
|
5221
|
+
}
|
|
5222
|
+
function onData(key) {
|
|
5223
|
+
switch (key) {
|
|
5224
|
+
case "\x1B[A":
|
|
5225
|
+
cursor = (cursor - 1 + recs.length) % recs.length;
|
|
5226
|
+
draw(false);
|
|
5227
|
+
break;
|
|
5228
|
+
case "\x1B[B":
|
|
5229
|
+
cursor = (cursor + 1) % recs.length;
|
|
5230
|
+
draw(false);
|
|
5231
|
+
break;
|
|
5232
|
+
case " ":
|
|
5233
|
+
selected.has(cursor) ? selected.delete(cursor) : selected.add(cursor);
|
|
5234
|
+
draw(false);
|
|
5235
|
+
break;
|
|
5236
|
+
case "a":
|
|
5237
|
+
recs.forEach((_, i) => selected.add(i));
|
|
5238
|
+
draw(false);
|
|
5239
|
+
break;
|
|
5240
|
+
case "n":
|
|
5241
|
+
selected.clear();
|
|
5242
|
+
draw(false);
|
|
5243
|
+
break;
|
|
5244
|
+
case "\r":
|
|
5245
|
+
case "\n":
|
|
5246
|
+
cleanup();
|
|
5247
|
+
if (selected.size === 0) {
|
|
5248
|
+
console.log(chalk6.dim("\n No skills selected.\n"));
|
|
5249
|
+
resolve2(null);
|
|
5250
|
+
} else {
|
|
5251
|
+
resolve2(Array.from(selected).sort().map((i) => recs[i]));
|
|
5252
|
+
}
|
|
5253
|
+
break;
|
|
5254
|
+
case "q":
|
|
5255
|
+
case "\x1B":
|
|
5256
|
+
case "":
|
|
5257
|
+
cleanup();
|
|
5258
|
+
console.log(chalk6.dim("\n Cancelled.\n"));
|
|
5259
|
+
resolve2(null);
|
|
5260
|
+
break;
|
|
5173
5261
|
}
|
|
5174
5262
|
}
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5263
|
+
stdin.on("data", onData);
|
|
5264
|
+
});
|
|
5265
|
+
}
|
|
5266
|
+
async function fetchSkillContent(rec) {
|
|
5267
|
+
if (!rec.source_url) return null;
|
|
5268
|
+
const repoPath = rec.source_url.replace("https://github.com/", "");
|
|
5269
|
+
const candidates = [
|
|
5270
|
+
`https://raw.githubusercontent.com/${repoPath}/HEAD/skills/${rec.slug}/SKILL.md`,
|
|
5271
|
+
`https://raw.githubusercontent.com/${repoPath}/HEAD/${rec.slug}/SKILL.md`,
|
|
5272
|
+
`https://raw.githubusercontent.com/${repoPath}/HEAD/.claude/skills/${rec.slug}/SKILL.md`,
|
|
5273
|
+
`https://raw.githubusercontent.com/${repoPath}/HEAD/.agents/skills/${rec.slug}/SKILL.md`
|
|
5274
|
+
];
|
|
5275
|
+
for (const url of candidates) {
|
|
5276
|
+
try {
|
|
5277
|
+
const resp = await fetch(url, { signal: AbortSignal.timeout(1e4) });
|
|
5278
|
+
if (resp.ok) {
|
|
5279
|
+
const text = await resp.text();
|
|
5280
|
+
if (text.length > 20) return text;
|
|
5189
5281
|
}
|
|
5282
|
+
} catch {
|
|
5190
5283
|
}
|
|
5191
5284
|
}
|
|
5192
|
-
if (!codex && !fs21.existsSync("AGENTS.md")) {
|
|
5193
|
-
console.log(` ${chalk6.green("+")} ${chalk6.bold("AGENTS.md")}`);
|
|
5194
|
-
console.log(chalk6.dim(" Cross-agent coordination file"));
|
|
5195
|
-
console.log("");
|
|
5196
|
-
}
|
|
5197
|
-
if (Array.isArray(deletions) && deletions.length > 0) {
|
|
5198
|
-
for (const del of deletions) {
|
|
5199
|
-
console.log(` ${chalk6.red("-")} ${chalk6.bold(del.filePath)}`);
|
|
5200
|
-
console.log(chalk6.dim(` ${del.reason}`));
|
|
5201
|
-
console.log("");
|
|
5202
|
-
}
|
|
5203
|
-
}
|
|
5204
|
-
console.log(` ${chalk6.green("+")} ${chalk6.dim("new")} ${chalk6.yellow("~")} ${chalk6.dim("modified")} ${chalk6.red("-")} ${chalk6.dim("removed")}`);
|
|
5205
|
-
console.log("");
|
|
5206
|
-
}
|
|
5207
|
-
function ensurePermissions() {
|
|
5208
|
-
const settingsPath = ".claude/settings.json";
|
|
5209
|
-
let settings = {};
|
|
5210
5285
|
try {
|
|
5211
|
-
|
|
5212
|
-
|
|
5286
|
+
const resp = await fetch(
|
|
5287
|
+
`https://api.github.com/repos/${repoPath}/git/trees/HEAD?recursive=1`,
|
|
5288
|
+
{ signal: AbortSignal.timeout(1e4) }
|
|
5289
|
+
);
|
|
5290
|
+
if (resp.ok) {
|
|
5291
|
+
const tree = await resp.json();
|
|
5292
|
+
const needle = `${rec.slug}/SKILL.md`;
|
|
5293
|
+
const match = tree.tree?.find((f) => f.path.endsWith(needle));
|
|
5294
|
+
if (match) {
|
|
5295
|
+
const rawUrl = `https://raw.githubusercontent.com/${repoPath}/HEAD/${match.path}`;
|
|
5296
|
+
const contentResp = await fetch(rawUrl, { signal: AbortSignal.timeout(1e4) });
|
|
5297
|
+
if (contentResp.ok) return await contentResp.text();
|
|
5298
|
+
}
|
|
5213
5299
|
}
|
|
5214
5300
|
} catch {
|
|
5215
5301
|
}
|
|
5216
|
-
|
|
5217
|
-
const allow = permissions.allow;
|
|
5218
|
-
if (Array.isArray(allow) && allow.length > 0) return;
|
|
5219
|
-
permissions.allow = [
|
|
5220
|
-
"Bash(npm run *)",
|
|
5221
|
-
"Bash(npx vitest *)",
|
|
5222
|
-
"Bash(npx tsc *)",
|
|
5223
|
-
"Bash(git *)"
|
|
5224
|
-
];
|
|
5225
|
-
settings.permissions = permissions;
|
|
5226
|
-
if (!fs21.existsSync(".claude")) fs21.mkdirSync(".claude", { recursive: true });
|
|
5227
|
-
fs21.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5302
|
+
return null;
|
|
5228
5303
|
}
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
spinner.succeed("Setup reverted successfully.\n");
|
|
5242
|
-
if (restored.length > 0) {
|
|
5243
|
-
console.log(chalk7.cyan(" Restored from backup:"));
|
|
5244
|
-
for (const file of restored) {
|
|
5245
|
-
console.log(` ${chalk7.green("\u21A9")} ${file}`);
|
|
5246
|
-
}
|
|
5304
|
+
async function installSkills(recs, platforms, contentMap) {
|
|
5305
|
+
const spinner = ora2(`Installing ${recs.length} skill${recs.length > 1 ? "s" : ""}...`).start();
|
|
5306
|
+
const installed = [];
|
|
5307
|
+
for (const rec of recs) {
|
|
5308
|
+
const content = contentMap.get(rec.slug);
|
|
5309
|
+
if (!content) continue;
|
|
5310
|
+
for (const platform of platforms) {
|
|
5311
|
+
const skillPath = getSkillPath(platform, rec.slug);
|
|
5312
|
+
const fullPath = join8(process.cwd(), skillPath);
|
|
5313
|
+
mkdirSync(dirname2(fullPath), { recursive: true });
|
|
5314
|
+
writeFileSync(fullPath, content, "utf-8");
|
|
5315
|
+
installed.push(`[${platform}] ${skillPath}`);
|
|
5247
5316
|
}
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
}
|
|
5317
|
+
}
|
|
5318
|
+
if (installed.length > 0) {
|
|
5319
|
+
spinner.succeed(`Installed ${installed.length} file${installed.length > 1 ? "s" : ""}`);
|
|
5320
|
+
for (const p of installed) {
|
|
5321
|
+
console.log(chalk6.green(` \u2713 ${p}`));
|
|
5253
5322
|
}
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
spinner.fail(chalk7.red(err instanceof Error ? err.message : "Undo failed"));
|
|
5257
|
-
throw new Error("__exit__");
|
|
5323
|
+
} else {
|
|
5324
|
+
spinner.fail("No skills were installed");
|
|
5258
5325
|
}
|
|
5326
|
+
console.log("");
|
|
5259
5327
|
}
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
const config = loadConfig();
|
|
5266
|
-
const manifest = readManifest();
|
|
5267
|
-
if (options.json) {
|
|
5268
|
-
console.log(JSON.stringify({
|
|
5269
|
-
configured: !!config,
|
|
5270
|
-
provider: config?.provider,
|
|
5271
|
-
model: config?.model,
|
|
5272
|
-
manifest
|
|
5273
|
-
}, null, 2));
|
|
5274
|
-
return;
|
|
5275
|
-
}
|
|
5276
|
-
console.log(chalk8.bold("\nCaliber Status\n"));
|
|
5277
|
-
if (config) {
|
|
5278
|
-
console.log(` LLM: ${chalk8.green(config.provider)} (${config.model})`);
|
|
5328
|
+
function printSkills(recs) {
|
|
5329
|
+
const hasScores = recs.some((r) => r.score > 0);
|
|
5330
|
+
console.log(chalk6.bold("\n Skills\n"));
|
|
5331
|
+
if (hasScores) {
|
|
5332
|
+
console.log(` ${chalk6.dim("Score".padEnd(7))} ${chalk6.dim("Name".padEnd(28))} ${chalk6.dim("Why")}`);
|
|
5279
5333
|
} else {
|
|
5280
|
-
console.log(`
|
|
5281
|
-
}
|
|
5282
|
-
if (!manifest) {
|
|
5283
|
-
console.log(` Setup: ${chalk8.dim("No setup applied")}`);
|
|
5284
|
-
console.log(chalk8.dim("\n Run ") + chalk8.hex("#83D1EB")("caliber onboard") + chalk8.dim(" to get started.\n"));
|
|
5285
|
-
return;
|
|
5334
|
+
console.log(` ${chalk6.dim("Name".padEnd(30))} ${chalk6.dim("Technology".padEnd(18))} ${chalk6.dim("Source")}`);
|
|
5286
5335
|
}
|
|
5287
|
-
console.log(
|
|
5288
|
-
for (const
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5336
|
+
console.log(chalk6.dim(" " + "\u2500".repeat(70)));
|
|
5337
|
+
for (const rec of recs) {
|
|
5338
|
+
if (hasScores) {
|
|
5339
|
+
console.log(` ${String(rec.score).padStart(3)} ${rec.name.padEnd(26)} ${chalk6.dim(rec.reason.slice(0, 50))}`);
|
|
5340
|
+
} else {
|
|
5341
|
+
console.log(` ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk6.dim(rec.source_url || "")}`);
|
|
5342
|
+
}
|
|
5292
5343
|
}
|
|
5293
5344
|
console.log("");
|
|
5294
5345
|
}
|
|
5295
5346
|
|
|
5296
|
-
// src/commands/
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5347
|
+
// src/commands/onboard.ts
|
|
5348
|
+
async function initCommand(options) {
|
|
5349
|
+
const brand = chalk7.hex("#EB9D83");
|
|
5350
|
+
const title = chalk7.hex("#83D1EB");
|
|
5351
|
+
console.log(brand.bold(`
|
|
5352
|
+
\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
|
|
5353
|
+
\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
|
|
5354
|
+
\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
5355
|
+
\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
5356
|
+
\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
|
|
5357
|
+
\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
|
|
5358
|
+
`));
|
|
5359
|
+
console.log(chalk7.dim(" Onboard your project for AI-assisted development\n"));
|
|
5360
|
+
console.log(title.bold(" Welcome to Caliber\n"));
|
|
5361
|
+
console.log(chalk7.dim(" Caliber analyzes your codebase and creates tailored config files"));
|
|
5362
|
+
console.log(chalk7.dim(" so your AI coding agents understand your project from day one.\n"));
|
|
5363
|
+
console.log(title.bold(" How onboarding works:\n"));
|
|
5364
|
+
console.log(chalk7.dim(" 1. Connect Set up your LLM provider"));
|
|
5365
|
+
console.log(chalk7.dim(" 2. Discover Analyze your code, dependencies, and structure"));
|
|
5366
|
+
console.log(chalk7.dim(" 3. Generate Create config files tailored to your project"));
|
|
5367
|
+
console.log(chalk7.dim(" 4. Review Preview, refine, and apply the changes"));
|
|
5368
|
+
console.log(chalk7.dim(" 5. Enhance Discover MCP servers for your tools"));
|
|
5369
|
+
console.log(chalk7.dim(" 6. Skills Browse community skills for your stack\n"));
|
|
5370
|
+
console.log(title.bold(" Step 1/6 \u2014 Connect your LLM\n"));
|
|
5371
|
+
let config = loadConfig();
|
|
5302
5372
|
if (!config) {
|
|
5303
|
-
console.log(
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5373
|
+
console.log(chalk7.dim(" No LLM provider set yet. Choose how to run Caliber:\n"));
|
|
5374
|
+
try {
|
|
5375
|
+
await runInteractiveProviderSetup({
|
|
5376
|
+
selectMessage: "How do you want to use Caliber? (choose LLM provider)"
|
|
5377
|
+
});
|
|
5378
|
+
} catch (err) {
|
|
5379
|
+
if (err.message === "__exit__") throw err;
|
|
5380
|
+
throw err;
|
|
5381
|
+
}
|
|
5382
|
+
config = loadConfig();
|
|
5383
|
+
if (!config) {
|
|
5384
|
+
console.log(chalk7.red(" Setup was cancelled or failed.\n"));
|
|
5385
|
+
throw new Error("__exit__");
|
|
5386
|
+
}
|
|
5387
|
+
console.log(chalk7.green(" \u2713 Provider saved. Let's continue.\n"));
|
|
5310
5388
|
}
|
|
5311
|
-
const
|
|
5312
|
-
const
|
|
5313
|
-
const
|
|
5314
|
-
|
|
5389
|
+
const displayModel = config.model === "default" && config.provider === "claude-cli" ? process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)" : config.model;
|
|
5390
|
+
const fastModel = getFastModel();
|
|
5391
|
+
const modelLine = fastModel ? ` Provider: ${config.provider} | Model: ${displayModel} | Scan: ${fastModel}` : ` Provider: ${config.provider} | Model: ${displayModel}`;
|
|
5392
|
+
console.log(chalk7.dim(modelLine + "\n"));
|
|
5393
|
+
console.log(title.bold(" Step 2/6 \u2014 Discover your project\n"));
|
|
5394
|
+
console.log(chalk7.dim(" Learning about your languages, dependencies, structure, and existing configs.\n"));
|
|
5395
|
+
const spinner = ora3("Analyzing project...").start();
|
|
5396
|
+
const fingerprint = await collectFingerprint(process.cwd());
|
|
5315
5397
|
spinner.succeed("Project analyzed");
|
|
5398
|
+
console.log(chalk7.dim(` Languages: ${fingerprint.languages.join(", ") || "none detected"}`));
|
|
5399
|
+
console.log(chalk7.dim(` Files: ${fingerprint.fileTree.length} found
|
|
5400
|
+
`));
|
|
5401
|
+
const targetAgent = options.agent || await promptAgent();
|
|
5402
|
+
const preScore = computeLocalScore(process.cwd(), targetAgent);
|
|
5403
|
+
const failingForDismissal = preScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
|
|
5404
|
+
if (failingForDismissal.length > 0) {
|
|
5405
|
+
const newDismissals = await evaluateDismissals(failingForDismissal, fingerprint);
|
|
5406
|
+
if (newDismissals.length > 0) {
|
|
5407
|
+
const existing = readDismissedChecks();
|
|
5408
|
+
const existingIds = new Set(existing.map((d) => d.id));
|
|
5409
|
+
const merged = [...existing, ...newDismissals.filter((d) => !existingIds.has(d.id))];
|
|
5410
|
+
writeDismissedChecks(merged);
|
|
5411
|
+
}
|
|
5412
|
+
}
|
|
5316
5413
|
const baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
5317
5414
|
displayScoreSummary(baselineScore);
|
|
5318
|
-
|
|
5319
|
-
|
|
5415
|
+
const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length || fingerprint.existingConfigs.agentsMd);
|
|
5416
|
+
const NON_LLM_CHECKS = /* @__PURE__ */ new Set(["hooks_configured", "agents_md_exists", "permissions_configured", "mcp_servers"]);
|
|
5417
|
+
if (hasExistingConfig && baselineScore.score === 100) {
|
|
5418
|
+
console.log(chalk7.bold.green(" Your setup is already optimal \u2014 nothing to change.\n"));
|
|
5419
|
+
console.log(chalk7.dim(" Run ") + chalk7.hex("#83D1EB")("caliber onboard --force") + chalk7.dim(" to regenerate anyway.\n"));
|
|
5420
|
+
if (!options.force) return;
|
|
5421
|
+
}
|
|
5422
|
+
const allFailingChecks = baselineScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
|
|
5423
|
+
const llmFixableChecks = allFailingChecks.filter((c) => !NON_LLM_CHECKS.has(c.id));
|
|
5424
|
+
if (hasExistingConfig && llmFixableChecks.length === 0 && allFailingChecks.length > 0 && !options.force) {
|
|
5425
|
+
console.log(chalk7.bold.green("\n Your config is fully optimized for LLM generation.\n"));
|
|
5426
|
+
console.log(chalk7.dim(" Remaining items need CLI actions:\n"));
|
|
5427
|
+
for (const check of allFailingChecks) {
|
|
5428
|
+
console.log(chalk7.dim(` \u2022 ${check.name}`));
|
|
5429
|
+
if (check.suggestion) {
|
|
5430
|
+
console.log(` ${chalk7.hex("#83D1EB")(check.suggestion)}`);
|
|
5431
|
+
}
|
|
5432
|
+
}
|
|
5433
|
+
console.log("");
|
|
5434
|
+
console.log(chalk7.dim(" Run ") + chalk7.hex("#83D1EB")("caliber onboard --force") + chalk7.dim(" to regenerate anyway.\n"));
|
|
5320
5435
|
return;
|
|
5321
5436
|
}
|
|
5322
|
-
const
|
|
5437
|
+
const isEmpty = fingerprint.fileTree.length < 3;
|
|
5438
|
+
if (isEmpty) {
|
|
5439
|
+
fingerprint.description = await promptInput3("What will you build in this project?");
|
|
5440
|
+
}
|
|
5441
|
+
let failingChecks;
|
|
5442
|
+
let passingChecks;
|
|
5443
|
+
let currentScore;
|
|
5444
|
+
if (hasExistingConfig && baselineScore.score >= 95 && !options.force) {
|
|
5445
|
+
failingChecks = llmFixableChecks.map((c) => ({ name: c.name, suggestion: c.suggestion }));
|
|
5446
|
+
passingChecks = baselineScore.checks.filter((c) => c.passed).map((c) => ({ name: c.name }));
|
|
5447
|
+
currentScore = baselineScore.score;
|
|
5448
|
+
if (failingChecks.length > 0) {
|
|
5449
|
+
console.log(title.bold(" Step 3/6 \u2014 Fine-tuning\n"));
|
|
5450
|
+
console.log(chalk7.dim(` Your setup scores ${baselineScore.score}/100 \u2014 fixing ${failingChecks.length} remaining issue${failingChecks.length === 1 ? "" : "s"}:
|
|
5451
|
+
`));
|
|
5452
|
+
for (const check of failingChecks) {
|
|
5453
|
+
console.log(chalk7.dim(` \u2022 ${check.name}`));
|
|
5454
|
+
}
|
|
5455
|
+
console.log("");
|
|
5456
|
+
}
|
|
5457
|
+
} else if (hasExistingConfig) {
|
|
5458
|
+
console.log(title.bold(" Step 3/6 \u2014 Improve your setup\n"));
|
|
5459
|
+
console.log(chalk7.dim(" Reviewing your existing configs against your codebase"));
|
|
5460
|
+
console.log(chalk7.dim(" and preparing improvements.\n"));
|
|
5461
|
+
} else {
|
|
5462
|
+
console.log(title.bold(" Step 3/6 \u2014 Build your agent setup\n"));
|
|
5463
|
+
console.log(chalk7.dim(" Creating config files tailored to your project.\n"));
|
|
5464
|
+
}
|
|
5465
|
+
console.log(chalk7.dim(" This can take a couple of minutes depending on your model and provider.\n"));
|
|
5466
|
+
const genStartTime = Date.now();
|
|
5467
|
+
const genSpinner = ora3("Generating setup...").start();
|
|
5323
5468
|
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
|
|
5324
5469
|
genMessages.start();
|
|
5325
5470
|
let generatedSetup = null;
|
|
5471
|
+
let rawOutput;
|
|
5326
5472
|
try {
|
|
5327
5473
|
const result = await generateSetup(
|
|
5328
5474
|
fingerprint,
|
|
5329
5475
|
targetAgent,
|
|
5330
|
-
|
|
5476
|
+
fingerprint.description,
|
|
5331
5477
|
{
|
|
5332
5478
|
onStatus: (status) => {
|
|
5333
5479
|
genMessages.handleServerStatus(status);
|
|
@@ -5339,797 +5485,687 @@ async function regenerateCommand(options) {
|
|
|
5339
5485
|
genMessages.stop();
|
|
5340
5486
|
genSpinner.fail(`Generation error: ${error}`);
|
|
5341
5487
|
}
|
|
5342
|
-
}
|
|
5488
|
+
},
|
|
5489
|
+
failingChecks,
|
|
5490
|
+
currentScore,
|
|
5491
|
+
passingChecks
|
|
5343
5492
|
);
|
|
5344
|
-
if (!generatedSetup)
|
|
5493
|
+
if (!generatedSetup) {
|
|
5494
|
+
generatedSetup = result.setup;
|
|
5495
|
+
rawOutput = result.raw;
|
|
5496
|
+
}
|
|
5345
5497
|
} catch (err) {
|
|
5346
5498
|
genMessages.stop();
|
|
5347
5499
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
5348
|
-
genSpinner.fail(`
|
|
5500
|
+
genSpinner.fail(`Generation failed: ${msg}`);
|
|
5349
5501
|
throw new Error("__exit__");
|
|
5350
5502
|
}
|
|
5351
5503
|
genMessages.stop();
|
|
5352
5504
|
if (!generatedSetup) {
|
|
5353
|
-
genSpinner.fail("Failed to
|
|
5505
|
+
genSpinner.fail("Failed to generate setup.");
|
|
5506
|
+
if (rawOutput) {
|
|
5507
|
+
console.log(chalk7.dim("\nRaw LLM output (JSON parse failed):"));
|
|
5508
|
+
console.log(chalk7.dim(rawOutput.slice(0, 500)));
|
|
5509
|
+
}
|
|
5354
5510
|
throw new Error("__exit__");
|
|
5355
5511
|
}
|
|
5356
|
-
|
|
5512
|
+
const elapsedMs = Date.now() - genStartTime;
|
|
5513
|
+
const mins = Math.floor(elapsedMs / 6e4);
|
|
5514
|
+
const secs = Math.floor(elapsedMs % 6e4 / 1e3);
|
|
5515
|
+
const timeStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
|
|
5516
|
+
genSpinner.succeed(`Setup generated ${chalk7.dim(`in ${timeStr}`)}`);
|
|
5517
|
+
printSetupSummary(generatedSetup);
|
|
5518
|
+
const sessionHistory = [];
|
|
5519
|
+
sessionHistory.push({
|
|
5520
|
+
role: "assistant",
|
|
5521
|
+
content: summarizeSetup("Initial generation", generatedSetup)
|
|
5522
|
+
});
|
|
5523
|
+
console.log(title.bold(" Step 4/6 \u2014 Review and apply\n"));
|
|
5357
5524
|
const setupFiles = collectSetupFiles(generatedSetup);
|
|
5358
5525
|
const staged = stageFiles(setupFiles, process.cwd());
|
|
5359
5526
|
const totalChanges = staged.newFiles + staged.modifiedFiles;
|
|
5360
|
-
console.log(
|
|
5361
|
-
${chalk9.green(`${staged.newFiles} new`)} / ${chalk9.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}
|
|
5527
|
+
console.log(chalk7.dim(` ${chalk7.green(`${staged.newFiles} new`)} / ${chalk7.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}
|
|
5362
5528
|
`));
|
|
5529
|
+
let action;
|
|
5363
5530
|
if (totalChanges === 0) {
|
|
5364
|
-
console.log(
|
|
5531
|
+
console.log(chalk7.dim(" No changes needed \u2014 your configs are already up to date.\n"));
|
|
5365
5532
|
cleanupStaging();
|
|
5366
|
-
|
|
5367
|
-
}
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5533
|
+
action = "accept";
|
|
5534
|
+
} else {
|
|
5535
|
+
const wantsReview = await promptWantsReview();
|
|
5536
|
+
if (wantsReview) {
|
|
5537
|
+
const reviewMethod = await promptReviewMethod();
|
|
5538
|
+
await openReview(reviewMethod, staged.stagedFiles);
|
|
5372
5539
|
}
|
|
5373
|
-
|
|
5374
|
-
return;
|
|
5540
|
+
action = await promptReviewAction();
|
|
5375
5541
|
}
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5542
|
+
while (action === "refine") {
|
|
5543
|
+
generatedSetup = await refineLoop(generatedSetup, targetAgent, sessionHistory);
|
|
5544
|
+
if (!generatedSetup) {
|
|
5545
|
+
cleanupStaging();
|
|
5546
|
+
console.log(chalk7.dim("Refinement cancelled. No files were modified."));
|
|
5547
|
+
return;
|
|
5548
|
+
}
|
|
5549
|
+
const updatedFiles = collectSetupFiles(generatedSetup);
|
|
5550
|
+
const restaged = stageFiles(updatedFiles, process.cwd());
|
|
5551
|
+
console.log(chalk7.dim(` ${chalk7.green(`${restaged.newFiles} new`)} / ${chalk7.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
|
|
5552
|
+
`));
|
|
5553
|
+
printSetupSummary(generatedSetup);
|
|
5554
|
+
await openReview("terminal", restaged.stagedFiles);
|
|
5555
|
+
action = await promptReviewAction();
|
|
5380
5556
|
}
|
|
5381
|
-
const action = await select4({
|
|
5382
|
-
message: "Apply regenerated setup?",
|
|
5383
|
-
choices: [
|
|
5384
|
-
{ name: "Accept and apply", value: "accept" },
|
|
5385
|
-
{ name: "Decline", value: "decline" }
|
|
5386
|
-
]
|
|
5387
|
-
});
|
|
5388
5557
|
cleanupStaging();
|
|
5389
5558
|
if (action === "decline") {
|
|
5390
|
-
console.log(
|
|
5559
|
+
console.log(chalk7.dim("Setup declined. No files were modified."));
|
|
5391
5560
|
return;
|
|
5392
5561
|
}
|
|
5393
|
-
|
|
5562
|
+
if (options.dryRun) {
|
|
5563
|
+
console.log(chalk7.yellow("\n[Dry run] Would write the following files:"));
|
|
5564
|
+
console.log(JSON.stringify(generatedSetup, null, 2));
|
|
5565
|
+
return;
|
|
5566
|
+
}
|
|
5567
|
+
const writeSpinner = ora3("Writing config files...").start();
|
|
5394
5568
|
try {
|
|
5395
5569
|
const result = writeSetup(generatedSetup);
|
|
5396
5570
|
writeSpinner.succeed("Config files written");
|
|
5571
|
+
console.log(chalk7.bold("\nFiles created/updated:"));
|
|
5397
5572
|
for (const file of result.written) {
|
|
5398
|
-
console.log(` ${
|
|
5573
|
+
console.log(` ${chalk7.green("\u2713")} ${file}`);
|
|
5399
5574
|
}
|
|
5400
5575
|
if (result.deleted.length > 0) {
|
|
5576
|
+
console.log(chalk7.bold("\nFiles removed:"));
|
|
5401
5577
|
for (const file of result.deleted) {
|
|
5402
|
-
console.log(` ${
|
|
5578
|
+
console.log(` ${chalk7.red("\u2717")} ${file}`);
|
|
5403
5579
|
}
|
|
5404
5580
|
}
|
|
5405
5581
|
if (result.backupDir) {
|
|
5406
|
-
console.log(
|
|
5582
|
+
console.log(chalk7.dim(`
|
|
5407
5583
|
Backups saved to ${result.backupDir}`));
|
|
5408
5584
|
}
|
|
5409
5585
|
} catch (err) {
|
|
5410
5586
|
writeSpinner.fail("Failed to write files");
|
|
5411
|
-
console.error(
|
|
5587
|
+
console.error(chalk7.red(err instanceof Error ? err.message : "Unknown error"));
|
|
5412
5588
|
throw new Error("__exit__");
|
|
5413
5589
|
}
|
|
5590
|
+
console.log(title.bold("\n Step 5/6 \u2014 Enhance with MCP servers\n"));
|
|
5591
|
+
console.log(chalk7.dim(" MCP servers connect your AI agents to external tools and services"));
|
|
5592
|
+
console.log(chalk7.dim(" like databases, APIs, and platforms your project depends on.\n"));
|
|
5593
|
+
if (fingerprint.tools.length > 0) {
|
|
5594
|
+
try {
|
|
5595
|
+
const mcpResult = await discoverAndInstallMcps(targetAgent, fingerprint, process.cwd());
|
|
5596
|
+
if (mcpResult.installed > 0) {
|
|
5597
|
+
console.log(chalk7.bold(`
|
|
5598
|
+
${mcpResult.installed} MCP server${mcpResult.installed > 1 ? "s" : ""} configured`));
|
|
5599
|
+
for (const name of mcpResult.names) {
|
|
5600
|
+
console.log(` ${chalk7.green("\u2713")} ${name}`);
|
|
5601
|
+
}
|
|
5602
|
+
}
|
|
5603
|
+
} catch (err) {
|
|
5604
|
+
console.log(chalk7.dim(" MCP discovery skipped: " + (err instanceof Error ? err.message : "unknown error")));
|
|
5605
|
+
}
|
|
5606
|
+
} else {
|
|
5607
|
+
console.log(chalk7.dim(" No external tools or services detected \u2014 skipping MCP discovery.\n"));
|
|
5608
|
+
}
|
|
5609
|
+
ensurePermissions();
|
|
5414
5610
|
const sha = getCurrentHeadSha();
|
|
5415
5611
|
writeState({
|
|
5416
5612
|
lastRefreshSha: sha ?? "",
|
|
5417
5613
|
lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5418
5614
|
targetAgent
|
|
5419
5615
|
});
|
|
5616
|
+
console.log("");
|
|
5617
|
+
console.log(title.bold(" Keep your configs fresh\n"));
|
|
5618
|
+
console.log(chalk7.dim(" Caliber can automatically update your agent configs when your code changes.\n"));
|
|
5619
|
+
const hookChoice = await promptHookType(targetAgent);
|
|
5620
|
+
if (hookChoice === "claude" || hookChoice === "both") {
|
|
5621
|
+
const hookResult = installHook();
|
|
5622
|
+
if (hookResult.installed) {
|
|
5623
|
+
console.log(` ${chalk7.green("\u2713")} Claude Code hook installed \u2014 docs update on session end`);
|
|
5624
|
+
console.log(chalk7.dim(" Run ") + chalk7.hex("#83D1EB")("caliber hooks --remove") + chalk7.dim(" to disable"));
|
|
5625
|
+
} else if (hookResult.alreadyInstalled) {
|
|
5626
|
+
console.log(chalk7.dim(" Claude Code hook already installed"));
|
|
5627
|
+
}
|
|
5628
|
+
const learnResult = installLearningHooks();
|
|
5629
|
+
if (learnResult.installed) {
|
|
5630
|
+
console.log(` ${chalk7.green("\u2713")} Learning hooks installed \u2014 session insights captured automatically`);
|
|
5631
|
+
console.log(chalk7.dim(" Run ") + chalk7.hex("#83D1EB")("caliber learn remove") + chalk7.dim(" to disable"));
|
|
5632
|
+
} else if (learnResult.alreadyInstalled) {
|
|
5633
|
+
console.log(chalk7.dim(" Learning hooks already installed"));
|
|
5634
|
+
}
|
|
5635
|
+
}
|
|
5636
|
+
if (hookChoice === "precommit" || hookChoice === "both") {
|
|
5637
|
+
const precommitResult = installPreCommitHook();
|
|
5638
|
+
if (precommitResult.installed) {
|
|
5639
|
+
console.log(` ${chalk7.green("\u2713")} Pre-commit hook installed \u2014 docs refresh before each commit`);
|
|
5640
|
+
console.log(chalk7.dim(" Run ") + chalk7.hex("#83D1EB")("caliber hooks --remove") + chalk7.dim(" to disable"));
|
|
5641
|
+
} else if (precommitResult.alreadyInstalled) {
|
|
5642
|
+
console.log(chalk7.dim(" Pre-commit hook already installed"));
|
|
5643
|
+
} else {
|
|
5644
|
+
console.log(chalk7.yellow(" Could not install pre-commit hook (not a git repository?)"));
|
|
5645
|
+
}
|
|
5646
|
+
}
|
|
5647
|
+
if (hookChoice === "skip") {
|
|
5648
|
+
console.log(chalk7.dim(" Skipped auto-refresh hooks. Run ") + chalk7.hex("#83D1EB")("caliber hooks --install") + chalk7.dim(" later to enable."));
|
|
5649
|
+
}
|
|
5420
5650
|
const afterScore = computeLocalScore(process.cwd(), targetAgent);
|
|
5421
5651
|
if (afterScore.score < baselineScore.score) {
|
|
5422
5652
|
console.log("");
|
|
5423
|
-
console.log(
|
|
5653
|
+
console.log(chalk7.yellow(` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`));
|
|
5424
5654
|
try {
|
|
5425
5655
|
const { restored, removed } = undoSetup();
|
|
5426
5656
|
if (restored.length > 0 || removed.length > 0) {
|
|
5427
|
-
console.log(
|
|
5657
|
+
console.log(chalk7.dim(` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`));
|
|
5428
5658
|
}
|
|
5429
5659
|
} catch {
|
|
5430
5660
|
}
|
|
5431
|
-
console.log(
|
|
5661
|
+
console.log(chalk7.dim(" Run ") + chalk7.hex("#83D1EB")("caliber onboard --force") + chalk7.dim(" to override.\n"));
|
|
5432
5662
|
return;
|
|
5433
5663
|
}
|
|
5434
5664
|
displayScoreDelta(baselineScore, afterScore);
|
|
5435
|
-
console.log(
|
|
5436
|
-
console.log(
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
// src/scanner/index.ts
|
|
5446
|
-
import fs23 from "fs";
|
|
5447
|
-
import path17 from "path";
|
|
5448
|
-
import crypto2 from "crypto";
|
|
5449
|
-
function scanLocalState(dir) {
|
|
5450
|
-
const items = [];
|
|
5451
|
-
const claudeMdPath = path17.join(dir, "CLAUDE.md");
|
|
5452
|
-
if (fs23.existsSync(claudeMdPath)) {
|
|
5453
|
-
items.push({
|
|
5454
|
-
type: "rule",
|
|
5455
|
-
platform: "claude",
|
|
5456
|
-
name: "CLAUDE.md",
|
|
5457
|
-
contentHash: hashFile(claudeMdPath),
|
|
5458
|
-
path: claudeMdPath
|
|
5459
|
-
});
|
|
5460
|
-
}
|
|
5461
|
-
const skillsDir = path17.join(dir, ".claude", "skills");
|
|
5462
|
-
if (fs23.existsSync(skillsDir)) {
|
|
5463
|
-
for (const file of fs23.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
5464
|
-
const filePath = path17.join(skillsDir, file);
|
|
5465
|
-
items.push({
|
|
5466
|
-
type: "skill",
|
|
5467
|
-
platform: "claude",
|
|
5468
|
-
name: file,
|
|
5469
|
-
contentHash: hashFile(filePath),
|
|
5470
|
-
path: filePath
|
|
5471
|
-
});
|
|
5472
|
-
}
|
|
5473
|
-
}
|
|
5474
|
-
const mcpJsonPath = path17.join(dir, ".mcp.json");
|
|
5475
|
-
if (fs23.existsSync(mcpJsonPath)) {
|
|
5665
|
+
console.log(title.bold("\n Step 6/6 \u2014 Community skills\n"));
|
|
5666
|
+
console.log(chalk7.dim(" Search public skill registries for skills that match your tech stack.\n"));
|
|
5667
|
+
const wantsSkills = await select4({
|
|
5668
|
+
message: "Search public repos for relevant skills to add to this project?",
|
|
5669
|
+
choices: [
|
|
5670
|
+
{ name: "Yes, find skills for my project", value: true },
|
|
5671
|
+
{ name: "Skip for now", value: false }
|
|
5672
|
+
]
|
|
5673
|
+
});
|
|
5674
|
+
if (wantsSkills) {
|
|
5476
5675
|
try {
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
type: "mcp",
|
|
5482
|
-
platform: "claude",
|
|
5483
|
-
name,
|
|
5484
|
-
contentHash: hashJson(mcpJson.mcpServers[name]),
|
|
5485
|
-
path: mcpJsonPath
|
|
5486
|
-
});
|
|
5487
|
-
}
|
|
5676
|
+
await searchAndInstallSkills();
|
|
5677
|
+
} catch (err) {
|
|
5678
|
+
if (err.message !== "__exit__") {
|
|
5679
|
+
console.log(chalk7.dim(" Skills search failed: " + (err.message || "unknown error")));
|
|
5488
5680
|
}
|
|
5489
|
-
|
|
5681
|
+
console.log(chalk7.dim(" Run ") + chalk7.hex("#83D1EB")("caliber skills") + chalk7.dim(" later to try again.\n"));
|
|
5490
5682
|
}
|
|
5683
|
+
} else {
|
|
5684
|
+
console.log(chalk7.dim(" Skipped. Run ") + chalk7.hex("#83D1EB")("caliber skills") + chalk7.dim(" later to browse.\n"));
|
|
5491
5685
|
}
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
for (const name of fs23.readdirSync(codexSkillsDir)) {
|
|
5506
|
-
const skillFile = path17.join(codexSkillsDir, name, "SKILL.md");
|
|
5507
|
-
if (fs23.existsSync(skillFile)) {
|
|
5508
|
-
items.push({
|
|
5509
|
-
type: "skill",
|
|
5510
|
-
platform: "codex",
|
|
5511
|
-
name: `${name}/SKILL.md`,
|
|
5512
|
-
contentHash: hashFile(skillFile),
|
|
5513
|
-
path: skillFile
|
|
5514
|
-
});
|
|
5515
|
-
}
|
|
5516
|
-
}
|
|
5517
|
-
} catch {
|
|
5686
|
+
console.log(chalk7.bold.green(" Onboarding complete! Your project is ready for AI-assisted development."));
|
|
5687
|
+
console.log(chalk7.dim(" Run ") + chalk7.hex("#83D1EB")("caliber undo") + chalk7.dim(" to revert changes.\n"));
|
|
5688
|
+
console.log(chalk7.bold(" Next steps:\n"));
|
|
5689
|
+
console.log(` ${title("caliber score")} See your full config breakdown`);
|
|
5690
|
+
console.log(` ${title("caliber skills")} Discover community skills for your stack`);
|
|
5691
|
+
console.log(` ${title("caliber undo")} Revert all changes from this run`);
|
|
5692
|
+
console.log("");
|
|
5693
|
+
}
|
|
5694
|
+
async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
5695
|
+
while (true) {
|
|
5696
|
+
const message = await promptInput3("\nWhat would you like to change?");
|
|
5697
|
+
if (!message || message.toLowerCase() === "done" || message.toLowerCase() === "accept") {
|
|
5698
|
+
return currentSetup;
|
|
5518
5699
|
}
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
if (fs23.existsSync(cursorrulesPath)) {
|
|
5522
|
-
items.push({
|
|
5523
|
-
type: "rule",
|
|
5524
|
-
platform: "cursor",
|
|
5525
|
-
name: ".cursorrules",
|
|
5526
|
-
contentHash: hashFile(cursorrulesPath),
|
|
5527
|
-
path: cursorrulesPath
|
|
5528
|
-
});
|
|
5529
|
-
}
|
|
5530
|
-
const cursorRulesDir = path17.join(dir, ".cursor", "rules");
|
|
5531
|
-
if (fs23.existsSync(cursorRulesDir)) {
|
|
5532
|
-
for (const file of fs23.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
5533
|
-
const filePath = path17.join(cursorRulesDir, file);
|
|
5534
|
-
items.push({
|
|
5535
|
-
type: "rule",
|
|
5536
|
-
platform: "cursor",
|
|
5537
|
-
name: file,
|
|
5538
|
-
contentHash: hashFile(filePath),
|
|
5539
|
-
path: filePath
|
|
5540
|
-
});
|
|
5700
|
+
if (message.toLowerCase() === "cancel") {
|
|
5701
|
+
return null;
|
|
5541
5702
|
}
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
if (fs23.existsSync(skillFile)) {
|
|
5549
|
-
items.push({
|
|
5550
|
-
type: "skill",
|
|
5551
|
-
platform: "cursor",
|
|
5552
|
-
name: `${name}/SKILL.md`,
|
|
5553
|
-
contentHash: hashFile(skillFile),
|
|
5554
|
-
path: skillFile
|
|
5555
|
-
});
|
|
5556
|
-
}
|
|
5557
|
-
}
|
|
5558
|
-
} catch {
|
|
5703
|
+
const isValid = await classifyRefineIntent(message);
|
|
5704
|
+
if (!isValid) {
|
|
5705
|
+
console.log(chalk7.dim(" This doesn't look like a config change request."));
|
|
5706
|
+
console.log(chalk7.dim(" Describe what to add, remove, or modify in your configs."));
|
|
5707
|
+
console.log(chalk7.dim(' Type "done" to accept the current setup.\n'));
|
|
5708
|
+
continue;
|
|
5559
5709
|
}
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
}
|
|
5576
|
-
|
|
5710
|
+
const refineSpinner = ora3("Refining setup...").start();
|
|
5711
|
+
const refineMessages = new SpinnerMessages(refineSpinner, REFINE_MESSAGES);
|
|
5712
|
+
refineMessages.start();
|
|
5713
|
+
const refined = await refineSetup(
|
|
5714
|
+
currentSetup,
|
|
5715
|
+
message,
|
|
5716
|
+
sessionHistory
|
|
5717
|
+
);
|
|
5718
|
+
refineMessages.stop();
|
|
5719
|
+
if (refined) {
|
|
5720
|
+
currentSetup = refined;
|
|
5721
|
+
sessionHistory.push({ role: "user", content: message });
|
|
5722
|
+
sessionHistory.push({
|
|
5723
|
+
role: "assistant",
|
|
5724
|
+
content: summarizeSetup("Applied changes", refined)
|
|
5725
|
+
});
|
|
5726
|
+
refineSpinner.succeed("Setup updated");
|
|
5727
|
+
printSetupSummary(refined);
|
|
5728
|
+
console.log(chalk7.dim('Type "done" to accept, or describe more changes.'));
|
|
5729
|
+
} else {
|
|
5730
|
+
refineSpinner.fail("Refinement failed \u2014 could not parse AI response.");
|
|
5731
|
+
console.log(chalk7.dim('Try rephrasing your request, or type "done" to keep the current setup.'));
|
|
5577
5732
|
}
|
|
5578
5733
|
}
|
|
5579
|
-
return items;
|
|
5580
5734
|
}
|
|
5581
|
-
function
|
|
5582
|
-
const
|
|
5583
|
-
|
|
5735
|
+
function summarizeSetup(action, setup) {
|
|
5736
|
+
const descriptions = setup.fileDescriptions;
|
|
5737
|
+
const files = descriptions ? Object.entries(descriptions).map(([path24, desc]) => ` ${path24}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
|
|
5738
|
+
return `${action}. Files:
|
|
5739
|
+
${files}`;
|
|
5584
5740
|
}
|
|
5585
|
-
function
|
|
5586
|
-
|
|
5741
|
+
async function classifyRefineIntent(message) {
|
|
5742
|
+
const fastModel = getFastModel();
|
|
5743
|
+
try {
|
|
5744
|
+
const result = await llmJsonCall({
|
|
5745
|
+
system: `You classify whether a user message is a valid request to modify AI agent config files (CLAUDE.md, .cursorrules, skills).
|
|
5746
|
+
Valid: requests to add, remove, change, or restructure config content. Examples: "add testing commands", "remove the terraform section", "make CLAUDE.md shorter".
|
|
5747
|
+
Invalid: questions, requests to show/display something, general chat, or anything that isn't a concrete config change.
|
|
5748
|
+
Return {"valid": true} or {"valid": false}. Nothing else.`,
|
|
5749
|
+
prompt: message,
|
|
5750
|
+
maxTokens: 20,
|
|
5751
|
+
...fastModel ? { model: fastModel } : {}
|
|
5752
|
+
});
|
|
5753
|
+
return result.valid === true;
|
|
5754
|
+
} catch {
|
|
5755
|
+
return true;
|
|
5756
|
+
}
|
|
5587
5757
|
}
|
|
5758
|
+
async function evaluateDismissals(failingChecks, fingerprint) {
|
|
5759
|
+
const fastModel = getFastModel();
|
|
5760
|
+
const checkList = failingChecks.map((c) => ({
|
|
5761
|
+
id: c.id,
|
|
5762
|
+
name: c.name,
|
|
5763
|
+
suggestion: c.suggestion
|
|
5764
|
+
}));
|
|
5765
|
+
try {
|
|
5766
|
+
const result = await llmJsonCall({
|
|
5767
|
+
system: `You evaluate whether scoring checks are applicable to a project.
|
|
5768
|
+
Given the project's languages/frameworks and a list of failing checks, return which checks are NOT applicable.
|
|
5588
5769
|
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5770
|
+
Only dismiss checks that truly don't apply \u2014 e.g. "Build/test/lint commands" for a pure Terraform/HCL repo with no build system.
|
|
5771
|
+
Do NOT dismiss checks that could reasonably apply even if the project doesn't use them yet.
|
|
5772
|
+
|
|
5773
|
+
Return {"dismissed": [{"id": "check_id", "reason": "brief reason"}]} or {"dismissed": []} if all apply.`,
|
|
5774
|
+
prompt: `Languages: ${fingerprint.languages.join(", ") || "none"}
|
|
5775
|
+
Frameworks: ${fingerprint.frameworks.join(", ") || "none"}
|
|
5776
|
+
|
|
5777
|
+
Failing checks:
|
|
5778
|
+
${JSON.stringify(checkList, null, 2)}`,
|
|
5779
|
+
maxTokens: 200,
|
|
5780
|
+
...fastModel ? { model: fastModel } : {}
|
|
5781
|
+
});
|
|
5782
|
+
if (!Array.isArray(result.dismissed)) return [];
|
|
5783
|
+
return result.dismissed.filter((d) => d.id && d.reason && failingChecks.some((c) => c.id === d.id)).map((d) => ({ id: d.id, reason: d.reason, dismissedAt: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
5784
|
+
} catch {
|
|
5785
|
+
return [];
|
|
5595
5786
|
}
|
|
5596
|
-
return platforms.size > 0 ? Array.from(platforms) : ["claude"];
|
|
5597
5787
|
}
|
|
5598
|
-
function
|
|
5599
|
-
|
|
5600
|
-
|
|
5788
|
+
function promptInput3(question) {
|
|
5789
|
+
const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
|
|
5790
|
+
return new Promise((resolve2) => {
|
|
5791
|
+
rl.question(chalk7.cyan(`${question} `), (answer) => {
|
|
5792
|
+
rl.close();
|
|
5793
|
+
resolve2(answer.trim());
|
|
5794
|
+
});
|
|
5795
|
+
});
|
|
5796
|
+
}
|
|
5797
|
+
async function promptAgent() {
|
|
5798
|
+
const selected = await checkbox({
|
|
5799
|
+
message: "Which coding agents do you use? (toggle with space)",
|
|
5800
|
+
choices: [
|
|
5801
|
+
{ name: "Claude Code", value: "claude" },
|
|
5802
|
+
{ name: "Cursor", value: "cursor" },
|
|
5803
|
+
{ name: "Codex (OpenAI)", value: "codex" }
|
|
5804
|
+
],
|
|
5805
|
+
validate: (items) => {
|
|
5806
|
+
if (items.length === 0) return "At least one agent must be selected";
|
|
5807
|
+
return true;
|
|
5808
|
+
}
|
|
5809
|
+
});
|
|
5810
|
+
return selected;
|
|
5811
|
+
}
|
|
5812
|
+
async function promptHookType(targetAgent) {
|
|
5813
|
+
const choices = [];
|
|
5814
|
+
const hasClaude = targetAgent.includes("claude");
|
|
5815
|
+
if (hasClaude) {
|
|
5816
|
+
choices.push({ name: "Claude Code hook (auto-refresh on session end)", value: "claude" });
|
|
5601
5817
|
}
|
|
5602
|
-
|
|
5603
|
-
|
|
5818
|
+
choices.push({ name: "Git pre-commit hook (refresh before each commit)", value: "precommit" });
|
|
5819
|
+
if (hasClaude) {
|
|
5820
|
+
choices.push({ name: "Both (Claude Code + pre-commit)", value: "both" });
|
|
5604
5821
|
}
|
|
5605
|
-
|
|
5822
|
+
choices.push({ name: "Skip for now", value: "skip" });
|
|
5823
|
+
return select4({
|
|
5824
|
+
message: "How would you like to auto-refresh your docs?",
|
|
5825
|
+
choices
|
|
5826
|
+
});
|
|
5606
5827
|
}
|
|
5607
|
-
function
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5828
|
+
async function promptReviewAction() {
|
|
5829
|
+
return select4({
|
|
5830
|
+
message: "What would you like to do?",
|
|
5831
|
+
choices: [
|
|
5832
|
+
{ name: "Accept and apply", value: "accept" },
|
|
5833
|
+
{ name: "Refine via chat", value: "refine" },
|
|
5834
|
+
{ name: "Decline", value: "decline" }
|
|
5835
|
+
]
|
|
5836
|
+
});
|
|
5837
|
+
}
|
|
5838
|
+
function printSetupSummary(setup) {
|
|
5839
|
+
const claude = setup.claude;
|
|
5840
|
+
const cursor = setup.cursor;
|
|
5841
|
+
const fileDescriptions = setup.fileDescriptions;
|
|
5842
|
+
const deletions = setup.deletions;
|
|
5843
|
+
console.log("");
|
|
5844
|
+
console.log(chalk7.bold(" Proposed changes:\n"));
|
|
5845
|
+
const getDescription = (filePath) => {
|
|
5846
|
+
return fileDescriptions?.[filePath];
|
|
5847
|
+
};
|
|
5848
|
+
if (claude) {
|
|
5849
|
+
if (claude.claudeMd) {
|
|
5850
|
+
const icon = fs22.existsSync("CLAUDE.md") ? chalk7.yellow("~") : chalk7.green("+");
|
|
5851
|
+
const desc = getDescription("CLAUDE.md");
|
|
5852
|
+
console.log(` ${icon} ${chalk7.bold("CLAUDE.md")}`);
|
|
5853
|
+
if (desc) console.log(chalk7.dim(` ${desc}`));
|
|
5854
|
+
console.log("");
|
|
5855
|
+
}
|
|
5856
|
+
const skills = claude.skills;
|
|
5857
|
+
if (Array.isArray(skills) && skills.length > 0) {
|
|
5858
|
+
for (const skill of skills) {
|
|
5859
|
+
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
5860
|
+
const icon = fs22.existsSync(skillPath) ? chalk7.yellow("~") : chalk7.green("+");
|
|
5861
|
+
const desc = getDescription(skillPath);
|
|
5862
|
+
console.log(` ${icon} ${chalk7.bold(skillPath)}`);
|
|
5863
|
+
console.log(chalk7.dim(` ${desc || skill.description || skill.name}`));
|
|
5864
|
+
console.log("");
|
|
5621
5865
|
}
|
|
5622
|
-
} catch {
|
|
5623
5866
|
}
|
|
5624
5867
|
}
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
const
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
source_url: skill.source ? `https://github.com/${skill.source}` : "",
|
|
5644
|
-
score: 0,
|
|
5645
|
-
reason: skill.description || "",
|
|
5646
|
-
detected_technology: tech,
|
|
5647
|
-
item_type: "skill",
|
|
5648
|
-
installs: skill.installs ?? 0
|
|
5649
|
-
});
|
|
5868
|
+
const codex = setup.codex;
|
|
5869
|
+
if (codex) {
|
|
5870
|
+
if (codex.agentsMd) {
|
|
5871
|
+
const icon = fs22.existsSync("AGENTS.md") ? chalk7.yellow("~") : chalk7.green("+");
|
|
5872
|
+
const desc = getDescription("AGENTS.md");
|
|
5873
|
+
console.log(` ${icon} ${chalk7.bold("AGENTS.md")}`);
|
|
5874
|
+
if (desc) console.log(chalk7.dim(` ${desc}`));
|
|
5875
|
+
console.log("");
|
|
5876
|
+
}
|
|
5877
|
+
const codexSkills = codex.skills;
|
|
5878
|
+
if (Array.isArray(codexSkills) && codexSkills.length > 0) {
|
|
5879
|
+
for (const skill of codexSkills) {
|
|
5880
|
+
const skillPath = `.agents/skills/${skill.name}/SKILL.md`;
|
|
5881
|
+
const icon = fs22.existsSync(skillPath) ? chalk7.yellow("~") : chalk7.green("+");
|
|
5882
|
+
const desc = getDescription(skillPath);
|
|
5883
|
+
console.log(` ${icon} ${chalk7.bold(skillPath)}`);
|
|
5884
|
+
console.log(chalk7.dim(` ${desc || skill.description || skill.name}`));
|
|
5885
|
+
console.log("");
|
|
5650
5886
|
}
|
|
5651
|
-
} catch {
|
|
5652
|
-
continue;
|
|
5653
5887
|
}
|
|
5654
5888
|
}
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
seen.add(slug);
|
|
5673
|
-
results.push({
|
|
5674
|
-
name: skillName,
|
|
5675
|
-
slug,
|
|
5676
|
-
source_url: `https://github.com/${org}/${repo}`,
|
|
5677
|
-
score: 0,
|
|
5678
|
-
reason: `Skill from ${org}/${repo}`,
|
|
5679
|
-
detected_technology: tech,
|
|
5680
|
-
item_type: "skill"
|
|
5681
|
-
});
|
|
5889
|
+
if (cursor) {
|
|
5890
|
+
if (cursor.cursorrules) {
|
|
5891
|
+
const icon = fs22.existsSync(".cursorrules") ? chalk7.yellow("~") : chalk7.green("+");
|
|
5892
|
+
const desc = getDescription(".cursorrules");
|
|
5893
|
+
console.log(` ${icon} ${chalk7.bold(".cursorrules")}`);
|
|
5894
|
+
if (desc) console.log(chalk7.dim(` ${desc}`));
|
|
5895
|
+
console.log("");
|
|
5896
|
+
}
|
|
5897
|
+
const cursorSkills = cursor.skills;
|
|
5898
|
+
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
5899
|
+
for (const skill of cursorSkills) {
|
|
5900
|
+
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
5901
|
+
const icon = fs22.existsSync(skillPath) ? chalk7.yellow("~") : chalk7.green("+");
|
|
5902
|
+
const desc = getDescription(skillPath);
|
|
5903
|
+
console.log(` ${icon} ${chalk7.bold(skillPath)}`);
|
|
5904
|
+
console.log(chalk7.dim(` ${desc || skill.description || skill.name}`));
|
|
5905
|
+
console.log("");
|
|
5682
5906
|
}
|
|
5683
|
-
} catch {
|
|
5684
|
-
continue;
|
|
5685
5907
|
}
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
});
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5908
|
+
const rules = cursor.rules;
|
|
5909
|
+
if (Array.isArray(rules) && rules.length > 0) {
|
|
5910
|
+
for (const rule of rules) {
|
|
5911
|
+
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
5912
|
+
const icon = fs22.existsSync(rulePath) ? chalk7.yellow("~") : chalk7.green("+");
|
|
5913
|
+
const desc = getDescription(rulePath);
|
|
5914
|
+
console.log(` ${icon} ${chalk7.bold(rulePath)}`);
|
|
5915
|
+
if (desc) {
|
|
5916
|
+
console.log(chalk7.dim(` ${desc}`));
|
|
5917
|
+
} else {
|
|
5918
|
+
const firstLine = rule.content.split("\n").filter((l) => l.trim() && !l.trim().startsWith("#"))[0];
|
|
5919
|
+
if (firstLine) console.log(chalk7.dim(` ${firstLine.trim().slice(0, 80)}`));
|
|
5920
|
+
}
|
|
5921
|
+
console.log("");
|
|
5922
|
+
}
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5925
|
+
if (!codex && !fs22.existsSync("AGENTS.md")) {
|
|
5926
|
+
console.log(` ${chalk7.green("+")} ${chalk7.bold("AGENTS.md")}`);
|
|
5927
|
+
console.log(chalk7.dim(" Cross-agent coordination file"));
|
|
5928
|
+
console.log("");
|
|
5929
|
+
}
|
|
5930
|
+
if (Array.isArray(deletions) && deletions.length > 0) {
|
|
5931
|
+
for (const del of deletions) {
|
|
5932
|
+
console.log(` ${chalk7.red("-")} ${chalk7.bold(del.filePath)}`);
|
|
5933
|
+
console.log(chalk7.dim(` ${del.reason}`));
|
|
5934
|
+
console.log("");
|
|
5713
5935
|
}
|
|
5714
|
-
const techLower = technologies.map((t) => t.toLowerCase());
|
|
5715
|
-
return items.filter((item) => {
|
|
5716
|
-
const text = `${item.name} ${item.reason}`.toLowerCase();
|
|
5717
|
-
return techLower.some((t) => text.includes(t));
|
|
5718
|
-
});
|
|
5719
|
-
} catch {
|
|
5720
|
-
return [];
|
|
5721
5936
|
}
|
|
5937
|
+
console.log(` ${chalk7.green("+")} ${chalk7.dim("new")} ${chalk7.yellow("~")} ${chalk7.dim("modified")} ${chalk7.red("-")} ${chalk7.dim("removed")}`);
|
|
5938
|
+
console.log("");
|
|
5722
5939
|
}
|
|
5723
|
-
|
|
5724
|
-
const
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
searches.push(searchAwesomeClaudeCode(technologies));
|
|
5730
|
-
}
|
|
5731
|
-
const results = await Promise.all(searches);
|
|
5732
|
-
const seen = /* @__PURE__ */ new Set();
|
|
5733
|
-
const combined = [];
|
|
5734
|
-
for (const batch of results) {
|
|
5735
|
-
for (const result of batch) {
|
|
5736
|
-
const key = result.name.toLowerCase().replace(/[-_]/g, "");
|
|
5737
|
-
if (seen.has(key)) continue;
|
|
5738
|
-
seen.add(key);
|
|
5739
|
-
combined.push(result);
|
|
5940
|
+
function ensurePermissions() {
|
|
5941
|
+
const settingsPath = ".claude/settings.json";
|
|
5942
|
+
let settings = {};
|
|
5943
|
+
try {
|
|
5944
|
+
if (fs22.existsSync(settingsPath)) {
|
|
5945
|
+
settings = JSON.parse(fs22.readFileSync(settingsPath, "utf-8"));
|
|
5740
5946
|
}
|
|
5947
|
+
} catch {
|
|
5741
5948
|
}
|
|
5742
|
-
|
|
5949
|
+
const permissions = settings.permissions ?? {};
|
|
5950
|
+
const allow = permissions.allow;
|
|
5951
|
+
if (Array.isArray(allow) && allow.length > 0) return;
|
|
5952
|
+
permissions.allow = [
|
|
5953
|
+
"Bash(npm run *)",
|
|
5954
|
+
"Bash(npx vitest *)",
|
|
5955
|
+
"Bash(npx tsc *)",
|
|
5956
|
+
"Bash(git *)"
|
|
5957
|
+
];
|
|
5958
|
+
settings.permissions = permissions;
|
|
5959
|
+
if (!fs22.existsSync(".claude")) fs22.mkdirSync(".claude", { recursive: true });
|
|
5960
|
+
fs22.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5743
5961
|
}
|
|
5744
|
-
async function scoreWithLLM2(candidates, projectContext, technologies) {
|
|
5745
|
-
const candidateList = candidates.map((c, i) => `${i}. "${c.name}" \u2014 ${c.reason || "no description"}`).join("\n");
|
|
5746
|
-
const scored = await llmJsonCall({
|
|
5747
|
-
system: `You evaluate whether AI agent skills and tools are relevant to a specific software project.
|
|
5748
|
-
Given a project context and a list of candidates, score each one's relevance from 0-100 and provide a brief reason (max 80 chars).
|
|
5749
|
-
|
|
5750
|
-
Return a JSON array where each element has:
|
|
5751
|
-
- "index": the candidate's index number
|
|
5752
|
-
- "score": relevance score 0-100
|
|
5753
|
-
- "reason": one-liner explaining why it fits or doesn't
|
|
5754
|
-
|
|
5755
|
-
Scoring guidelines:
|
|
5756
|
-
- 90-100: Directly matches a core technology or workflow in the project
|
|
5757
|
-
- 70-89: Relevant to the project's stack, patterns, or development workflow
|
|
5758
|
-
- 50-69: Tangentially related or generic but useful
|
|
5759
|
-
- 0-49: Not relevant to this project
|
|
5760
|
-
|
|
5761
|
-
Be selective. Prefer specific, high-quality matches over generic ones.
|
|
5762
|
-
A skill for "React testing" is only relevant if the project uses React.
|
|
5763
|
-
A generic "TypeScript best practices" skill is less valuable than one targeting the project's actual framework.
|
|
5764
|
-
Return ONLY the JSON array.`,
|
|
5765
|
-
prompt: `PROJECT CONTEXT:
|
|
5766
|
-
${projectContext}
|
|
5767
|
-
|
|
5768
|
-
DETECTED TECHNOLOGIES:
|
|
5769
|
-
${technologies.join(", ")}
|
|
5770
5962
|
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5963
|
+
// src/commands/undo.ts
|
|
5964
|
+
import chalk8 from "chalk";
|
|
5965
|
+
import ora4 from "ora";
|
|
5966
|
+
function undoCommand() {
|
|
5967
|
+
const spinner = ora4("Reverting setup...").start();
|
|
5968
|
+
try {
|
|
5969
|
+
const { restored, removed } = undoSetup();
|
|
5970
|
+
if (restored.length === 0 && removed.length === 0) {
|
|
5971
|
+
spinner.info("Nothing to undo.");
|
|
5972
|
+
return;
|
|
5973
|
+
}
|
|
5974
|
+
spinner.succeed("Setup reverted successfully.\n");
|
|
5975
|
+
if (restored.length > 0) {
|
|
5976
|
+
console.log(chalk8.cyan(" Restored from backup:"));
|
|
5977
|
+
for (const file of restored) {
|
|
5978
|
+
console.log(` ${chalk8.green("\u21A9")} ${file}`);
|
|
5979
|
+
}
|
|
5980
|
+
}
|
|
5981
|
+
if (removed.length > 0) {
|
|
5982
|
+
console.log(chalk8.cyan(" Removed:"));
|
|
5983
|
+
for (const file of removed) {
|
|
5984
|
+
console.log(` ${chalk8.red("\u2717")} ${file}`);
|
|
5985
|
+
}
|
|
5986
|
+
}
|
|
5987
|
+
console.log("");
|
|
5988
|
+
} catch (err) {
|
|
5989
|
+
spinner.fail(chalk8.red(err instanceof Error ? err.message : "Undo failed"));
|
|
5990
|
+
throw new Error("__exit__");
|
|
5991
|
+
}
|
|
5781
5992
|
}
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
if (
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5993
|
+
|
|
5994
|
+
// src/commands/status.ts
|
|
5995
|
+
import chalk9 from "chalk";
|
|
5996
|
+
import fs23 from "fs";
|
|
5997
|
+
async function statusCommand(options) {
|
|
5998
|
+
const config = loadConfig();
|
|
5999
|
+
const manifest = readManifest();
|
|
6000
|
+
if (options.json) {
|
|
6001
|
+
console.log(JSON.stringify({
|
|
6002
|
+
configured: !!config,
|
|
6003
|
+
provider: config?.provider,
|
|
6004
|
+
model: config?.model,
|
|
6005
|
+
manifest
|
|
6006
|
+
}, null, 2));
|
|
6007
|
+
return;
|
|
5793
6008
|
}
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
6009
|
+
console.log(chalk9.bold("\nCaliber Status\n"));
|
|
6010
|
+
if (config) {
|
|
6011
|
+
console.log(` LLM: ${chalk9.green(config.provider)} (${config.model})`);
|
|
6012
|
+
} else {
|
|
6013
|
+
console.log(` LLM: ${chalk9.yellow("Not configured")} \u2014 run ${chalk9.hex("#83D1EB")("caliber config")}`);
|
|
5798
6014
|
}
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
6015
|
+
if (!manifest) {
|
|
6016
|
+
console.log(` Setup: ${chalk9.dim("No setup applied")}`);
|
|
6017
|
+
console.log(chalk9.dim("\n Run ") + chalk9.hex("#83D1EB")("caliber onboard") + chalk9.dim(" to get started.\n"));
|
|
6018
|
+
return;
|
|
5803
6019
|
}
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
6020
|
+
console.log(` Files managed: ${chalk9.cyan(manifest.entries.length.toString())}`);
|
|
6021
|
+
for (const entry of manifest.entries) {
|
|
6022
|
+
const exists = fs23.existsSync(entry.path);
|
|
6023
|
+
const icon = exists ? chalk9.green("\u2713") : chalk9.red("\u2717");
|
|
6024
|
+
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
5808
6025
|
}
|
|
5809
|
-
|
|
6026
|
+
console.log("");
|
|
5810
6027
|
}
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
6028
|
+
|
|
6029
|
+
// src/commands/regenerate.ts
|
|
6030
|
+
import chalk10 from "chalk";
|
|
6031
|
+
import ora5 from "ora";
|
|
6032
|
+
import select5 from "@inquirer/select";
|
|
6033
|
+
async function regenerateCommand(options) {
|
|
6034
|
+
const config = loadConfig();
|
|
6035
|
+
if (!config) {
|
|
6036
|
+
console.log(chalk10.red("No LLM provider configured. Run ") + chalk10.hex("#83D1EB")("caliber config") + chalk10.red(" first."));
|
|
6037
|
+
throw new Error("__exit__");
|
|
6038
|
+
}
|
|
6039
|
+
const manifest = readManifest();
|
|
6040
|
+
if (!manifest) {
|
|
6041
|
+
console.log(chalk10.yellow("No existing setup found. Run ") + chalk10.hex("#83D1EB")("caliber onboard") + chalk10.yellow(" first."));
|
|
6042
|
+
throw new Error("__exit__");
|
|
6043
|
+
}
|
|
6044
|
+
const targetAgent = readState()?.targetAgent ?? ["claude", "cursor"];
|
|
6045
|
+
const spinner = ora5("Analyzing project...").start();
|
|
6046
|
+
const fingerprint = await collectFingerprint(process.cwd());
|
|
6047
|
+
spinner.succeed("Project analyzed");
|
|
6048
|
+
const baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
6049
|
+
displayScoreSummary(baselineScore);
|
|
6050
|
+
if (baselineScore.score === 100) {
|
|
6051
|
+
console.log(chalk10.green(" Your setup is already at 100/100 \u2014 nothing to regenerate.\n"));
|
|
6052
|
+
return;
|
|
6053
|
+
}
|
|
6054
|
+
const genSpinner = ora5("Regenerating setup...").start();
|
|
6055
|
+
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
|
|
6056
|
+
genMessages.start();
|
|
6057
|
+
let generatedSetup = null;
|
|
5814
6058
|
try {
|
|
5815
|
-
const
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
"commitlint",
|
|
5832
|
-
"chalk",
|
|
5833
|
-
"ora",
|
|
5834
|
-
"commander",
|
|
5835
|
-
"yargs",
|
|
5836
|
-
"meow",
|
|
5837
|
-
"inquirer",
|
|
5838
|
-
"@inquirer/confirm",
|
|
5839
|
-
"@inquirer/select",
|
|
5840
|
-
"@inquirer/prompts",
|
|
5841
|
-
"glob",
|
|
5842
|
-
"minimatch",
|
|
5843
|
-
"micromatch",
|
|
5844
|
-
"diff",
|
|
5845
|
-
"semver",
|
|
5846
|
-
"uuid",
|
|
5847
|
-
"nanoid",
|
|
5848
|
-
"debug",
|
|
5849
|
-
"ms",
|
|
5850
|
-
"lodash",
|
|
5851
|
-
"underscore",
|
|
5852
|
-
"tsup",
|
|
5853
|
-
"esbuild",
|
|
5854
|
-
"rollup",
|
|
5855
|
-
"webpack",
|
|
5856
|
-
"vite",
|
|
5857
|
-
"vitest",
|
|
5858
|
-
"jest",
|
|
5859
|
-
"mocha",
|
|
5860
|
-
"chai",
|
|
5861
|
-
"ava",
|
|
5862
|
-
"fs-extra",
|
|
5863
|
-
"mkdirp",
|
|
5864
|
-
"del",
|
|
5865
|
-
"rimraf",
|
|
5866
|
-
"path-to-regexp",
|
|
5867
|
-
"strip-ansi",
|
|
5868
|
-
"ansi-colors"
|
|
5869
|
-
]);
|
|
5870
|
-
const trivialPatterns = [
|
|
5871
|
-
/^@types\//,
|
|
5872
|
-
/^@rely-ai\//,
|
|
5873
|
-
/^@caliber-ai\//,
|
|
5874
|
-
/^eslint-/,
|
|
5875
|
-
/^@eslint\//,
|
|
5876
|
-
/^prettier-/,
|
|
5877
|
-
/^@typescript-eslint\//,
|
|
5878
|
-
/^@commitlint\//
|
|
5879
|
-
];
|
|
5880
|
-
return deps.filter(
|
|
5881
|
-
(d) => !trivial.has(d) && !trivialPatterns.some((p) => p.test(d))
|
|
6059
|
+
const result = await generateSetup(
|
|
6060
|
+
fingerprint,
|
|
6061
|
+
targetAgent,
|
|
6062
|
+
void 0,
|
|
6063
|
+
{
|
|
6064
|
+
onStatus: (status) => {
|
|
6065
|
+
genMessages.handleServerStatus(status);
|
|
6066
|
+
},
|
|
6067
|
+
onComplete: (setup) => {
|
|
6068
|
+
generatedSetup = setup;
|
|
6069
|
+
},
|
|
6070
|
+
onError: (error) => {
|
|
6071
|
+
genMessages.stop();
|
|
6072
|
+
genSpinner.fail(`Generation error: ${error}`);
|
|
6073
|
+
}
|
|
6074
|
+
}
|
|
5882
6075
|
);
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
const fingerprint = collectFingerprint(process.cwd());
|
|
5889
|
-
const platforms = detectLocalPlatforms();
|
|
5890
|
-
const installedSkills = getInstalledSkills();
|
|
5891
|
-
const technologies = [...new Set([
|
|
5892
|
-
...fingerprint.languages,
|
|
5893
|
-
...fingerprint.frameworks,
|
|
5894
|
-
...extractTopDeps()
|
|
5895
|
-
].filter(Boolean))];
|
|
5896
|
-
if (technologies.length === 0) {
|
|
5897
|
-
console.log(chalk10.yellow("Could not detect any languages or dependencies. Try running from a project root."));
|
|
6076
|
+
if (!generatedSetup) generatedSetup = result.setup;
|
|
6077
|
+
} catch (err) {
|
|
6078
|
+
genMessages.stop();
|
|
6079
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
6080
|
+
genSpinner.fail(`Regeneration failed: ${msg}`);
|
|
5898
6081
|
throw new Error("__exit__");
|
|
5899
6082
|
}
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
searchSpinner.succeed("No skills found matching your tech stack.");
|
|
5905
|
-
return;
|
|
6083
|
+
genMessages.stop();
|
|
6084
|
+
if (!generatedSetup) {
|
|
6085
|
+
genSpinner.fail("Failed to regenerate setup.");
|
|
6086
|
+
throw new Error("__exit__");
|
|
5906
6087
|
}
|
|
5907
|
-
|
|
5908
|
-
const
|
|
5909
|
-
|
|
5910
|
-
|
|
6088
|
+
genSpinner.succeed("Setup regenerated");
|
|
6089
|
+
const setupFiles = collectSetupFiles(generatedSetup);
|
|
6090
|
+
const staged = stageFiles(setupFiles, process.cwd());
|
|
6091
|
+
const totalChanges = staged.newFiles + staged.modifiedFiles;
|
|
6092
|
+
console.log(chalk10.dim(`
|
|
6093
|
+
${chalk10.green(`${staged.newFiles} new`)} / ${chalk10.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}
|
|
6094
|
+
`));
|
|
6095
|
+
if (totalChanges === 0) {
|
|
6096
|
+
console.log(chalk10.dim(" No changes needed \u2014 your configs are already up to date.\n"));
|
|
6097
|
+
cleanupStaging();
|
|
5911
6098
|
return;
|
|
5912
6099
|
}
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
const config = loadConfig();
|
|
5918
|
-
if (config) {
|
|
5919
|
-
const scoreSpinner = ora5("Scoring relevance for your project...").start();
|
|
5920
|
-
try {
|
|
5921
|
-
const projectContext = buildProjectContext(process.cwd());
|
|
5922
|
-
results = await scoreWithLLM2(newCandidates, projectContext, technologies);
|
|
5923
|
-
if (results.length === 0) {
|
|
5924
|
-
scoreSpinner.succeed("No highly relevant skills found for your specific project.");
|
|
5925
|
-
return;
|
|
5926
|
-
}
|
|
5927
|
-
scoreSpinner.succeed(`${results.length} relevant skill${results.length > 1 ? "s" : ""} for your project`);
|
|
5928
|
-
} catch {
|
|
5929
|
-
scoreSpinner.warn("Could not score relevance \u2014 showing top results");
|
|
5930
|
-
results = newCandidates.slice(0, 20);
|
|
6100
|
+
if (options.dryRun) {
|
|
6101
|
+
console.log(chalk10.yellow("[Dry run] Would write:"));
|
|
6102
|
+
for (const f of staged.stagedFiles) {
|
|
6103
|
+
console.log(` ${f.isNew ? chalk10.green("+") : chalk10.yellow("~")} ${f.relativePath}`);
|
|
5931
6104
|
}
|
|
5932
|
-
|
|
5933
|
-
results = newCandidates.slice(0, 20);
|
|
5934
|
-
}
|
|
5935
|
-
const fetchSpinner = ora5("Verifying skill availability...").start();
|
|
5936
|
-
const contentMap = /* @__PURE__ */ new Map();
|
|
5937
|
-
await Promise.all(results.map(async (rec) => {
|
|
5938
|
-
const content = await fetchSkillContent(rec);
|
|
5939
|
-
if (content) contentMap.set(rec.slug, content);
|
|
5940
|
-
}));
|
|
5941
|
-
const available = results.filter((r) => contentMap.has(r.slug));
|
|
5942
|
-
if (!available.length) {
|
|
5943
|
-
fetchSpinner.fail("No installable skills found \u2014 content could not be fetched.");
|
|
6105
|
+
cleanupStaging();
|
|
5944
6106
|
return;
|
|
5945
6107
|
}
|
|
5946
|
-
const
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
const selected = await interactiveSelect2(available);
|
|
5951
|
-
if (selected?.length) {
|
|
5952
|
-
await installSkills(selected, platforms, contentMap);
|
|
6108
|
+
const wantsReview = await promptWantsReview();
|
|
6109
|
+
if (wantsReview) {
|
|
6110
|
+
const reviewMethod = await promptReviewMethod();
|
|
6111
|
+
await openReview(reviewMethod, staged.stagedFiles);
|
|
5953
6112
|
}
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
6113
|
+
const action = await select5({
|
|
6114
|
+
message: "Apply regenerated setup?",
|
|
6115
|
+
choices: [
|
|
6116
|
+
{ name: "Accept and apply", value: "accept" },
|
|
6117
|
+
{ name: "Decline", value: "decline" }
|
|
6118
|
+
]
|
|
6119
|
+
});
|
|
6120
|
+
cleanupStaging();
|
|
6121
|
+
if (action === "decline") {
|
|
6122
|
+
console.log(chalk10.dim("Regeneration cancelled. No files were modified."));
|
|
6123
|
+
return;
|
|
5959
6124
|
}
|
|
5960
|
-
const
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
const lines = [];
|
|
5967
|
-
lines.push(chalk10.bold(" Recommendations"));
|
|
5968
|
-
lines.push("");
|
|
5969
|
-
if (hasScores) {
|
|
5970
|
-
lines.push(` ${chalk10.dim("Score".padEnd(7))} ${chalk10.dim("Name".padEnd(28))} ${chalk10.dim("Why")}`);
|
|
5971
|
-
} else {
|
|
5972
|
-
lines.push(` ${chalk10.dim("Name".padEnd(30))} ${chalk10.dim("Technology".padEnd(18))} ${chalk10.dim("Source")}`);
|
|
6125
|
+
const writeSpinner = ora5("Writing config files...").start();
|
|
6126
|
+
try {
|
|
6127
|
+
const result = writeSetup(generatedSetup);
|
|
6128
|
+
writeSpinner.succeed("Config files written");
|
|
6129
|
+
for (const file of result.written) {
|
|
6130
|
+
console.log(` ${chalk10.green("\u2713")} ${file}`);
|
|
5973
6131
|
}
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
const check = selected.has(i) ? chalk10.green("[x]") : "[ ]";
|
|
5978
|
-
const ptr = i === cursor ? chalk10.cyan(">") : " ";
|
|
5979
|
-
if (hasScores) {
|
|
5980
|
-
const scoreColor = rec.score >= 90 ? chalk10.green : rec.score >= 70 ? chalk10.yellow : chalk10.dim;
|
|
5981
|
-
lines.push(` ${ptr} ${check} ${scoreColor(String(rec.score).padStart(3))} ${rec.name.padEnd(26)} ${chalk10.dim(rec.reason.slice(0, 40))}`);
|
|
5982
|
-
} else {
|
|
5983
|
-
lines.push(` ${ptr} ${check} ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk10.dim(rec.source_url || "")}`);
|
|
6132
|
+
if (result.deleted.length > 0) {
|
|
6133
|
+
for (const file of result.deleted) {
|
|
6134
|
+
console.log(` ${chalk10.red("\u2717")} ${file}`);
|
|
5984
6135
|
}
|
|
5985
6136
|
}
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
}
|
|
5990
|
-
function draw(initial) {
|
|
5991
|
-
if (!initial && lineCount > 0) {
|
|
5992
|
-
stdout.write(`\x1B[${lineCount}A`);
|
|
6137
|
+
if (result.backupDir) {
|
|
6138
|
+
console.log(chalk10.dim(`
|
|
6139
|
+
Backups saved to ${result.backupDir}`));
|
|
5993
6140
|
}
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
6141
|
+
} catch (err) {
|
|
6142
|
+
writeSpinner.fail("Failed to write files");
|
|
6143
|
+
console.error(chalk10.red(err instanceof Error ? err.message : "Unknown error"));
|
|
6144
|
+
throw new Error("__exit__");
|
|
5998
6145
|
}
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
stdin.setEncoding("utf8");
|
|
6005
|
-
function cleanup() {
|
|
6006
|
-
stdin.removeListener("data", onData);
|
|
6007
|
-
stdin.setRawMode(false);
|
|
6008
|
-
stdin.pause();
|
|
6009
|
-
}
|
|
6010
|
-
function onData(key) {
|
|
6011
|
-
switch (key) {
|
|
6012
|
-
case "\x1B[A":
|
|
6013
|
-
cursor = (cursor - 1 + recs.length) % recs.length;
|
|
6014
|
-
draw(false);
|
|
6015
|
-
break;
|
|
6016
|
-
case "\x1B[B":
|
|
6017
|
-
cursor = (cursor + 1) % recs.length;
|
|
6018
|
-
draw(false);
|
|
6019
|
-
break;
|
|
6020
|
-
case " ":
|
|
6021
|
-
selected.has(cursor) ? selected.delete(cursor) : selected.add(cursor);
|
|
6022
|
-
draw(false);
|
|
6023
|
-
break;
|
|
6024
|
-
case "a":
|
|
6025
|
-
recs.forEach((_, i) => selected.add(i));
|
|
6026
|
-
draw(false);
|
|
6027
|
-
break;
|
|
6028
|
-
case "n":
|
|
6029
|
-
selected.clear();
|
|
6030
|
-
draw(false);
|
|
6031
|
-
break;
|
|
6032
|
-
case "\r":
|
|
6033
|
-
case "\n":
|
|
6034
|
-
cleanup();
|
|
6035
|
-
if (selected.size === 0) {
|
|
6036
|
-
console.log(chalk10.dim("\n No skills selected.\n"));
|
|
6037
|
-
resolve2(null);
|
|
6038
|
-
} else {
|
|
6039
|
-
resolve2(Array.from(selected).sort().map((i) => recs[i]));
|
|
6040
|
-
}
|
|
6041
|
-
break;
|
|
6042
|
-
case "q":
|
|
6043
|
-
case "\x1B":
|
|
6044
|
-
case "":
|
|
6045
|
-
cleanup();
|
|
6046
|
-
console.log(chalk10.dim("\n Cancelled.\n"));
|
|
6047
|
-
resolve2(null);
|
|
6048
|
-
break;
|
|
6049
|
-
}
|
|
6050
|
-
}
|
|
6051
|
-
stdin.on("data", onData);
|
|
6146
|
+
const sha = getCurrentHeadSha();
|
|
6147
|
+
writeState({
|
|
6148
|
+
lastRefreshSha: sha ?? "",
|
|
6149
|
+
lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6150
|
+
targetAgent
|
|
6052
6151
|
});
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
const candidates = [
|
|
6058
|
-
`https://raw.githubusercontent.com/${repoPath}/HEAD/skills/${rec.slug}/SKILL.md`,
|
|
6059
|
-
`https://raw.githubusercontent.com/${repoPath}/HEAD/${rec.slug}/SKILL.md`,
|
|
6060
|
-
`https://raw.githubusercontent.com/${repoPath}/HEAD/.claude/skills/${rec.slug}/SKILL.md`,
|
|
6061
|
-
`https://raw.githubusercontent.com/${repoPath}/HEAD/.agents/skills/${rec.slug}/SKILL.md`
|
|
6062
|
-
];
|
|
6063
|
-
for (const url of candidates) {
|
|
6152
|
+
const afterScore = computeLocalScore(process.cwd(), targetAgent);
|
|
6153
|
+
if (afterScore.score < baselineScore.score) {
|
|
6154
|
+
console.log("");
|
|
6155
|
+
console.log(chalk10.yellow(` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`));
|
|
6064
6156
|
try {
|
|
6065
|
-
const
|
|
6066
|
-
if (
|
|
6067
|
-
|
|
6068
|
-
if (text.length > 20) return text;
|
|
6157
|
+
const { restored, removed } = undoSetup();
|
|
6158
|
+
if (restored.length > 0 || removed.length > 0) {
|
|
6159
|
+
console.log(chalk10.dim(` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`));
|
|
6069
6160
|
}
|
|
6070
6161
|
} catch {
|
|
6071
6162
|
}
|
|
6163
|
+
console.log(chalk10.dim(" Run ") + chalk10.hex("#83D1EB")("caliber onboard --force") + chalk10.dim(" to override.\n"));
|
|
6164
|
+
return;
|
|
6072
6165
|
}
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
{ signal: AbortSignal.timeout(1e4) }
|
|
6077
|
-
);
|
|
6078
|
-
if (resp.ok) {
|
|
6079
|
-
const tree = await resp.json();
|
|
6080
|
-
const needle = `${rec.slug}/SKILL.md`;
|
|
6081
|
-
const match = tree.tree?.find((f) => f.path.endsWith(needle));
|
|
6082
|
-
if (match) {
|
|
6083
|
-
const rawUrl = `https://raw.githubusercontent.com/${repoPath}/HEAD/${match.path}`;
|
|
6084
|
-
const contentResp = await fetch(rawUrl, { signal: AbortSignal.timeout(1e4) });
|
|
6085
|
-
if (contentResp.ok) return await contentResp.text();
|
|
6086
|
-
}
|
|
6087
|
-
}
|
|
6088
|
-
} catch {
|
|
6089
|
-
}
|
|
6090
|
-
return null;
|
|
6091
|
-
}
|
|
6092
|
-
async function installSkills(recs, platforms, contentMap) {
|
|
6093
|
-
const spinner = ora5(`Installing ${recs.length} skill${recs.length > 1 ? "s" : ""}...`).start();
|
|
6094
|
-
const installed = [];
|
|
6095
|
-
for (const rec of recs) {
|
|
6096
|
-
const content = contentMap.get(rec.slug);
|
|
6097
|
-
if (!content) continue;
|
|
6098
|
-
for (const platform of platforms) {
|
|
6099
|
-
const skillPath = getSkillPath(platform, rec.slug);
|
|
6100
|
-
const fullPath = join8(process.cwd(), skillPath);
|
|
6101
|
-
mkdirSync(dirname2(fullPath), { recursive: true });
|
|
6102
|
-
writeFileSync(fullPath, content, "utf-8");
|
|
6103
|
-
installed.push(`[${platform}] ${skillPath}`);
|
|
6104
|
-
}
|
|
6105
|
-
}
|
|
6106
|
-
if (installed.length > 0) {
|
|
6107
|
-
spinner.succeed(`Installed ${installed.length} file${installed.length > 1 ? "s" : ""}`);
|
|
6108
|
-
for (const p of installed) {
|
|
6109
|
-
console.log(chalk10.green(` \u2713 ${p}`));
|
|
6110
|
-
}
|
|
6111
|
-
} else {
|
|
6112
|
-
spinner.fail("No skills were installed");
|
|
6113
|
-
}
|
|
6114
|
-
console.log("");
|
|
6115
|
-
}
|
|
6116
|
-
function printRecommendations(recs) {
|
|
6117
|
-
const hasScores = recs.some((r) => r.score > 0);
|
|
6118
|
-
console.log(chalk10.bold("\n Recommendations\n"));
|
|
6119
|
-
if (hasScores) {
|
|
6120
|
-
console.log(` ${chalk10.dim("Score".padEnd(7))} ${chalk10.dim("Name".padEnd(28))} ${chalk10.dim("Why")}`);
|
|
6121
|
-
} else {
|
|
6122
|
-
console.log(` ${chalk10.dim("Name".padEnd(30))} ${chalk10.dim("Technology".padEnd(18))} ${chalk10.dim("Source")}`);
|
|
6123
|
-
}
|
|
6124
|
-
console.log(chalk10.dim(" " + "\u2500".repeat(70)));
|
|
6125
|
-
for (const rec of recs) {
|
|
6126
|
-
if (hasScores) {
|
|
6127
|
-
console.log(` ${String(rec.score).padStart(3)} ${rec.name.padEnd(26)} ${chalk10.dim(rec.reason.slice(0, 50))}`);
|
|
6128
|
-
} else {
|
|
6129
|
-
console.log(` ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk10.dim(rec.source_url || "")}`);
|
|
6130
|
-
}
|
|
6131
|
-
}
|
|
6132
|
-
console.log("");
|
|
6166
|
+
displayScoreDelta(baselineScore, afterScore);
|
|
6167
|
+
console.log(chalk10.bold.green(" Regeneration complete!"));
|
|
6168
|
+
console.log(chalk10.dim(" Run ") + chalk10.hex("#83D1EB")("caliber undo") + chalk10.dim(" to revert changes.\n"));
|
|
6133
6169
|
}
|
|
6134
6170
|
|
|
6135
6171
|
// src/commands/score.ts
|
|
@@ -6373,7 +6409,7 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
6373
6409
|
}
|
|
6374
6410
|
const spinner = quiet ? null : ora6(`${prefix}Analyzing changes...`).start();
|
|
6375
6411
|
const existingDocs = readExistingConfigs(repoDir);
|
|
6376
|
-
const fingerprint = collectFingerprint(repoDir);
|
|
6412
|
+
const fingerprint = await collectFingerprint(repoDir);
|
|
6377
6413
|
const projectContext = {
|
|
6378
6414
|
languages: fingerprint.languages,
|
|
6379
6415
|
frameworks: fingerprint.frameworks,
|
|
@@ -7033,7 +7069,7 @@ program.command("undo").description("Revert all config changes made by Caliber")
|
|
|
7033
7069
|
program.command("status").description("Show current Caliber setup status").option("--json", "Output as JSON").action(statusCommand);
|
|
7034
7070
|
program.command("regenerate").alias("regen").alias("re").description("Re-analyze project and regenerate setup").option("--dry-run", "Preview changes without writing files").action(regenerateCommand);
|
|
7035
7071
|
program.command("config").description("Configure LLM provider, API key, and model").action(configCommand);
|
|
7036
|
-
program.command("
|
|
7072
|
+
program.command("skills").description("Discover and install community skills for your project").action(recommendCommand);
|
|
7037
7073
|
program.command("score").description("Score your current agent config setup (deterministic, no network)").option("--json", "Output as JSON").option("--quiet", "One-line output for scripts/hooks").option("--agent <type>", "Target agents (comma-separated): claude, cursor, codex", parseAgentOption).action(scoreCommand);
|
|
7038
7074
|
program.command("refresh").description("Update docs based on recent code changes").option("--quiet", "Suppress output (for use in hooks)").option("--dry-run", "Preview changes without writing files").action(refreshCommand);
|
|
7039
7075
|
program.command("hooks").description("Manage auto-refresh hooks (toggle interactively)").option("--install", "Enable all hooks non-interactively").option("--remove", "Disable all hooks non-interactively").action(hooksCommand);
|