@rely-ai/caliber 0.4.5 → 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.
- package/dist/bin.js +326 -32
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -3102,7 +3102,7 @@ function checkQuality(dir) {
|
|
|
3102
3102
|
earnedPoints: hasCommands ? POINTS_HAS_COMMANDS : 0,
|
|
3103
3103
|
passed: hasCommands,
|
|
3104
3104
|
detail: hasCommands ? `Found: ${matchedCommands.slice(0, 3).join(", ")}` : claudeMd ? "No build/test/lint commands detected" : "No CLAUDE.md to check",
|
|
3105
|
-
suggestion: hasCommands ? void 0 : "Add build, test, and lint commands
|
|
3105
|
+
suggestion: hasCommands ? void 0 : "Add build, test, and lint commands to CLAUDE.md"
|
|
3106
3106
|
});
|
|
3107
3107
|
const primaryFile = claudeMd ?? cursorrules;
|
|
3108
3108
|
const primaryName = claudeMd ? "CLAUDE.md" : cursorrules ? ".cursorrules" : null;
|
|
@@ -3170,7 +3170,7 @@ function checkQuality(dir) {
|
|
|
3170
3170
|
earnedPoints: hasLargeTree ? 0 : POINTS_NO_DIR_TREE,
|
|
3171
3171
|
passed: !hasLargeTree,
|
|
3172
3172
|
detail: hasLargeTree ? `${treeLineCount}-line directory tree detected in code block` : "No large directory trees found",
|
|
3173
|
-
suggestion: hasLargeTree ? "Remove directory tree listings \u2014 agents discover project structure by reading code
|
|
3173
|
+
suggestion: hasLargeTree ? "Remove directory tree listings \u2014 agents discover project structure by reading code" : void 0
|
|
3174
3174
|
});
|
|
3175
3175
|
let duplicatePercent = 0;
|
|
3176
3176
|
if (claudeMd && cursorrules) {
|
|
@@ -3253,7 +3253,7 @@ function validateDocumentedCommands(dir) {
|
|
|
3253
3253
|
const scripts = getPackageScripts(dir);
|
|
3254
3254
|
const valid = [];
|
|
3255
3255
|
const invalid = [];
|
|
3256
|
-
const cmdPattern = /(?:npm|yarn|pnpm|bun)\s+(?:run\s+)?(
|
|
3256
|
+
const cmdPattern = /(?:npm|yarn|pnpm|bun)\s+(?:run\s+)?([a-zA-Z0-9_:@./-]+)/g;
|
|
3257
3257
|
const seen = /* @__PURE__ */ new Set();
|
|
3258
3258
|
let match;
|
|
3259
3259
|
while ((match = cmdPattern.exec(claudeMd)) !== null) {
|
|
@@ -3834,7 +3834,8 @@ async function initCommand(options) {
|
|
|
3834
3834
|
}
|
|
3835
3835
|
console.log(chalk4.green(" \u2713 Provider saved. Continuing with init.\n"));
|
|
3836
3836
|
}
|
|
3837
|
-
|
|
3837
|
+
const displayModel = config.model === "default" && config.provider === "claude-cli" ? process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)" : config.model;
|
|
3838
|
+
console.log(chalk4.dim(` Provider: ${config.provider} | Model: ${displayModel}
|
|
3838
3839
|
`));
|
|
3839
3840
|
console.log(chalk4.hex("#6366f1").bold(" Step 2/4 \u2014 Scan project\n"));
|
|
3840
3841
|
console.log(chalk4.dim(" Detecting languages, dependencies, file structure, and existing configs.\n"));
|
|
@@ -3883,7 +3884,7 @@ async function initCommand(options) {
|
|
|
3883
3884
|
console.log(chalk4.hex("#6366f1").bold(" Step 3/4 \u2014 Generating configs\n"));
|
|
3884
3885
|
console.log(chalk4.dim(" AI is creating agent config files tailored to your project.\n"));
|
|
3885
3886
|
}
|
|
3886
|
-
console.log(chalk4.dim(" This
|
|
3887
|
+
console.log(chalk4.dim(" This can take a couple of minutes depending on your model and provider.\n"));
|
|
3887
3888
|
const genStartTime = Date.now();
|
|
3888
3889
|
const genSpinner = ora("Generating setup...").start();
|
|
3889
3890
|
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
|
|
@@ -4473,7 +4474,7 @@ async function regenerateCommand(options) {
|
|
|
4473
4474
|
// src/commands/recommend.ts
|
|
4474
4475
|
import chalk8 from "chalk";
|
|
4475
4476
|
import ora4 from "ora";
|
|
4476
|
-
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";
|
|
4477
4478
|
import { join as join8, dirname as dirname2 } from "path";
|
|
4478
4479
|
|
|
4479
4480
|
// src/scanner/index.ts
|
|
@@ -4607,22 +4608,43 @@ function getSkillPath(platform, slug) {
|
|
|
4607
4608
|
}
|
|
4608
4609
|
return join8(".claude", "skills", slug, "SKILL.md");
|
|
4609
4610
|
}
|
|
4610
|
-
|
|
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) {
|
|
4611
4631
|
const results = [];
|
|
4612
4632
|
const seen = /* @__PURE__ */ new Set();
|
|
4613
4633
|
for (const tech of technologies) {
|
|
4614
4634
|
try {
|
|
4615
|
-
const resp = await fetch(`https://
|
|
4635
|
+
const resp = await fetch(`https://skills.sh/api/search?q=${encodeURIComponent(tech)}&limit=10`, {
|
|
4636
|
+
signal: AbortSignal.timeout(1e4)
|
|
4637
|
+
});
|
|
4616
4638
|
if (!resp.ok) continue;
|
|
4617
4639
|
const data = await resp.json();
|
|
4618
4640
|
if (!data.skills?.length) continue;
|
|
4619
4641
|
for (const skill of data.skills) {
|
|
4620
|
-
if (seen.has(skill.
|
|
4621
|
-
seen.add(skill.
|
|
4642
|
+
if (seen.has(skill.skillId)) continue;
|
|
4643
|
+
seen.add(skill.skillId);
|
|
4622
4644
|
results.push({
|
|
4623
4645
|
name: skill.name,
|
|
4624
|
-
slug: skill.
|
|
4625
|
-
source_url: skill.
|
|
4646
|
+
slug: skill.skillId,
|
|
4647
|
+
source_url: skill.source ? `https://github.com/${skill.source}` : "",
|
|
4626
4648
|
score: 0,
|
|
4627
4649
|
reason: skill.description || "",
|
|
4628
4650
|
detected_technology: tech,
|
|
@@ -4635,12 +4657,231 @@ async function searchSkills(technologies) {
|
|
|
4635
4657
|
}
|
|
4636
4658
|
return results;
|
|
4637
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
|
+
}
|
|
4638
4813
|
function extractTopDeps() {
|
|
4639
4814
|
const pkgPath = join8(process.cwd(), "package.json");
|
|
4640
4815
|
if (!existsSync9(pkgPath)) return [];
|
|
4641
4816
|
try {
|
|
4642
4817
|
const pkg3 = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
4643
|
-
|
|
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
|
+
);
|
|
4644
4885
|
} catch {
|
|
4645
4886
|
return [];
|
|
4646
4887
|
}
|
|
@@ -4648,6 +4889,7 @@ function extractTopDeps() {
|
|
|
4648
4889
|
async function recommendCommand(options) {
|
|
4649
4890
|
const fingerprint = collectFingerprint(process.cwd());
|
|
4650
4891
|
const platforms = detectLocalPlatforms();
|
|
4892
|
+
const installedSkills = getInstalledSkills();
|
|
4651
4893
|
const technologies = [...new Set([
|
|
4652
4894
|
...fingerprint.languages,
|
|
4653
4895
|
...fingerprint.frameworks,
|
|
@@ -4657,13 +4899,41 @@ async function recommendCommand(options) {
|
|
|
4657
4899
|
console.log(chalk8.yellow("Could not detect any languages or dependencies. Try running from a project root."));
|
|
4658
4900
|
throw new Error("__exit__");
|
|
4659
4901
|
}
|
|
4660
|
-
const
|
|
4661
|
-
const
|
|
4662
|
-
|
|
4663
|
-
|
|
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.`);
|
|
4664
4913
|
return;
|
|
4665
4914
|
}
|
|
4666
|
-
|
|
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
|
+
}
|
|
4667
4937
|
const selected = await interactiveSelect(results);
|
|
4668
4938
|
if (selected?.length) {
|
|
4669
4939
|
await installSkills(selected, platforms);
|
|
@@ -4678,17 +4948,27 @@ async function interactiveSelect(recs) {
|
|
|
4678
4948
|
let cursor = 0;
|
|
4679
4949
|
const { stdin, stdout } = process;
|
|
4680
4950
|
let lineCount = 0;
|
|
4951
|
+
const hasScores = recs.some((r) => r.score > 0);
|
|
4681
4952
|
function render() {
|
|
4682
4953
|
const lines = [];
|
|
4683
4954
|
lines.push(chalk8.bold(" Recommendations"));
|
|
4684
4955
|
lines.push("");
|
|
4685
|
-
|
|
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
|
+
}
|
|
4686
4961
|
lines.push(chalk8.dim(" " + "\u2500".repeat(70)));
|
|
4687
4962
|
for (let i = 0; i < recs.length; i++) {
|
|
4688
4963
|
const rec = recs[i];
|
|
4689
4964
|
const check = selected.has(i) ? chalk8.green("[x]") : "[ ]";
|
|
4690
|
-
const ptr = i === cursor ? chalk8.cyan("
|
|
4691
|
-
|
|
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
|
+
}
|
|
4692
4972
|
}
|
|
4693
4973
|
lines.push("");
|
|
4694
4974
|
lines.push(chalk8.dim(" \u2191\u2193 navigate \u23B5 toggle a all n none \u23CE install q cancel"));
|
|
@@ -4759,10 +5039,22 @@ async function interactiveSelect(recs) {
|
|
|
4759
5039
|
});
|
|
4760
5040
|
}
|
|
4761
5041
|
async function fetchSkillContent(rec) {
|
|
4762
|
-
|
|
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) {
|
|
4763
5054
|
try {
|
|
4764
|
-
const
|
|
4765
|
-
const
|
|
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) });
|
|
4766
5058
|
if (resp.ok) return await resp.text();
|
|
4767
5059
|
} catch {
|
|
4768
5060
|
}
|
|
@@ -4801,17 +5093,19 @@ async function installSkills(recs, platforms) {
|
|
|
4801
5093
|
console.log("");
|
|
4802
5094
|
}
|
|
4803
5095
|
function printRecommendations(recs) {
|
|
5096
|
+
const hasScores = recs.some((r) => r.score > 0);
|
|
4804
5097
|
console.log(chalk8.bold("\n Recommendations\n"));
|
|
4805
|
-
|
|
4806
|
-
` ${chalk8.dim("
|
|
4807
|
-
|
|
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
|
+
}
|
|
4808
5103
|
console.log(chalk8.dim(" " + "\u2500".repeat(70)));
|
|
4809
5104
|
for (const rec of recs) {
|
|
4810
|
-
|
|
4811
|
-
` ${rec.
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
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 || "")}`);
|
|
4815
5109
|
}
|
|
4816
5110
|
}
|
|
4817
5111
|
console.log("");
|
package/package.json
CHANGED