@rely-ai/caliber 0.4.6 → 0.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.
Files changed (2) hide show
  1. package/dist/bin.js +320 -27
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -4474,7 +4474,7 @@ async function regenerateCommand(options) {
4474
4474
  // src/commands/recommend.ts
4475
4475
  import chalk8 from "chalk";
4476
4476
  import ora4 from "ora";
4477
- import { mkdirSync, readFileSync as readFileSync7, existsSync as existsSync9, writeFileSync } from "fs";
4477
+ import { mkdirSync, readFileSync as readFileSync7, readdirSync as readdirSync5, existsSync as existsSync9, writeFileSync } from "fs";
4478
4478
  import { join as join8, dirname as dirname2 } from "path";
4479
4479
 
4480
4480
  // src/scanner/index.ts
@@ -4608,22 +4608,43 @@ function getSkillPath(platform, slug) {
4608
4608
  }
4609
4609
  return join8(".claude", "skills", slug, "SKILL.md");
4610
4610
  }
4611
- async function searchSkills(technologies) {
4611
+ function getInstalledSkills() {
4612
+ const installed = /* @__PURE__ */ new Set();
4613
+ const dirs = [
4614
+ join8(process.cwd(), ".claude", "skills"),
4615
+ join8(process.cwd(), ".cursor", "skills")
4616
+ ];
4617
+ for (const dir of dirs) {
4618
+ try {
4619
+ const entries = readdirSync5(dir, { withFileTypes: true });
4620
+ for (const entry of entries) {
4621
+ if (entry.isDirectory()) {
4622
+ installed.add(entry.name.toLowerCase());
4623
+ }
4624
+ }
4625
+ } catch {
4626
+ }
4627
+ }
4628
+ return installed;
4629
+ }
4630
+ async function searchSkillsSh(technologies) {
4612
4631
  const results = [];
4613
4632
  const seen = /* @__PURE__ */ new Set();
4614
4633
  for (const tech of technologies) {
4615
4634
  try {
4616
- const resp = await fetch(`https://api.skills.sh/v1/search?q=${encodeURIComponent(tech)}&limit=10`);
4635
+ const resp = await fetch(`https://skills.sh/api/search?q=${encodeURIComponent(tech)}&limit=10`, {
4636
+ signal: AbortSignal.timeout(1e4)
4637
+ });
4617
4638
  if (!resp.ok) continue;
4618
4639
  const data = await resp.json();
4619
4640
  if (!data.skills?.length) continue;
4620
4641
  for (const skill of data.skills) {
4621
- if (seen.has(skill.slug)) continue;
4622
- seen.add(skill.slug);
4642
+ if (seen.has(skill.skillId)) continue;
4643
+ seen.add(skill.skillId);
4623
4644
  results.push({
4624
4645
  name: skill.name,
4625
- slug: skill.slug,
4626
- source_url: skill.repo,
4646
+ slug: skill.skillId,
4647
+ source_url: skill.source ? `https://github.com/${skill.source}` : "",
4627
4648
  score: 0,
4628
4649
  reason: skill.description || "",
4629
4650
  detected_technology: tech,
@@ -4636,12 +4657,231 @@ async function searchSkills(technologies) {
4636
4657
  }
4637
4658
  return results;
4638
4659
  }
4660
+ async function searchTessl(technologies) {
4661
+ const results = [];
4662
+ const seen = /* @__PURE__ */ new Set();
4663
+ for (const tech of technologies) {
4664
+ try {
4665
+ const resp = await fetch(`https://tessl.io/registry?q=${encodeURIComponent(tech)}`, {
4666
+ signal: AbortSignal.timeout(1e4)
4667
+ });
4668
+ if (!resp.ok) continue;
4669
+ const html = await resp.text();
4670
+ const linkMatches = html.matchAll(/\/registry\/skills\/github\/([^/]+)\/([^/]+)\/([^/"]+)/g);
4671
+ for (const match of linkMatches) {
4672
+ const [, org, repo, skillName] = match;
4673
+ const slug = `${org}-${repo}-${skillName}`.toLowerCase();
4674
+ if (seen.has(slug)) continue;
4675
+ seen.add(slug);
4676
+ results.push({
4677
+ name: skillName,
4678
+ slug,
4679
+ source_url: `https://github.com/${org}/${repo}`,
4680
+ score: 0,
4681
+ reason: `Skill from ${org}/${repo}`,
4682
+ detected_technology: tech,
4683
+ item_type: "skill"
4684
+ });
4685
+ }
4686
+ } catch {
4687
+ continue;
4688
+ }
4689
+ }
4690
+ return results;
4691
+ }
4692
+ var AWESOME_CLAUDE_CODE_URL = "https://raw.githubusercontent.com/hesreallyhim/awesome-claude-code/main/README.md";
4693
+ async function searchAwesomeClaudeCode(technologies) {
4694
+ try {
4695
+ const resp = await fetch(AWESOME_CLAUDE_CODE_URL, {
4696
+ signal: AbortSignal.timeout(1e4)
4697
+ });
4698
+ if (!resp.ok) return [];
4699
+ const markdown = await resp.text();
4700
+ const items = [];
4701
+ const itemPattern = /^[-*]\s+\[([^\]]+)\]\(([^)]+)\)(?:\s+by\s+\[[^\]]*\]\([^)]*\))?\s*[-–—:]\s*(.*)/gm;
4702
+ let match;
4703
+ while ((match = itemPattern.exec(markdown)) !== null) {
4704
+ const [, name, url, description] = match;
4705
+ if (url.startsWith("#")) continue;
4706
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
4707
+ items.push({
4708
+ name: name.trim(),
4709
+ slug,
4710
+ source_url: url.trim(),
4711
+ score: 0,
4712
+ reason: description.trim().slice(0, 150),
4713
+ detected_technology: "claude-code",
4714
+ item_type: "skill"
4715
+ });
4716
+ }
4717
+ const techLower = technologies.map((t) => t.toLowerCase());
4718
+ return items.filter((item) => {
4719
+ const text = `${item.name} ${item.reason}`.toLowerCase();
4720
+ return techLower.some((t) => text.includes(t));
4721
+ });
4722
+ } catch {
4723
+ return [];
4724
+ }
4725
+ }
4726
+ async function searchAllProviders(technologies, platform) {
4727
+ const searches = [
4728
+ searchSkillsSh(technologies),
4729
+ searchTessl(technologies)
4730
+ ];
4731
+ if (platform === "claude" || !platform) {
4732
+ searches.push(searchAwesomeClaudeCode(technologies));
4733
+ }
4734
+ const results = await Promise.all(searches);
4735
+ const seen = /* @__PURE__ */ new Set();
4736
+ const combined = [];
4737
+ for (const batch of results) {
4738
+ for (const result of batch) {
4739
+ if (seen.has(result.slug)) continue;
4740
+ seen.add(result.slug);
4741
+ combined.push(result);
4742
+ }
4743
+ }
4744
+ return combined;
4745
+ }
4746
+ async function scoreWithLLM(candidates, projectContext, technologies) {
4747
+ const candidateList = candidates.map((c, i) => `${i}. "${c.name}" \u2014 ${c.reason || "no description"}`).join("\n");
4748
+ const scored = await llmJsonCall({
4749
+ system: `You evaluate whether AI agent skills and tools are relevant to a specific software project.
4750
+ 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).
4751
+
4752
+ Return a JSON array where each element has:
4753
+ - "index": the candidate's index number
4754
+ - "score": relevance score 0-100
4755
+ - "reason": one-liner explaining why it fits or doesn't
4756
+
4757
+ Scoring guidelines:
4758
+ - 90-100: Directly matches a core technology or workflow in the project
4759
+ - 70-89: Relevant to the project's stack, patterns, or development workflow
4760
+ - 50-69: Tangentially related or generic but useful
4761
+ - 0-49: Not relevant to this project
4762
+
4763
+ Be selective. Prefer specific, high-quality matches over generic ones.
4764
+ A skill for "React testing" is only relevant if the project uses React.
4765
+ A generic "TypeScript best practices" skill is less valuable than one targeting the project's actual framework.
4766
+ Return ONLY the JSON array.`,
4767
+ prompt: `PROJECT CONTEXT:
4768
+ ${projectContext}
4769
+
4770
+ DETECTED TECHNOLOGIES:
4771
+ ${technologies.join(", ")}
4772
+
4773
+ CANDIDATES:
4774
+ ${candidateList}`,
4775
+ maxTokens: 8e3
4776
+ });
4777
+ if (!Array.isArray(scored)) return [];
4778
+ 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) => ({
4779
+ ...candidates[s.index],
4780
+ score: s.score,
4781
+ reason: s.reason || candidates[s.index].reason
4782
+ }));
4783
+ }
4784
+ function buildProjectContext(dir) {
4785
+ const parts = [];
4786
+ const fingerprint = collectFingerprint(dir);
4787
+ if (fingerprint.packageName) parts.push(`Package: ${fingerprint.packageName}`);
4788
+ if (fingerprint.languages.length > 0) parts.push(`Languages: ${fingerprint.languages.join(", ")}`);
4789
+ if (fingerprint.frameworks.length > 0) parts.push(`Frameworks: ${fingerprint.frameworks.join(", ")}`);
4790
+ if (fingerprint.description) parts.push(`Description: ${fingerprint.description}`);
4791
+ if (fingerprint.fileTree.length > 0) {
4792
+ parts.push(`
4793
+ File tree (${fingerprint.fileTree.length} files):
4794
+ ${fingerprint.fileTree.slice(0, 50).join("\n")}`);
4795
+ }
4796
+ if (fingerprint.existingConfigs.claudeMd) {
4797
+ parts.push(`
4798
+ Existing CLAUDE.md (first 500 chars):
4799
+ ${fingerprint.existingConfigs.claudeMd.slice(0, 500)}`);
4800
+ }
4801
+ const deps = extractTopDeps();
4802
+ if (deps.length > 0) {
4803
+ parts.push(`
4804
+ Dependencies: ${deps.slice(0, 30).join(", ")}`);
4805
+ }
4806
+ const installed = getInstalledSkills();
4807
+ if (installed.size > 0) {
4808
+ parts.push(`
4809
+ Already installed skills: ${Array.from(installed).join(", ")}`);
4810
+ }
4811
+ return parts.join("\n");
4812
+ }
4639
4813
  function extractTopDeps() {
4640
4814
  const pkgPath = join8(process.cwd(), "package.json");
4641
4815
  if (!existsSync9(pkgPath)) return [];
4642
4816
  try {
4643
4817
  const pkg3 = JSON.parse(readFileSync7(pkgPath, "utf-8"));
4644
- return Object.keys(pkg3.dependencies ?? {});
4818
+ const deps = Object.keys(pkg3.dependencies ?? {});
4819
+ const trivial = /* @__PURE__ */ new Set([
4820
+ "typescript",
4821
+ "tslib",
4822
+ "ts-node",
4823
+ "tsx",
4824
+ "prettier",
4825
+ "eslint",
4826
+ "@eslint/js",
4827
+ "rimraf",
4828
+ "cross-env",
4829
+ "dotenv",
4830
+ "nodemon",
4831
+ "husky",
4832
+ "lint-staged",
4833
+ "commitlint",
4834
+ "chalk",
4835
+ "ora",
4836
+ "commander",
4837
+ "yargs",
4838
+ "meow",
4839
+ "inquirer",
4840
+ "@inquirer/confirm",
4841
+ "@inquirer/select",
4842
+ "@inquirer/prompts",
4843
+ "glob",
4844
+ "minimatch",
4845
+ "micromatch",
4846
+ "diff",
4847
+ "semver",
4848
+ "uuid",
4849
+ "nanoid",
4850
+ "debug",
4851
+ "ms",
4852
+ "lodash",
4853
+ "underscore",
4854
+ "tsup",
4855
+ "esbuild",
4856
+ "rollup",
4857
+ "webpack",
4858
+ "vite",
4859
+ "vitest",
4860
+ "jest",
4861
+ "mocha",
4862
+ "chai",
4863
+ "ava",
4864
+ "fs-extra",
4865
+ "mkdirp",
4866
+ "del",
4867
+ "rimraf",
4868
+ "path-to-regexp",
4869
+ "strip-ansi",
4870
+ "ansi-colors"
4871
+ ]);
4872
+ const trivialPatterns = [
4873
+ /^@types\//,
4874
+ /^@rely-ai\//,
4875
+ /^@caliber-ai\//,
4876
+ /^eslint-/,
4877
+ /^@eslint\//,
4878
+ /^prettier-/,
4879
+ /^@typescript-eslint\//,
4880
+ /^@commitlint\//
4881
+ ];
4882
+ return deps.filter(
4883
+ (d) => !trivial.has(d) && !trivialPatterns.some((p) => p.test(d))
4884
+ );
4645
4885
  } catch {
4646
4886
  return [];
4647
4887
  }
@@ -4649,6 +4889,7 @@ function extractTopDeps() {
4649
4889
  async function recommendCommand(options) {
4650
4890
  const fingerprint = collectFingerprint(process.cwd());
4651
4891
  const platforms = detectLocalPlatforms();
4892
+ const installedSkills = getInstalledSkills();
4652
4893
  const technologies = [...new Set([
4653
4894
  ...fingerprint.languages,
4654
4895
  ...fingerprint.frameworks,
@@ -4658,13 +4899,41 @@ async function recommendCommand(options) {
4658
4899
  console.log(chalk8.yellow("Could not detect any languages or dependencies. Try running from a project root."));
4659
4900
  throw new Error("__exit__");
4660
4901
  }
4661
- const spinner = ora4("Searching for skills...").start();
4662
- const results = await searchSkills(technologies);
4663
- if (!results.length) {
4664
- spinner.succeed("No skill recommendations found for your tech stack.");
4902
+ const primaryPlatform = platforms.includes("claude") ? "claude" : platforms[0];
4903
+ const searchSpinner = ora4("Searching skill registries...").start();
4904
+ const allCandidates = await searchAllProviders(technologies, primaryPlatform);
4905
+ if (!allCandidates.length) {
4906
+ searchSpinner.succeed("No skills found matching your tech stack.");
4907
+ return;
4908
+ }
4909
+ const newCandidates = allCandidates.filter((c) => !installedSkills.has(c.slug.toLowerCase()));
4910
+ const filteredCount = allCandidates.length - newCandidates.length;
4911
+ if (!newCandidates.length) {
4912
+ searchSpinner.succeed(`Found ${allCandidates.length} skills \u2014 all already installed.`);
4665
4913
  return;
4666
4914
  }
4667
- spinner.succeed(`Found ${results.length} skill${results.length > 1 ? "s" : ""}`);
4915
+ searchSpinner.succeed(
4916
+ `Found ${allCandidates.length} skills` + (filteredCount > 0 ? chalk8.dim(` (${filteredCount} already installed)`) : "")
4917
+ );
4918
+ let results;
4919
+ const config = loadConfig();
4920
+ if (config) {
4921
+ const scoreSpinner = ora4("Scoring relevance for your project...").start();
4922
+ try {
4923
+ const projectContext = buildProjectContext(process.cwd());
4924
+ results = await scoreWithLLM(newCandidates, projectContext, technologies);
4925
+ if (results.length === 0) {
4926
+ scoreSpinner.succeed("No highly relevant skills found for your specific project.");
4927
+ return;
4928
+ }
4929
+ scoreSpinner.succeed(`${results.length} relevant skill${results.length > 1 ? "s" : ""} for your project`);
4930
+ } catch {
4931
+ scoreSpinner.warn("Could not score relevance \u2014 showing top results");
4932
+ results = newCandidates.slice(0, 20);
4933
+ }
4934
+ } else {
4935
+ results = newCandidates.slice(0, 20);
4936
+ }
4668
4937
  const selected = await interactiveSelect(results);
4669
4938
  if (selected?.length) {
4670
4939
  await installSkills(selected, platforms);
@@ -4679,17 +4948,27 @@ async function interactiveSelect(recs) {
4679
4948
  let cursor = 0;
4680
4949
  const { stdin, stdout } = process;
4681
4950
  let lineCount = 0;
4951
+ const hasScores = recs.some((r) => r.score > 0);
4682
4952
  function render() {
4683
4953
  const lines = [];
4684
4954
  lines.push(chalk8.bold(" Recommendations"));
4685
4955
  lines.push("");
4686
- lines.push(` ${chalk8.dim("Name".padEnd(30))} ${chalk8.dim("Technology".padEnd(18))} ${chalk8.dim("Source")}`);
4956
+ if (hasScores) {
4957
+ lines.push(` ${chalk8.dim("Score".padEnd(7))} ${chalk8.dim("Name".padEnd(28))} ${chalk8.dim("Why")}`);
4958
+ } else {
4959
+ lines.push(` ${chalk8.dim("Name".padEnd(30))} ${chalk8.dim("Technology".padEnd(18))} ${chalk8.dim("Source")}`);
4960
+ }
4687
4961
  lines.push(chalk8.dim(" " + "\u2500".repeat(70)));
4688
4962
  for (let i = 0; i < recs.length; i++) {
4689
4963
  const rec = recs[i];
4690
4964
  const check = selected.has(i) ? chalk8.green("[x]") : "[ ]";
4691
- const ptr = i === cursor ? chalk8.cyan("\u276F") : " ";
4692
- lines.push(` ${ptr} ${check} ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk8.dim(rec.source_url || "")}`);
4965
+ const ptr = i === cursor ? chalk8.cyan(">") : " ";
4966
+ if (hasScores) {
4967
+ const scoreColor = rec.score >= 90 ? chalk8.green : rec.score >= 70 ? chalk8.yellow : chalk8.dim;
4968
+ lines.push(` ${ptr} ${check} ${scoreColor(String(rec.score).padStart(3))} ${rec.name.padEnd(26)} ${chalk8.dim(rec.reason.slice(0, 40))}`);
4969
+ } else {
4970
+ lines.push(` ${ptr} ${check} ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk8.dim(rec.source_url || "")}`);
4971
+ }
4693
4972
  }
4694
4973
  lines.push("");
4695
4974
  lines.push(chalk8.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q cancel"));
@@ -4760,10 +5039,22 @@ async function interactiveSelect(recs) {
4760
5039
  });
4761
5040
  }
4762
5041
  async function fetchSkillContent(rec) {
4763
- if (rec.source_url && rec.slug) {
5042
+ try {
5043
+ const resp = await fetch(`https://skills.sh/api/skills/${rec.slug}`, {
5044
+ signal: AbortSignal.timeout(1e4)
5045
+ });
5046
+ if (resp.ok) {
5047
+ const data = await resp.json();
5048
+ const content = data.content || data.text;
5049
+ if (content) return content;
5050
+ }
5051
+ } catch {
5052
+ }
5053
+ if (rec.source_url) {
4764
5054
  try {
4765
- const url = `https://raw.githubusercontent.com/${rec.source_url}/HEAD/skills/${rec.slug}/SKILL.md`;
4766
- const resp = await fetch(url);
5055
+ const repoPath = rec.source_url.replace("https://github.com/", "");
5056
+ const url = `https://raw.githubusercontent.com/${repoPath}/HEAD/skills/${rec.slug}/SKILL.md`;
5057
+ const resp = await fetch(url, { signal: AbortSignal.timeout(1e4) });
4767
5058
  if (resp.ok) return await resp.text();
4768
5059
  } catch {
4769
5060
  }
@@ -4802,17 +5093,19 @@ async function installSkills(recs, platforms) {
4802
5093
  console.log("");
4803
5094
  }
4804
5095
  function printRecommendations(recs) {
5096
+ const hasScores = recs.some((r) => r.score > 0);
4805
5097
  console.log(chalk8.bold("\n Recommendations\n"));
4806
- console.log(
4807
- ` ${chalk8.dim("Name".padEnd(30))} ${chalk8.dim("Technology".padEnd(18))} ${chalk8.dim("Source")}`
4808
- );
5098
+ if (hasScores) {
5099
+ console.log(` ${chalk8.dim("Score".padEnd(7))} ${chalk8.dim("Name".padEnd(28))} ${chalk8.dim("Why")}`);
5100
+ } else {
5101
+ console.log(` ${chalk8.dim("Name".padEnd(30))} ${chalk8.dim("Technology".padEnd(18))} ${chalk8.dim("Source")}`);
5102
+ }
4809
5103
  console.log(chalk8.dim(" " + "\u2500".repeat(70)));
4810
5104
  for (const rec of recs) {
4811
- console.log(
4812
- ` ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk8.dim(rec.source_url || "")}`
4813
- );
4814
- if (rec.reason) {
4815
- console.log(` ${chalk8.dim(" " + rec.reason)}`);
5105
+ if (hasScores) {
5106
+ console.log(` ${String(rec.score).padStart(3)} ${rec.name.padEnd(26)} ${chalk8.dim(rec.reason.slice(0, 50))}`);
5107
+ } else {
5108
+ console.log(` ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk8.dim(rec.source_url || "")}`);
4816
5109
  }
4817
5110
  }
4818
5111
  console.log("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rely-ai/caliber",
3
- "version": "0.4.6",
3
+ "version": "0.5.0",
4
4
  "description": "Analyze your codebase and generate optimized AI agent configs (CLAUDE.md, .cursorrules, skills) — no API key needed",
5
5
  "type": "module",
6
6
  "bin": {