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