skillmesh-mcp 1.0.0 → 1.0.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/index.js +273 -169
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { z } from 'zod';
|
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import * as os2 from 'os';
|
|
8
8
|
import fs from 'fs-extra';
|
|
9
|
+
import { spawnSync } from 'child_process';
|
|
9
10
|
import * as yaml from 'js-yaml';
|
|
10
11
|
|
|
11
12
|
var { existsSync } = fs;
|
|
@@ -308,93 +309,6 @@ async function createFolder(input) {
|
|
|
308
309
|
}
|
|
309
310
|
}
|
|
310
311
|
|
|
311
|
-
// src/lib/tavily.ts
|
|
312
|
-
async function searchTavily(query, maxResults = 10) {
|
|
313
|
-
const tavilyKey = process.env.TAVILY_API_KEY;
|
|
314
|
-
if (!tavilyKey) {
|
|
315
|
-
console.error("TAVILY_API_KEY not found in environment, trying Firecrawl fallback");
|
|
316
|
-
return searchFirecrawl(query, maxResults);
|
|
317
|
-
}
|
|
318
|
-
try {
|
|
319
|
-
const controller = new AbortController();
|
|
320
|
-
const timeoutId = setTimeout(() => controller.abort(), 15e3);
|
|
321
|
-
const response = await fetch("https://api.tavily.com/search", {
|
|
322
|
-
method: "POST",
|
|
323
|
-
headers: {
|
|
324
|
-
"Content-Type": "application/json"
|
|
325
|
-
},
|
|
326
|
-
body: JSON.stringify({
|
|
327
|
-
api_key: tavilyKey,
|
|
328
|
-
query,
|
|
329
|
-
max_results: maxResults,
|
|
330
|
-
search_depth: "basic",
|
|
331
|
-
include_answer: false,
|
|
332
|
-
include_images: false
|
|
333
|
-
}),
|
|
334
|
-
signal: controller.signal
|
|
335
|
-
});
|
|
336
|
-
clearTimeout(timeoutId);
|
|
337
|
-
if (!response.ok) {
|
|
338
|
-
console.error("Tavily API error:", response.status, response.statusText);
|
|
339
|
-
return [];
|
|
340
|
-
}
|
|
341
|
-
const data = await response.json();
|
|
342
|
-
if (!data.results || !Array.isArray(data.results)) {
|
|
343
|
-
return [];
|
|
344
|
-
}
|
|
345
|
-
return data.results.map((result) => ({
|
|
346
|
-
title: result.title || "",
|
|
347
|
-
url: result.url || "",
|
|
348
|
-
snippet: result.content || "",
|
|
349
|
-
score: result.score
|
|
350
|
-
}));
|
|
351
|
-
} catch (error) {
|
|
352
|
-
console.error("Tavily search failed:", error);
|
|
353
|
-
return [];
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
async function searchFirecrawl(query, maxResults = 10) {
|
|
357
|
-
const firecrawlKey = process.env.FIRECRAWL_API_KEY;
|
|
358
|
-
if (!firecrawlKey) {
|
|
359
|
-
console.error("Neither TAVILY_API_KEY nor FIRECRAWL_API_KEY found in environment");
|
|
360
|
-
return [];
|
|
361
|
-
}
|
|
362
|
-
try {
|
|
363
|
-
const controller = new AbortController();
|
|
364
|
-
const timeoutId = setTimeout(() => controller.abort(), 15e3);
|
|
365
|
-
const response = await fetch("https://api.firecrawl.dev/v1/search", {
|
|
366
|
-
method: "POST",
|
|
367
|
-
headers: {
|
|
368
|
-
"Content-Type": "application/json",
|
|
369
|
-
"Authorization": `Bearer ${firecrawlKey}`
|
|
370
|
-
},
|
|
371
|
-
body: JSON.stringify({
|
|
372
|
-
query,
|
|
373
|
-
limit: maxResults
|
|
374
|
-
}),
|
|
375
|
-
signal: controller.signal
|
|
376
|
-
});
|
|
377
|
-
clearTimeout(timeoutId);
|
|
378
|
-
if (!response.ok) {
|
|
379
|
-
console.error("Firecrawl API error:", response.status, response.statusText);
|
|
380
|
-
return [];
|
|
381
|
-
}
|
|
382
|
-
const data = await response.json();
|
|
383
|
-
if (!data.data || !Array.isArray(data.data)) {
|
|
384
|
-
return [];
|
|
385
|
-
}
|
|
386
|
-
return data.data.map((result) => ({
|
|
387
|
-
title: result.title || "",
|
|
388
|
-
url: result.url || "",
|
|
389
|
-
snippet: result.description || result.content || "",
|
|
390
|
-
score: result.score
|
|
391
|
-
}));
|
|
392
|
-
} catch (error) {
|
|
393
|
-
console.error("Firecrawl search failed:", error);
|
|
394
|
-
return [];
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
312
|
// src/lib/github.ts
|
|
399
313
|
async function fetchSkillFromGitHub(owner, repo, skillName, githubToken) {
|
|
400
314
|
const baseUrl = `https://raw.githubusercontent.com/${owner}/${repo}/main`;
|
|
@@ -578,64 +492,43 @@ var SearchSkillsInputSchema = z.object({
|
|
|
578
492
|
async function searchSkills(input) {
|
|
579
493
|
try {
|
|
580
494
|
const { keyword, minStars, maxAgeMonths, limit } = input;
|
|
581
|
-
const searchQuery = `site:skills.sh ${keyword}`;
|
|
582
|
-
const searchResults = await searchTavily(searchQuery, limit * 3);
|
|
583
|
-
const qualityResults = [];
|
|
584
495
|
const githubToken = process.env.GITHUB_TOKEN;
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
const recencyBonus = lastUpdated ? Math.max(0, 100 - (Date.now() - lastUpdated.getTime()) / (1e3 * 60 * 60 * 24 * 30)) : 0;
|
|
600
|
-
const score = stars + recencyBonus + (result.score || 0) * 10;
|
|
601
|
-
qualityResults.push({
|
|
602
|
-
name: repo2,
|
|
603
|
-
repo: repo2,
|
|
604
|
-
owner: owner2,
|
|
605
|
-
description: result.snippet,
|
|
606
|
-
stars,
|
|
607
|
-
lastUpdated: lastUpdated ? lastUpdated.toISOString() : "unknown",
|
|
608
|
-
url: result.url,
|
|
609
|
-
score
|
|
610
|
-
});
|
|
611
|
-
} catch (err) {
|
|
612
|
-
continue;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
continue;
|
|
616
|
-
}
|
|
617
|
-
const [, owner, repo] = githubMatch;
|
|
496
|
+
let rawResults = await searchViaNpxSkillsFind(keyword);
|
|
497
|
+
if (rawResults.length === 0) {
|
|
498
|
+
rawResults = await searchViaSkillScout(keyword);
|
|
499
|
+
}
|
|
500
|
+
if (rawResults.length === 0) {
|
|
501
|
+
return {
|
|
502
|
+
success: true,
|
|
503
|
+
results: [],
|
|
504
|
+
query: keyword,
|
|
505
|
+
totalFound: 0
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
const qualityResults = [];
|
|
509
|
+
for (const result of rawResults) {
|
|
618
510
|
try {
|
|
619
|
-
const stars = await getGitHubStars(owner, repo, githubToken);
|
|
620
|
-
const lastUpdated = await getLastUpdated(owner, repo, githubToken);
|
|
511
|
+
const stars = await getGitHubStars(result.owner, result.repo, githubToken);
|
|
512
|
+
const lastUpdated = await getLastUpdated(result.owner, result.repo, githubToken);
|
|
621
513
|
if (stars < minStars) continue;
|
|
622
514
|
if (lastUpdated) {
|
|
623
515
|
const ageMonths = (Date.now() - lastUpdated.getTime()) / (1e3 * 60 * 60 * 24 * 30);
|
|
624
516
|
if (ageMonths > maxAgeMonths) continue;
|
|
625
517
|
}
|
|
626
518
|
const recencyBonus = lastUpdated ? Math.max(0, 100 - (Date.now() - lastUpdated.getTime()) / (1e3 * 60 * 60 * 24 * 30)) : 0;
|
|
627
|
-
const score = stars + recencyBonus
|
|
519
|
+
const score = stars + recencyBonus;
|
|
628
520
|
qualityResults.push({
|
|
629
|
-
name:
|
|
630
|
-
repo,
|
|
631
|
-
owner,
|
|
632
|
-
description: result.
|
|
521
|
+
name: result.skillName,
|
|
522
|
+
repo: result.repo,
|
|
523
|
+
owner: result.owner,
|
|
524
|
+
description: result.description || "",
|
|
633
525
|
stars,
|
|
634
526
|
lastUpdated: lastUpdated ? lastUpdated.toISOString() : "unknown",
|
|
635
|
-
url: result.
|
|
636
|
-
score
|
|
527
|
+
url: `https://github.com/${result.owner}/${result.repo}`,
|
|
528
|
+
score,
|
|
529
|
+
installRef: `${result.owner}/${result.repo}@${result.skillName}`
|
|
637
530
|
});
|
|
638
|
-
} catch
|
|
531
|
+
} catch {
|
|
639
532
|
continue;
|
|
640
533
|
}
|
|
641
534
|
}
|
|
@@ -658,6 +551,69 @@ async function searchSkills(input) {
|
|
|
658
551
|
};
|
|
659
552
|
}
|
|
660
553
|
}
|
|
554
|
+
async function searchViaNpxSkillsFind(keyword) {
|
|
555
|
+
try {
|
|
556
|
+
const result = spawnSync("npx", ["skills", "find", keyword], {
|
|
557
|
+
encoding: "utf-8",
|
|
558
|
+
timeout: 3e4,
|
|
559
|
+
// 30 second timeout
|
|
560
|
+
env: { ...process.env }
|
|
561
|
+
});
|
|
562
|
+
if (result.error || result.status !== 0) {
|
|
563
|
+
return [];
|
|
564
|
+
}
|
|
565
|
+
const output = result.stdout || "";
|
|
566
|
+
const results = [];
|
|
567
|
+
const lines = output.split("\n").filter((line) => line.trim());
|
|
568
|
+
for (const line of lines) {
|
|
569
|
+
const match = line.match(/^\s*([^\/\s]+)\/([^@\s]+)@([^\s-]+)(?:\s*-\s*(.*))?/);
|
|
570
|
+
if (match) {
|
|
571
|
+
const [, owner, repo, skillName, description] = match;
|
|
572
|
+
results.push({
|
|
573
|
+
owner,
|
|
574
|
+
repo,
|
|
575
|
+
skillName,
|
|
576
|
+
description: description?.trim()
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return results;
|
|
581
|
+
} catch {
|
|
582
|
+
return [];
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
async function searchViaSkillScout(keyword) {
|
|
586
|
+
try {
|
|
587
|
+
const controller = new AbortController();
|
|
588
|
+
const timeoutId = setTimeout(() => controller.abort(), 15e3);
|
|
589
|
+
const response = await fetch(
|
|
590
|
+
`https://api.skillscout.dev/v1/search?q=${encodeURIComponent(keyword)}`,
|
|
591
|
+
{
|
|
592
|
+
signal: controller.signal,
|
|
593
|
+
headers: {
|
|
594
|
+
"Accept": "application/json",
|
|
595
|
+
"User-Agent": "skillmesh-mcp/1.0.0"
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
);
|
|
599
|
+
clearTimeout(timeoutId);
|
|
600
|
+
if (!response.ok) {
|
|
601
|
+
return [];
|
|
602
|
+
}
|
|
603
|
+
const data = await response.json();
|
|
604
|
+
if (!data.results || !Array.isArray(data.results)) {
|
|
605
|
+
return [];
|
|
606
|
+
}
|
|
607
|
+
return data.results.filter((r) => r.owner && r.repo && (r.skill_name || r.name)).map((r) => ({
|
|
608
|
+
owner: r.owner,
|
|
609
|
+
repo: r.repo,
|
|
610
|
+
skillName: r.skill_name || r.name,
|
|
611
|
+
description: r.description
|
|
612
|
+
}));
|
|
613
|
+
} catch {
|
|
614
|
+
return [];
|
|
615
|
+
}
|
|
616
|
+
}
|
|
661
617
|
var SkillMetadataSchema = z.object({
|
|
662
618
|
name: z.string().min(1, "Skill name is required"),
|
|
663
619
|
description: z.string().min(1, "Description is required"),
|
|
@@ -783,15 +739,43 @@ function checkSkillQuality(parsed) {
|
|
|
783
739
|
|
|
784
740
|
// src/tools/fetch-skill.ts
|
|
785
741
|
var FetchSkillInputSchema = z.object({
|
|
786
|
-
owner: z.string().describe("GitHub repository owner"),
|
|
787
|
-
repo: z.string().describe("GitHub repository name"),
|
|
788
|
-
skillName: z.string().describe("Skill name to fetch"),
|
|
789
|
-
githubToken: z.string().optional().describe("Optional GitHub token for higher rate limits")
|
|
742
|
+
owner: z.string().optional().describe("GitHub repository owner"),
|
|
743
|
+
repo: z.string().optional().describe("GitHub repository name"),
|
|
744
|
+
skillName: z.string().optional().describe("Skill name to fetch"),
|
|
745
|
+
githubToken: z.string().optional().describe("Optional GitHub token for higher rate limits"),
|
|
746
|
+
/** Direct install reference in format owner/repo@skillname - if provided, parses owner/repo/skillName from it */
|
|
747
|
+
installRef: z.string().optional().describe("Install reference in format owner/repo@skillname")
|
|
790
748
|
});
|
|
791
749
|
async function fetchSkillContent(input) {
|
|
792
750
|
try {
|
|
793
|
-
|
|
751
|
+
let { owner, repo, skillName, githubToken, installRef } = input;
|
|
752
|
+
if (installRef) {
|
|
753
|
+
const match = installRef.match(/^([^\/]+)\/([^@]+)@(.+)$/);
|
|
754
|
+
if (match) {
|
|
755
|
+
owner = match[1];
|
|
756
|
+
repo = match[2];
|
|
757
|
+
skillName = match[3];
|
|
758
|
+
} else {
|
|
759
|
+
return {
|
|
760
|
+
success: false,
|
|
761
|
+
content: "",
|
|
762
|
+
sourceUrl: "",
|
|
763
|
+
found: false,
|
|
764
|
+
error: `Invalid installRef format: "${installRef}". Expected format: owner/repo@skillname`
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if (!owner || !repo || !skillName) {
|
|
769
|
+
return {
|
|
770
|
+
success: false,
|
|
771
|
+
content: "",
|
|
772
|
+
sourceUrl: "",
|
|
773
|
+
found: false,
|
|
774
|
+
error: "Missing required fields: owner, repo, and skillName (or provide installRef)"
|
|
775
|
+
};
|
|
776
|
+
}
|
|
794
777
|
const token = githubToken || process.env.GITHUB_TOKEN;
|
|
778
|
+
const resolvedInstallRef = `${owner}/${repo}@${skillName}`;
|
|
795
779
|
const fetchResult = await fetchSkillFromGitHub(owner, repo, skillName, token);
|
|
796
780
|
if (!fetchResult.found || !fetchResult.content) {
|
|
797
781
|
return {
|
|
@@ -799,6 +783,7 @@ async function fetchSkillContent(input) {
|
|
|
799
783
|
content: "",
|
|
800
784
|
sourceUrl: fetchResult.url,
|
|
801
785
|
found: false,
|
|
786
|
+
installRef: resolvedInstallRef,
|
|
802
787
|
error: "SKILL.md not found in repository"
|
|
803
788
|
};
|
|
804
789
|
}
|
|
@@ -811,6 +796,7 @@ async function fetchSkillContent(input) {
|
|
|
811
796
|
content: fetchResult.content,
|
|
812
797
|
sourceUrl: fetchResult.url,
|
|
813
798
|
found: true,
|
|
799
|
+
installRef: resolvedInstallRef,
|
|
814
800
|
quality: {
|
|
815
801
|
valid: false,
|
|
816
802
|
issues: ["Failed to parse SKILL.md frontmatter"]
|
|
@@ -822,6 +808,7 @@ async function fetchSkillContent(input) {
|
|
|
822
808
|
content: fetchResult.content,
|
|
823
809
|
sourceUrl: fetchResult.url,
|
|
824
810
|
found: true,
|
|
811
|
+
installRef: resolvedInstallRef,
|
|
825
812
|
metadata: {
|
|
826
813
|
name: parsed.metadata.name,
|
|
827
814
|
description: parsed.metadata.description,
|
|
@@ -840,6 +827,7 @@ async function fetchSkillContent(input) {
|
|
|
840
827
|
content: fetchResult.content,
|
|
841
828
|
sourceUrl: fetchResult.url,
|
|
842
829
|
found: true,
|
|
830
|
+
installRef: resolvedInstallRef,
|
|
843
831
|
quality: {
|
|
844
832
|
valid: false,
|
|
845
833
|
issues: [`Failed to parse SKILL.md: ${parseMessage}`]
|
|
@@ -863,6 +851,93 @@ function sanitizeSkillName(name) {
|
|
|
863
851
|
return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
864
852
|
}
|
|
865
853
|
|
|
854
|
+
// src/lib/tavily.ts
|
|
855
|
+
async function searchTavily(query, maxResults = 10) {
|
|
856
|
+
const tavilyKey = process.env.TAVILY_API_KEY;
|
|
857
|
+
if (!tavilyKey) {
|
|
858
|
+
console.error("TAVILY_API_KEY not found in environment, trying Firecrawl fallback");
|
|
859
|
+
return searchFirecrawl(query, maxResults);
|
|
860
|
+
}
|
|
861
|
+
try {
|
|
862
|
+
const controller = new AbortController();
|
|
863
|
+
const timeoutId = setTimeout(() => controller.abort(), 15e3);
|
|
864
|
+
const response = await fetch("https://api.tavily.com/search", {
|
|
865
|
+
method: "POST",
|
|
866
|
+
headers: {
|
|
867
|
+
"Content-Type": "application/json"
|
|
868
|
+
},
|
|
869
|
+
body: JSON.stringify({
|
|
870
|
+
api_key: tavilyKey,
|
|
871
|
+
query,
|
|
872
|
+
max_results: maxResults,
|
|
873
|
+
search_depth: "basic",
|
|
874
|
+
include_answer: false,
|
|
875
|
+
include_images: false
|
|
876
|
+
}),
|
|
877
|
+
signal: controller.signal
|
|
878
|
+
});
|
|
879
|
+
clearTimeout(timeoutId);
|
|
880
|
+
if (!response.ok) {
|
|
881
|
+
console.error("Tavily API error:", response.status, response.statusText);
|
|
882
|
+
return [];
|
|
883
|
+
}
|
|
884
|
+
const data = await response.json();
|
|
885
|
+
if (!data.results || !Array.isArray(data.results)) {
|
|
886
|
+
return [];
|
|
887
|
+
}
|
|
888
|
+
return data.results.map((result) => ({
|
|
889
|
+
title: result.title || "",
|
|
890
|
+
url: result.url || "",
|
|
891
|
+
snippet: result.content || "",
|
|
892
|
+
score: result.score
|
|
893
|
+
}));
|
|
894
|
+
} catch (error) {
|
|
895
|
+
console.error("Tavily search failed:", error);
|
|
896
|
+
return [];
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
async function searchFirecrawl(query, maxResults = 10) {
|
|
900
|
+
const firecrawlKey = process.env.FIRECRAWL_API_KEY;
|
|
901
|
+
if (!firecrawlKey) {
|
|
902
|
+
console.error("Neither TAVILY_API_KEY nor FIRECRAWL_API_KEY found in environment");
|
|
903
|
+
return [];
|
|
904
|
+
}
|
|
905
|
+
try {
|
|
906
|
+
const controller = new AbortController();
|
|
907
|
+
const timeoutId = setTimeout(() => controller.abort(), 15e3);
|
|
908
|
+
const response = await fetch("https://api.firecrawl.dev/v1/search", {
|
|
909
|
+
method: "POST",
|
|
910
|
+
headers: {
|
|
911
|
+
"Content-Type": "application/json",
|
|
912
|
+
"Authorization": `Bearer ${firecrawlKey}`
|
|
913
|
+
},
|
|
914
|
+
body: JSON.stringify({
|
|
915
|
+
query,
|
|
916
|
+
limit: maxResults
|
|
917
|
+
}),
|
|
918
|
+
signal: controller.signal
|
|
919
|
+
});
|
|
920
|
+
clearTimeout(timeoutId);
|
|
921
|
+
if (!response.ok) {
|
|
922
|
+
console.error("Firecrawl API error:", response.status, response.statusText);
|
|
923
|
+
return [];
|
|
924
|
+
}
|
|
925
|
+
const data = await response.json();
|
|
926
|
+
if (!data.data || !Array.isArray(data.data)) {
|
|
927
|
+
return [];
|
|
928
|
+
}
|
|
929
|
+
return data.data.map((result) => ({
|
|
930
|
+
title: result.title || "",
|
|
931
|
+
url: result.url || "",
|
|
932
|
+
snippet: result.description || result.content || "",
|
|
933
|
+
score: result.score
|
|
934
|
+
}));
|
|
935
|
+
} catch (error) {
|
|
936
|
+
console.error("Firecrawl search failed:", error);
|
|
937
|
+
return [];
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
866
941
|
// src/lib/llm.ts
|
|
867
942
|
var PROVIDER_MODELS = {
|
|
868
943
|
anthropic: "claude-sonnet-4-5-20250929",
|
|
@@ -1188,14 +1263,27 @@ var InstallSkillInputSchema = z.object({
|
|
|
1188
1263
|
owner: z.string().optional().describe("GitHub owner (for type: github)"),
|
|
1189
1264
|
repo: z.string().optional().describe("GitHub repo (for type: github)"),
|
|
1190
1265
|
content: z.string().optional().describe("Direct SKILL.md content (for type: content)"),
|
|
1191
|
-
stars: z.number().optional().describe("GitHub star count (for quality check)")
|
|
1266
|
+
stars: z.number().optional().describe("GitHub star count (for quality check)"),
|
|
1267
|
+
/** Direct install reference in format owner/repo@skillname - if provided, parses owner/repo from it */
|
|
1268
|
+
installRef: z.string().optional().describe("Install reference in format owner/repo@skillname")
|
|
1192
1269
|
}).describe("Skill source"),
|
|
1193
1270
|
overwrite: z.boolean().optional().default(false).describe("Overwrite existing skill if present"),
|
|
1194
|
-
global: z.boolean().optional().default(false).describe("Install to global scope")
|
|
1271
|
+
global: z.boolean().optional().default(false).describe("Install to global scope"),
|
|
1272
|
+
/** Rich project context for PATH C (LLM generation) - include framework versions, patterns, folder structure */
|
|
1273
|
+
projectContext: z.string().optional().describe("Rich project context for skill generation (frameworks, versions, patterns, folder structure)")
|
|
1195
1274
|
});
|
|
1196
1275
|
async function installSkill(input) {
|
|
1197
1276
|
try {
|
|
1198
|
-
const { projectPath, skillName, agents, source, overwrite, global } = input;
|
|
1277
|
+
const { projectPath, skillName, agents, source, overwrite, global, projectContext } = input;
|
|
1278
|
+
let sourceOwner = source.owner;
|
|
1279
|
+
let sourceRepo = source.repo;
|
|
1280
|
+
if (source.installRef && source.type === "github") {
|
|
1281
|
+
const match = source.installRef.match(/^([^\/]+)\/([^@]+)@(.+)$/);
|
|
1282
|
+
if (match) {
|
|
1283
|
+
sourceOwner = match[1];
|
|
1284
|
+
sourceRepo = match[2];
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1199
1287
|
const sanitizedSkillName = sanitizeSkillName(skillName);
|
|
1200
1288
|
if (!sanitizedSkillName || sanitizedSkillName.length === 0) {
|
|
1201
1289
|
return {
|
|
@@ -1227,20 +1315,20 @@ async function installSkill(input) {
|
|
|
1227
1315
|
};
|
|
1228
1316
|
}
|
|
1229
1317
|
}
|
|
1230
|
-
if (source.type === "github" &&
|
|
1318
|
+
if (source.type === "github" && sourceOwner && sourceRepo) {
|
|
1231
1319
|
const stars = source.stars ?? 0;
|
|
1232
1320
|
if (stars >= 50) {
|
|
1233
|
-
return executePathA(
|
|
1321
|
+
return executePathA(sourceOwner, sourceRepo, sanitizedSkillName, agents, projectPath, global);
|
|
1234
1322
|
}
|
|
1235
1323
|
}
|
|
1236
1324
|
let skillContent = null;
|
|
1237
1325
|
if (source.type === "content" && source.content) {
|
|
1238
1326
|
skillContent = source.content;
|
|
1239
|
-
} else if (source.type === "github" &&
|
|
1327
|
+
} else if (source.type === "github" && sourceOwner && sourceRepo) {
|
|
1240
1328
|
const githubToken = process.env.GITHUB_TOKEN;
|
|
1241
1329
|
const fetchResult = await fetchSkillFromGitHub(
|
|
1242
|
-
|
|
1243
|
-
|
|
1330
|
+
sourceOwner,
|
|
1331
|
+
sourceRepo,
|
|
1244
1332
|
sanitizedSkillName,
|
|
1245
1333
|
githubToken
|
|
1246
1334
|
);
|
|
@@ -1256,10 +1344,10 @@ async function installSkill(input) {
|
|
|
1256
1344
|
projectPath,
|
|
1257
1345
|
global,
|
|
1258
1346
|
overwrite,
|
|
1259
|
-
source.type === "github" ? `${
|
|
1347
|
+
source.type === "github" ? `${sourceOwner}/${sourceRepo}` : "direct-content"
|
|
1260
1348
|
);
|
|
1261
1349
|
}
|
|
1262
|
-
return await executePathC(skillName, sanitizedSkillName, agents, projectPath, global, overwrite);
|
|
1350
|
+
return await executePathC(skillName, sanitizedSkillName, agents, projectPath, global, overwrite, projectContext);
|
|
1263
1351
|
} catch (err) {
|
|
1264
1352
|
const message = err instanceof Error ? err.message : String(err);
|
|
1265
1353
|
return {
|
|
@@ -1273,8 +1361,7 @@ async function installSkill(input) {
|
|
|
1273
1361
|
function executePathA(owner, repo, skillName, agents, projectPath, global) {
|
|
1274
1362
|
const agentFlags = agents.map((agent) => `--agent ${agent}`).join(" ");
|
|
1275
1363
|
const globalFlag = global ? "-g" : "";
|
|
1276
|
-
const
|
|
1277
|
-
const command = `npx skills add ${repoPath} --skill ${skillName} ${agentFlags} ${globalFlag} -y`.trim().replace(/\s+/g, " ");
|
|
1364
|
+
const command = `npx skills add ${owner}/${repo}@${skillName} ${agentFlags} ${globalFlag} -y`.trim().replace(/\s+/g, " ");
|
|
1278
1365
|
const envNote = process.env.GITHUB_TOKEN ? "GITHUB_TOKEN is available in environment (higher rate limits)." : "Note: Set GITHUB_TOKEN environment variable for higher GitHub API rate limits.";
|
|
1279
1366
|
const instruction = `Execute this command in your terminal now. The command will install the skill to all specified agents without any interactive prompts.
|
|
1280
1367
|
|
|
@@ -1358,9 +1445,9 @@ async function executePathB(content, skillName, agents, projectPath, global, ove
|
|
|
1358
1445
|
error: allSucceeded ? void 0 : "Some installations failed. Check individual agent results."
|
|
1359
1446
|
};
|
|
1360
1447
|
}
|
|
1361
|
-
async function executePathC(originalSkillName, sanitizedSkillName, agents, projectPath, global, overwrite) {
|
|
1448
|
+
async function executePathC(originalSkillName, sanitizedSkillName, agents, projectPath, global, overwrite, providedContext) {
|
|
1362
1449
|
try {
|
|
1363
|
-
const projectContext = `Generating skill for: ${originalSkillName}
|
|
1450
|
+
const projectContext = providedContext || `Generating skill for: ${originalSkillName}
|
|
1364
1451
|
Project path: ${projectPath}
|
|
1365
1452
|
Target agents: ${agents.join(", ")}`;
|
|
1366
1453
|
const generateResult = await generateSkill({
|
|
@@ -1597,11 +1684,11 @@ A SKILL.md file that lives in your agent's skill directory. You read all SKILL.m
|
|
|
1597
1684
|
|
|
1598
1685
|
2. create_skills_folder \u2014 Initializes skill directory structure for selected agents. Creates .claude/skills/, .cursor/skills/, etc. Also creates .skillmesh/skillmesh.config.json for tracking.
|
|
1599
1686
|
|
|
1600
|
-
3. search_skills \u2014 Searches skills
|
|
1687
|
+
3. search_skills \u2014 Searches for skills using npx skills find (primary) with SkillScout.dev API fallback. Fetches GitHub star count and recency. Returns ranked list filtered by quality (stars >= 50, updated within 12 months). Each result includes an installRef field for direct use with install_skill.
|
|
1601
1688
|
|
|
1602
|
-
4. fetch_skill_content \u2014 Fetches full SKILL.md content from a GitHub repository using raw URL fallbacks. Validates YAML frontmatter. Returns content, source URL, and parsed metadata.
|
|
1689
|
+
4. fetch_skill_content \u2014 Fetches full SKILL.md content from a GitHub repository using raw URL fallbacks. Accepts either owner/repo/skillName OR installRef (format: owner/repo@skillname). Validates YAML frontmatter. Returns content, source URL, installRef, and parsed metadata.
|
|
1603
1690
|
|
|
1604
|
-
5. install_skill \u2014 Core installation tool. PATH A: returns npx skills add command string for you to execute via your bash tool. PATH B: writes SKILL.md directly to disk if npx fails. PATH C: calls generate_skill if both fail. Handles all selected agents simultaneously.
|
|
1691
|
+
5. install_skill \u2014 Core installation tool. Accepts source.installRef (format: owner/repo@skillname) OR source.owner/source.repo. Also accepts projectContext for PATH C generation. PATH A: returns npx skills add command string for you to execute via your bash tool. PATH B: writes SKILL.md directly to disk if npx fails. PATH C: calls generate_skill if both fail. Handles all selected agents simultaneously.
|
|
1605
1692
|
|
|
1606
1693
|
6. generate_skill \u2014 LLM-powered skill generation. Searches web via Tavily for best practices, sends research + project context to LLM, generates a gold-standard SKILL.md. Only called when PATH A and B fail.
|
|
1607
1694
|
|
|
@@ -1666,8 +1753,9 @@ STEP 5: Install skills for each domain
|
|
|
1666
1753
|
For each domain you identified:
|
|
1667
1754
|
|
|
1668
1755
|
a. Call search_skills(domain)
|
|
1669
|
-
- This searches skills.
|
|
1756
|
+
- This searches using npx skills find (primary) with SkillScout.dev fallback
|
|
1670
1757
|
- Returns quality-filtered results (stars >= 50, updated within 12 months)
|
|
1758
|
+
- Each result includes installRef field (format: owner/repo@skillname)
|
|
1671
1759
|
|
|
1672
1760
|
b. If quality results found (stars >= 50):
|
|
1673
1761
|
- Call fetch_skill_content for the top result
|
|
@@ -1675,7 +1763,7 @@ b. If quality results found (stars >= 50):
|
|
|
1675
1763
|
- Call install_skill with source type 'github' and the owner/repo/stars data
|
|
1676
1764
|
- install_skill returns a PATH A response: an npx command string
|
|
1677
1765
|
- YOU MUST EXECUTE THIS COMMAND using your bash tool immediately
|
|
1678
|
-
- Example command: npx skills add vercel-labs/next-skills
|
|
1766
|
+
- Example command: npx skills add vercel-labs/next-skills@nextjs-patterns --agent claude-code --agent cursor -y
|
|
1679
1767
|
- The command is fully non-interactive (no prompts) thanks to --agent flags and -y flag
|
|
1680
1768
|
|
|
1681
1769
|
c. If no quality results OR PATH A fails:
|
|
@@ -1720,7 +1808,7 @@ PATH A (npx command \u2014 preferred):
|
|
|
1720
1808
|
- Used for high-quality GitHub repos (stars >= 50)
|
|
1721
1809
|
- install_skill returns an npx command string
|
|
1722
1810
|
- YOU execute it using your bash tool
|
|
1723
|
-
- Command format: npx skills add <owner/repo> --
|
|
1811
|
+
- Command format: npx skills add <owner/repo@skillname> --agent <agent1> --agent <agent2> -y
|
|
1724
1812
|
- The npx CLI handles cloning, writing, and directory creation
|
|
1725
1813
|
- Fully automated with zero interactive prompts
|
|
1726
1814
|
|
|
@@ -1747,10 +1835,10 @@ You:
|
|
|
1747
1835
|
3. [Analyze project] \u2192 Next.js 15, App Router, TypeScript, Prisma, TailwindCSS
|
|
1748
1836
|
4. [Derive domains] \u2192 nextjs-app-router-patterns, prisma-schema-design, tailwindcss-components
|
|
1749
1837
|
5. For nextjs-app-router-patterns:
|
|
1750
|
-
- [Call search_skills] \u2192 Found vercel-labs/next-skills (2,341 stars)
|
|
1751
|
-
- [Call fetch_skill_content] \u2192 Retrieved SKILL.md
|
|
1752
|
-
- [Call install_skill] \u2192 Returns npx command
|
|
1753
|
-
- [Execute via bash] \u2192 npx skills add vercel-labs/next-skills
|
|
1838
|
+
- [Call search_skills] \u2192 Found vercel-labs/next-skills (2,341 stars), installRef: vercel-labs/next-skills@nextjs-app-router-patterns
|
|
1839
|
+
- [Call fetch_skill_content with installRef] \u2192 Retrieved SKILL.md
|
|
1840
|
+
- [Call install_skill with source.installRef] \u2192 Returns npx command
|
|
1841
|
+
- [Execute via bash] \u2192 npx skills add vercel-labs/next-skills@nextjs-app-router-patterns --agent claude-code --agent cursor -y
|
|
1754
1842
|
6. [Repeat for other domains]
|
|
1755
1843
|
7. [Call list_installed_skills] \u2192 Report summary table
|
|
1756
1844
|
|
|
@@ -1825,7 +1913,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1825
1913
|
},
|
|
1826
1914
|
{
|
|
1827
1915
|
name: "search_skills",
|
|
1828
|
-
description: "Searches skills
|
|
1916
|
+
description: "Searches for skills using npx skills find (primary) with SkillScout.dev API fallback. Fetches GitHub star count and recency. Returns ranked list filtered by quality (stars >= 50, updated within 12 months). Each result includes installRef field.",
|
|
1829
1917
|
inputSchema: {
|
|
1830
1918
|
type: "object",
|
|
1831
1919
|
properties: {
|
|
@@ -1854,33 +1942,37 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1854
1942
|
},
|
|
1855
1943
|
{
|
|
1856
1944
|
name: "fetch_skill_content",
|
|
1857
|
-
description: "Fetches full SKILL.md content from a GitHub repository using raw URL fallbacks. Validates YAML frontmatter. Returns content, source URL, and parsed metadata.",
|
|
1945
|
+
description: "Fetches full SKILL.md content from a GitHub repository using raw URL fallbacks. Accepts either owner/repo/skillName OR installRef (format: owner/repo@skillname). Validates YAML frontmatter. Returns content, source URL, installRef, and parsed metadata.",
|
|
1858
1946
|
inputSchema: {
|
|
1859
1947
|
type: "object",
|
|
1860
1948
|
properties: {
|
|
1861
1949
|
owner: {
|
|
1862
1950
|
type: "string",
|
|
1863
|
-
description: "GitHub repository owner"
|
|
1951
|
+
description: "GitHub repository owner (optional if installRef provided)"
|
|
1864
1952
|
},
|
|
1865
1953
|
repo: {
|
|
1866
1954
|
type: "string",
|
|
1867
|
-
description: "GitHub repository name"
|
|
1955
|
+
description: "GitHub repository name (optional if installRef provided)"
|
|
1868
1956
|
},
|
|
1869
1957
|
skillName: {
|
|
1870
1958
|
type: "string",
|
|
1871
|
-
description: "Skill name to fetch"
|
|
1959
|
+
description: "Skill name to fetch (optional if installRef provided)"
|
|
1960
|
+
},
|
|
1961
|
+
installRef: {
|
|
1962
|
+
type: "string",
|
|
1963
|
+
description: "Install reference in format owner/repo@skillname (alternative to owner/repo/skillName)"
|
|
1872
1964
|
},
|
|
1873
1965
|
githubToken: {
|
|
1874
1966
|
type: "string",
|
|
1875
1967
|
description: "Optional GitHub token for higher rate limits"
|
|
1876
1968
|
}
|
|
1877
1969
|
},
|
|
1878
|
-
required: [
|
|
1970
|
+
required: []
|
|
1879
1971
|
}
|
|
1880
1972
|
},
|
|
1881
1973
|
{
|
|
1882
1974
|
name: "install_skill",
|
|
1883
|
-
description: "Core installation tool. PATH A: returns npx skills add command string for agent to execute. PATH B: writes SKILL.md directly to disk if npx fails. Handles all selected agents simultaneously.",
|
|
1975
|
+
description: "Core installation tool. PATH A: returns npx skills add command string for agent to execute. PATH B: writes SKILL.md directly to disk if npx fails. PATH C: generates skill via LLM if both fail. Handles all selected agents simultaneously.",
|
|
1884
1976
|
inputSchema: {
|
|
1885
1977
|
type: "object",
|
|
1886
1978
|
properties: {
|
|
@@ -1913,9 +2005,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1913
2005
|
type: "string",
|
|
1914
2006
|
description: "GitHub repo (for type: github)"
|
|
1915
2007
|
},
|
|
2008
|
+
installRef: {
|
|
2009
|
+
type: "string",
|
|
2010
|
+
description: "Install reference in format owner/repo@skillname (alternative to owner/repo)"
|
|
2011
|
+
},
|
|
1916
2012
|
content: {
|
|
1917
2013
|
type: "string",
|
|
1918
2014
|
description: "Direct SKILL.md content (for type: content)"
|
|
2015
|
+
},
|
|
2016
|
+
stars: {
|
|
2017
|
+
type: "number",
|
|
2018
|
+
description: "GitHub star count (for quality check, triggers PATH A if >= 50)"
|
|
1919
2019
|
}
|
|
1920
2020
|
},
|
|
1921
2021
|
required: ["type"],
|
|
@@ -1930,6 +2030,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1930
2030
|
type: "boolean",
|
|
1931
2031
|
description: "Install to global scope",
|
|
1932
2032
|
default: false
|
|
2033
|
+
},
|
|
2034
|
+
projectContext: {
|
|
2035
|
+
type: "string",
|
|
2036
|
+
description: "Rich project context for PATH C skill generation (frameworks, versions, patterns, folder structure)"
|
|
1933
2037
|
}
|
|
1934
2038
|
},
|
|
1935
2039
|
required: ["projectPath", "skillName", "agents", "source"]
|