@rely-ai/caliber 0.4.6 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +339 -31
- 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,25 +4608,78 @@ function getSkillPath(platform, slug) {
|
|
|
4608
4608
|
}
|
|
4609
4609
|
return join8(".claude", "skills", slug, "SKILL.md");
|
|
4610
4610
|
}
|
|
4611
|
-
|
|
4612
|
-
const
|
|
4613
|
-
const
|
|
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) {
|
|
4631
|
+
const bestBySlug = /* @__PURE__ */ new Map();
|
|
4614
4632
|
for (const tech of technologies) {
|
|
4615
4633
|
try {
|
|
4616
|
-
const resp = await fetch(`https://
|
|
4634
|
+
const resp = await fetch(`https://skills.sh/api/search?q=${encodeURIComponent(tech)}&limit=10`, {
|
|
4635
|
+
signal: AbortSignal.timeout(1e4)
|
|
4636
|
+
});
|
|
4617
4637
|
if (!resp.ok) continue;
|
|
4618
4638
|
const data = await resp.json();
|
|
4619
4639
|
if (!data.skills?.length) continue;
|
|
4620
4640
|
for (const skill of data.skills) {
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4641
|
+
const existing = bestBySlug.get(skill.skillId);
|
|
4642
|
+
if (existing && existing.installs >= (skill.installs ?? 0)) continue;
|
|
4643
|
+
bestBySlug.set(skill.skillId, {
|
|
4624
4644
|
name: skill.name,
|
|
4625
|
-
slug: skill.
|
|
4626
|
-
source_url: skill.
|
|
4645
|
+
slug: skill.skillId,
|
|
4646
|
+
source_url: skill.source ? `https://github.com/${skill.source}` : "",
|
|
4627
4647
|
score: 0,
|
|
4628
4648
|
reason: skill.description || "",
|
|
4629
4649
|
detected_technology: tech,
|
|
4650
|
+
item_type: "skill",
|
|
4651
|
+
installs: skill.installs ?? 0
|
|
4652
|
+
});
|
|
4653
|
+
}
|
|
4654
|
+
} catch {
|
|
4655
|
+
continue;
|
|
4656
|
+
}
|
|
4657
|
+
}
|
|
4658
|
+
return Array.from(bestBySlug.values());
|
|
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,
|
|
4630
4683
|
item_type: "skill"
|
|
4631
4684
|
});
|
|
4632
4685
|
}
|
|
@@ -4636,12 +4689,200 @@ async function searchSkills(technologies) {
|
|
|
4636
4689
|
}
|
|
4637
4690
|
return results;
|
|
4638
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
|
+
const key = result.name.toLowerCase().replace(/[-_]/g, "");
|
|
4740
|
+
if (seen.has(key)) continue;
|
|
4741
|
+
seen.add(key);
|
|
4742
|
+
combined.push(result);
|
|
4743
|
+
}
|
|
4744
|
+
}
|
|
4745
|
+
return combined;
|
|
4746
|
+
}
|
|
4747
|
+
async function scoreWithLLM(candidates, projectContext, technologies) {
|
|
4748
|
+
const candidateList = candidates.map((c, i) => `${i}. "${c.name}" \u2014 ${c.reason || "no description"}`).join("\n");
|
|
4749
|
+
const scored = await llmJsonCall({
|
|
4750
|
+
system: `You evaluate whether AI agent skills and tools are relevant to a specific software project.
|
|
4751
|
+
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).
|
|
4752
|
+
|
|
4753
|
+
Return a JSON array where each element has:
|
|
4754
|
+
- "index": the candidate's index number
|
|
4755
|
+
- "score": relevance score 0-100
|
|
4756
|
+
- "reason": one-liner explaining why it fits or doesn't
|
|
4757
|
+
|
|
4758
|
+
Scoring guidelines:
|
|
4759
|
+
- 90-100: Directly matches a core technology or workflow in the project
|
|
4760
|
+
- 70-89: Relevant to the project's stack, patterns, or development workflow
|
|
4761
|
+
- 50-69: Tangentially related or generic but useful
|
|
4762
|
+
- 0-49: Not relevant to this project
|
|
4763
|
+
|
|
4764
|
+
Be selective. Prefer specific, high-quality matches over generic ones.
|
|
4765
|
+
A skill for "React testing" is only relevant if the project uses React.
|
|
4766
|
+
A generic "TypeScript best practices" skill is less valuable than one targeting the project's actual framework.
|
|
4767
|
+
Return ONLY the JSON array.`,
|
|
4768
|
+
prompt: `PROJECT CONTEXT:
|
|
4769
|
+
${projectContext}
|
|
4770
|
+
|
|
4771
|
+
DETECTED TECHNOLOGIES:
|
|
4772
|
+
${technologies.join(", ")}
|
|
4773
|
+
|
|
4774
|
+
CANDIDATES:
|
|
4775
|
+
${candidateList}`,
|
|
4776
|
+
maxTokens: 8e3
|
|
4777
|
+
});
|
|
4778
|
+
if (!Array.isArray(scored)) return [];
|
|
4779
|
+
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) => ({
|
|
4780
|
+
...candidates[s.index],
|
|
4781
|
+
score: s.score,
|
|
4782
|
+
reason: s.reason || candidates[s.index].reason
|
|
4783
|
+
}));
|
|
4784
|
+
}
|
|
4785
|
+
function buildProjectContext(dir) {
|
|
4786
|
+
const parts = [];
|
|
4787
|
+
const fingerprint = collectFingerprint(dir);
|
|
4788
|
+
if (fingerprint.packageName) parts.push(`Package: ${fingerprint.packageName}`);
|
|
4789
|
+
if (fingerprint.languages.length > 0) parts.push(`Languages: ${fingerprint.languages.join(", ")}`);
|
|
4790
|
+
if (fingerprint.frameworks.length > 0) parts.push(`Frameworks: ${fingerprint.frameworks.join(", ")}`);
|
|
4791
|
+
if (fingerprint.description) parts.push(`Description: ${fingerprint.description}`);
|
|
4792
|
+
if (fingerprint.fileTree.length > 0) {
|
|
4793
|
+
parts.push(`
|
|
4794
|
+
File tree (${fingerprint.fileTree.length} files):
|
|
4795
|
+
${fingerprint.fileTree.slice(0, 50).join("\n")}`);
|
|
4796
|
+
}
|
|
4797
|
+
if (fingerprint.existingConfigs.claudeMd) {
|
|
4798
|
+
parts.push(`
|
|
4799
|
+
Existing CLAUDE.md (first 500 chars):
|
|
4800
|
+
${fingerprint.existingConfigs.claudeMd.slice(0, 500)}`);
|
|
4801
|
+
}
|
|
4802
|
+
const deps = extractTopDeps();
|
|
4803
|
+
if (deps.length > 0) {
|
|
4804
|
+
parts.push(`
|
|
4805
|
+
Dependencies: ${deps.slice(0, 30).join(", ")}`);
|
|
4806
|
+
}
|
|
4807
|
+
const installed = getInstalledSkills();
|
|
4808
|
+
if (installed.size > 0) {
|
|
4809
|
+
parts.push(`
|
|
4810
|
+
Already installed skills: ${Array.from(installed).join(", ")}`);
|
|
4811
|
+
}
|
|
4812
|
+
return parts.join("\n");
|
|
4813
|
+
}
|
|
4639
4814
|
function extractTopDeps() {
|
|
4640
4815
|
const pkgPath = join8(process.cwd(), "package.json");
|
|
4641
4816
|
if (!existsSync9(pkgPath)) return [];
|
|
4642
4817
|
try {
|
|
4643
4818
|
const pkg3 = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
4644
|
-
|
|
4819
|
+
const deps = Object.keys(pkg3.dependencies ?? {});
|
|
4820
|
+
const trivial = /* @__PURE__ */ new Set([
|
|
4821
|
+
"typescript",
|
|
4822
|
+
"tslib",
|
|
4823
|
+
"ts-node",
|
|
4824
|
+
"tsx",
|
|
4825
|
+
"prettier",
|
|
4826
|
+
"eslint",
|
|
4827
|
+
"@eslint/js",
|
|
4828
|
+
"rimraf",
|
|
4829
|
+
"cross-env",
|
|
4830
|
+
"dotenv",
|
|
4831
|
+
"nodemon",
|
|
4832
|
+
"husky",
|
|
4833
|
+
"lint-staged",
|
|
4834
|
+
"commitlint",
|
|
4835
|
+
"chalk",
|
|
4836
|
+
"ora",
|
|
4837
|
+
"commander",
|
|
4838
|
+
"yargs",
|
|
4839
|
+
"meow",
|
|
4840
|
+
"inquirer",
|
|
4841
|
+
"@inquirer/confirm",
|
|
4842
|
+
"@inquirer/select",
|
|
4843
|
+
"@inquirer/prompts",
|
|
4844
|
+
"glob",
|
|
4845
|
+
"minimatch",
|
|
4846
|
+
"micromatch",
|
|
4847
|
+
"diff",
|
|
4848
|
+
"semver",
|
|
4849
|
+
"uuid",
|
|
4850
|
+
"nanoid",
|
|
4851
|
+
"debug",
|
|
4852
|
+
"ms",
|
|
4853
|
+
"lodash",
|
|
4854
|
+
"underscore",
|
|
4855
|
+
"tsup",
|
|
4856
|
+
"esbuild",
|
|
4857
|
+
"rollup",
|
|
4858
|
+
"webpack",
|
|
4859
|
+
"vite",
|
|
4860
|
+
"vitest",
|
|
4861
|
+
"jest",
|
|
4862
|
+
"mocha",
|
|
4863
|
+
"chai",
|
|
4864
|
+
"ava",
|
|
4865
|
+
"fs-extra",
|
|
4866
|
+
"mkdirp",
|
|
4867
|
+
"del",
|
|
4868
|
+
"rimraf",
|
|
4869
|
+
"path-to-regexp",
|
|
4870
|
+
"strip-ansi",
|
|
4871
|
+
"ansi-colors"
|
|
4872
|
+
]);
|
|
4873
|
+
const trivialPatterns = [
|
|
4874
|
+
/^@types\//,
|
|
4875
|
+
/^@rely-ai\//,
|
|
4876
|
+
/^@caliber-ai\//,
|
|
4877
|
+
/^eslint-/,
|
|
4878
|
+
/^@eslint\//,
|
|
4879
|
+
/^prettier-/,
|
|
4880
|
+
/^@typescript-eslint\//,
|
|
4881
|
+
/^@commitlint\//
|
|
4882
|
+
];
|
|
4883
|
+
return deps.filter(
|
|
4884
|
+
(d) => !trivial.has(d) && !trivialPatterns.some((p) => p.test(d))
|
|
4885
|
+
);
|
|
4645
4886
|
} catch {
|
|
4646
4887
|
return [];
|
|
4647
4888
|
}
|
|
@@ -4649,6 +4890,7 @@ function extractTopDeps() {
|
|
|
4649
4890
|
async function recommendCommand(options) {
|
|
4650
4891
|
const fingerprint = collectFingerprint(process.cwd());
|
|
4651
4892
|
const platforms = detectLocalPlatforms();
|
|
4893
|
+
const installedSkills = getInstalledSkills();
|
|
4652
4894
|
const technologies = [...new Set([
|
|
4653
4895
|
...fingerprint.languages,
|
|
4654
4896
|
...fingerprint.frameworks,
|
|
@@ -4658,13 +4900,41 @@ async function recommendCommand(options) {
|
|
|
4658
4900
|
console.log(chalk8.yellow("Could not detect any languages or dependencies. Try running from a project root."));
|
|
4659
4901
|
throw new Error("__exit__");
|
|
4660
4902
|
}
|
|
4661
|
-
const
|
|
4662
|
-
const
|
|
4663
|
-
|
|
4664
|
-
|
|
4903
|
+
const primaryPlatform = platforms.includes("claude") ? "claude" : platforms[0];
|
|
4904
|
+
const searchSpinner = ora4("Searching skill registries...").start();
|
|
4905
|
+
const allCandidates = await searchAllProviders(technologies, primaryPlatform);
|
|
4906
|
+
if (!allCandidates.length) {
|
|
4907
|
+
searchSpinner.succeed("No skills found matching your tech stack.");
|
|
4908
|
+
return;
|
|
4909
|
+
}
|
|
4910
|
+
const newCandidates = allCandidates.filter((c) => !installedSkills.has(c.slug.toLowerCase()));
|
|
4911
|
+
const filteredCount = allCandidates.length - newCandidates.length;
|
|
4912
|
+
if (!newCandidates.length) {
|
|
4913
|
+
searchSpinner.succeed(`Found ${allCandidates.length} skills \u2014 all already installed.`);
|
|
4665
4914
|
return;
|
|
4666
4915
|
}
|
|
4667
|
-
|
|
4916
|
+
searchSpinner.succeed(
|
|
4917
|
+
`Found ${allCandidates.length} skills` + (filteredCount > 0 ? chalk8.dim(` (${filteredCount} already installed)`) : "")
|
|
4918
|
+
);
|
|
4919
|
+
let results;
|
|
4920
|
+
const config = loadConfig();
|
|
4921
|
+
if (config) {
|
|
4922
|
+
const scoreSpinner = ora4("Scoring relevance for your project...").start();
|
|
4923
|
+
try {
|
|
4924
|
+
const projectContext = buildProjectContext(process.cwd());
|
|
4925
|
+
results = await scoreWithLLM(newCandidates, projectContext, technologies);
|
|
4926
|
+
if (results.length === 0) {
|
|
4927
|
+
scoreSpinner.succeed("No highly relevant skills found for your specific project.");
|
|
4928
|
+
return;
|
|
4929
|
+
}
|
|
4930
|
+
scoreSpinner.succeed(`${results.length} relevant skill${results.length > 1 ? "s" : ""} for your project`);
|
|
4931
|
+
} catch {
|
|
4932
|
+
scoreSpinner.warn("Could not score relevance \u2014 showing top results");
|
|
4933
|
+
results = newCandidates.slice(0, 20);
|
|
4934
|
+
}
|
|
4935
|
+
} else {
|
|
4936
|
+
results = newCandidates.slice(0, 20);
|
|
4937
|
+
}
|
|
4668
4938
|
const selected = await interactiveSelect(results);
|
|
4669
4939
|
if (selected?.length) {
|
|
4670
4940
|
await installSkills(selected, platforms);
|
|
@@ -4679,17 +4949,27 @@ async function interactiveSelect(recs) {
|
|
|
4679
4949
|
let cursor = 0;
|
|
4680
4950
|
const { stdin, stdout } = process;
|
|
4681
4951
|
let lineCount = 0;
|
|
4952
|
+
const hasScores = recs.some((r) => r.score > 0);
|
|
4682
4953
|
function render() {
|
|
4683
4954
|
const lines = [];
|
|
4684
4955
|
lines.push(chalk8.bold(" Recommendations"));
|
|
4685
4956
|
lines.push("");
|
|
4686
|
-
|
|
4957
|
+
if (hasScores) {
|
|
4958
|
+
lines.push(` ${chalk8.dim("Score".padEnd(7))} ${chalk8.dim("Name".padEnd(28))} ${chalk8.dim("Why")}`);
|
|
4959
|
+
} else {
|
|
4960
|
+
lines.push(` ${chalk8.dim("Name".padEnd(30))} ${chalk8.dim("Technology".padEnd(18))} ${chalk8.dim("Source")}`);
|
|
4961
|
+
}
|
|
4687
4962
|
lines.push(chalk8.dim(" " + "\u2500".repeat(70)));
|
|
4688
4963
|
for (let i = 0; i < recs.length; i++) {
|
|
4689
4964
|
const rec = recs[i];
|
|
4690
4965
|
const check = selected.has(i) ? chalk8.green("[x]") : "[ ]";
|
|
4691
|
-
const ptr = i === cursor ? chalk8.cyan("
|
|
4692
|
-
|
|
4966
|
+
const ptr = i === cursor ? chalk8.cyan(">") : " ";
|
|
4967
|
+
if (hasScores) {
|
|
4968
|
+
const scoreColor = rec.score >= 90 ? chalk8.green : rec.score >= 70 ? chalk8.yellow : chalk8.dim;
|
|
4969
|
+
lines.push(` ${ptr} ${check} ${scoreColor(String(rec.score).padStart(3))} ${rec.name.padEnd(26)} ${chalk8.dim(rec.reason.slice(0, 40))}`);
|
|
4970
|
+
} else {
|
|
4971
|
+
lines.push(` ${ptr} ${check} ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk8.dim(rec.source_url || "")}`);
|
|
4972
|
+
}
|
|
4693
4973
|
}
|
|
4694
4974
|
lines.push("");
|
|
4695
4975
|
lines.push(chalk8.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q cancel"));
|
|
@@ -4760,14 +5040,40 @@ async function interactiveSelect(recs) {
|
|
|
4760
5040
|
});
|
|
4761
5041
|
}
|
|
4762
5042
|
async function fetchSkillContent(rec) {
|
|
4763
|
-
if (rec.source_url
|
|
5043
|
+
if (!rec.source_url) return null;
|
|
5044
|
+
const repoPath = rec.source_url.replace("https://github.com/", "");
|
|
5045
|
+
const candidates = [
|
|
5046
|
+
`https://raw.githubusercontent.com/${repoPath}/HEAD/skills/${rec.slug}/SKILL.md`,
|
|
5047
|
+
`https://raw.githubusercontent.com/${repoPath}/HEAD/${rec.slug}/SKILL.md`,
|
|
5048
|
+
`https://raw.githubusercontent.com/${repoPath}/HEAD/.claude/skills/${rec.slug}/SKILL.md`
|
|
5049
|
+
];
|
|
5050
|
+
for (const url of candidates) {
|
|
4764
5051
|
try {
|
|
4765
|
-
const
|
|
4766
|
-
|
|
4767
|
-
|
|
5052
|
+
const resp = await fetch(url, { signal: AbortSignal.timeout(1e4) });
|
|
5053
|
+
if (resp.ok) {
|
|
5054
|
+
const text = await resp.text();
|
|
5055
|
+
if (text.length > 20) return text;
|
|
5056
|
+
}
|
|
4768
5057
|
} catch {
|
|
4769
5058
|
}
|
|
4770
5059
|
}
|
|
5060
|
+
try {
|
|
5061
|
+
const resp = await fetch(
|
|
5062
|
+
`https://api.github.com/repos/${repoPath}/git/trees/HEAD?recursive=1`,
|
|
5063
|
+
{ signal: AbortSignal.timeout(1e4) }
|
|
5064
|
+
);
|
|
5065
|
+
if (resp.ok) {
|
|
5066
|
+
const tree = await resp.json();
|
|
5067
|
+
const needle = `${rec.slug}/SKILL.md`;
|
|
5068
|
+
const match = tree.tree?.find((f) => f.path.endsWith(needle));
|
|
5069
|
+
if (match) {
|
|
5070
|
+
const rawUrl = `https://raw.githubusercontent.com/${repoPath}/HEAD/${match.path}`;
|
|
5071
|
+
const contentResp = await fetch(rawUrl, { signal: AbortSignal.timeout(1e4) });
|
|
5072
|
+
if (contentResp.ok) return await contentResp.text();
|
|
5073
|
+
}
|
|
5074
|
+
}
|
|
5075
|
+
} catch {
|
|
5076
|
+
}
|
|
4771
5077
|
return null;
|
|
4772
5078
|
}
|
|
4773
5079
|
async function installSkills(recs, platforms) {
|
|
@@ -4802,17 +5108,19 @@ async function installSkills(recs, platforms) {
|
|
|
4802
5108
|
console.log("");
|
|
4803
5109
|
}
|
|
4804
5110
|
function printRecommendations(recs) {
|
|
5111
|
+
const hasScores = recs.some((r) => r.score > 0);
|
|
4805
5112
|
console.log(chalk8.bold("\n Recommendations\n"));
|
|
4806
|
-
|
|
4807
|
-
` ${chalk8.dim("
|
|
4808
|
-
|
|
5113
|
+
if (hasScores) {
|
|
5114
|
+
console.log(` ${chalk8.dim("Score".padEnd(7))} ${chalk8.dim("Name".padEnd(28))} ${chalk8.dim("Why")}`);
|
|
5115
|
+
} else {
|
|
5116
|
+
console.log(` ${chalk8.dim("Name".padEnd(30))} ${chalk8.dim("Technology".padEnd(18))} ${chalk8.dim("Source")}`);
|
|
5117
|
+
}
|
|
4809
5118
|
console.log(chalk8.dim(" " + "\u2500".repeat(70)));
|
|
4810
5119
|
for (const rec of recs) {
|
|
4811
|
-
|
|
4812
|
-
` ${rec.
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
console.log(` ${chalk8.dim(" " + rec.reason)}`);
|
|
5120
|
+
if (hasScores) {
|
|
5121
|
+
console.log(` ${String(rec.score).padStart(3)} ${rec.name.padEnd(26)} ${chalk8.dim(rec.reason.slice(0, 50))}`);
|
|
5122
|
+
} else {
|
|
5123
|
+
console.log(` ${rec.name.padEnd(28)} ${rec.detected_technology.padEnd(16)} ${chalk8.dim(rec.source_url || "")}`);
|
|
4816
5124
|
}
|
|
4817
5125
|
}
|
|
4818
5126
|
console.log("");
|
package/package.json
CHANGED