mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-23 20:05:12 +00:00
Skills rendering in collections
This commit is contained in:
@@ -1,30 +1,28 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path, { dirname } from "path";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
import { dirname } from "path";
|
|
||||||
import {
|
import {
|
||||||
parseCollectionYaml,
|
AGENTS_DIR,
|
||||||
extractMcpServers,
|
AKA_INSTALL_URLS,
|
||||||
|
COLLECTIONS_DIR,
|
||||||
|
DOCS_DIR,
|
||||||
|
INSTRUCTIONS_DIR,
|
||||||
|
PROMPTS_DIR,
|
||||||
|
repoBaseUrl,
|
||||||
|
ROOT_FOLDER,
|
||||||
|
SKILLS_DIR,
|
||||||
|
TEMPLATES,
|
||||||
|
vscodeInsidersInstallImage,
|
||||||
|
vscodeInstallImage,
|
||||||
|
} from "./constants.mjs";
|
||||||
|
import {
|
||||||
extractMcpServerConfigs,
|
extractMcpServerConfigs,
|
||||||
|
parseCollectionYaml,
|
||||||
parseFrontmatter,
|
parseFrontmatter,
|
||||||
parseSkillMetadata,
|
parseSkillMetadata,
|
||||||
} from "./yaml-parser.mjs";
|
} from "./yaml-parser.mjs";
|
||||||
import {
|
|
||||||
TEMPLATES,
|
|
||||||
AKA_INSTALL_URLS,
|
|
||||||
repoBaseUrl,
|
|
||||||
vscodeInstallImage,
|
|
||||||
vscodeInsidersInstallImage,
|
|
||||||
ROOT_FOLDER,
|
|
||||||
PROMPTS_DIR,
|
|
||||||
AGENTS_DIR,
|
|
||||||
SKILLS_DIR,
|
|
||||||
COLLECTIONS_DIR,
|
|
||||||
INSTRUCTIONS_DIR,
|
|
||||||
DOCS_DIR,
|
|
||||||
} from "./constants.mjs";
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
@@ -55,14 +53,16 @@ async function loadMcpRegistryNames() {
|
|||||||
if (MCP_REGISTRY_SET) return MCP_REGISTRY_SET;
|
if (MCP_REGISTRY_SET) return MCP_REGISTRY_SET;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('Fetching MCP registry from API...');
|
console.log("Fetching MCP registry from API...");
|
||||||
const allServers = [];
|
const allServers = [];
|
||||||
let cursor = null;
|
let cursor = null;
|
||||||
const apiUrl = 'https://api.mcp.github.com/v0.1/servers/';
|
const apiUrl = "https://api.mcp.github.com/v0.1/servers/";
|
||||||
|
|
||||||
// Fetch all pages using cursor-based pagination
|
// Fetch all pages using cursor-based pagination
|
||||||
do {
|
do {
|
||||||
const url = cursor ? `${apiUrl}?cursor=${encodeURIComponent(cursor)}` : apiUrl;
|
const url = cursor
|
||||||
|
? `${apiUrl}?cursor=${encodeURIComponent(cursor)}`
|
||||||
|
: apiUrl;
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -78,8 +78,9 @@ async function loadMcpRegistryNames() {
|
|||||||
if (serverName) {
|
if (serverName) {
|
||||||
// Try to get displayName from GitHub metadata, fall back to server name
|
// Try to get displayName from GitHub metadata, fall back to server name
|
||||||
const displayName =
|
const displayName =
|
||||||
entry?.server?._meta?.["io.modelcontextprotocol.registry/publisher-provided"]?.github?.displayName ||
|
entry?.server?._meta?.[
|
||||||
serverName;
|
"io.modelcontextprotocol.registry/publisher-provided"
|
||||||
|
]?.github?.displayName || serverName;
|
||||||
|
|
||||||
allServers.push({
|
allServers.push({
|
||||||
name: serverName,
|
name: serverName,
|
||||||
@@ -431,19 +432,23 @@ function generateMcpServerLinks(servers, registryNames) {
|
|||||||
|
|
||||||
// Match against both displayName and full name (case-insensitive)
|
// Match against both displayName and full name (case-insensitive)
|
||||||
const serverNameLower = serverName.toLowerCase();
|
const serverNameLower = serverName.toLowerCase();
|
||||||
const registryEntry = registryNames.find(
|
const registryEntry = registryNames.find((entry) => {
|
||||||
(entry) => {
|
|
||||||
// Exact match on displayName or fullName
|
// Exact match on displayName or fullName
|
||||||
if (entry.displayName === serverNameLower || entry.fullName === serverNameLower) {
|
if (
|
||||||
|
entry.displayName === serverNameLower ||
|
||||||
|
entry.fullName === serverNameLower
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the serverName matches a part of the full name after a slash
|
// Check if the serverName matches a part of the full name after a slash
|
||||||
// e.g., "apify" matches "com.apify/apify-mcp-server"
|
// e.g., "apify" matches "com.apify/apify-mcp-server"
|
||||||
const nameParts = entry.fullName.split('/');
|
const nameParts = entry.fullName.split("/");
|
||||||
if (nameParts.length > 1 && nameParts[1]) {
|
if (nameParts.length > 1 && nameParts[1]) {
|
||||||
// Check if it matches the second part (after the slash)
|
// Check if it matches the second part (after the slash)
|
||||||
const secondPart = nameParts[1].replace('-mcp-server', '').replace('-mcp', '');
|
const secondPart = nameParts[1]
|
||||||
|
.replace("-mcp-server", "")
|
||||||
|
.replace("-mcp", "");
|
||||||
if (secondPart === serverNameLower) {
|
if (secondPart === serverNameLower) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -451,8 +456,7 @@ function generateMcpServerLinks(servers, registryNames) {
|
|||||||
|
|
||||||
// Check if serverName matches the displayName ignoring case
|
// Check if serverName matches the displayName ignoring case
|
||||||
return entry.displayName === serverNameLower;
|
return entry.displayName === serverNameLower;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
const serverLabel = registryEntry
|
const serverLabel = registryEntry
|
||||||
? `[${serverName}](${`https://github.com/mcp/${registryEntry.name}`})`
|
? `[${serverName}](${`https://github.com/mcp/${registryEntry.name}`})`
|
||||||
: serverName;
|
: serverName;
|
||||||
@@ -489,9 +493,7 @@ function generateSkillsSection(skillsDir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get all skill folders (directories)
|
// Get all skill folders (directories)
|
||||||
const skillFolders = fs
|
const skillFolders = fs.readdirSync(skillsDir).filter((file) => {
|
||||||
.readdirSync(skillsDir)
|
|
||||||
.filter((file) => {
|
|
||||||
const filePath = path.join(skillsDir, file);
|
const filePath = path.join(skillsDir, file);
|
||||||
return fs.statSync(filePath).isDirectory();
|
return fs.statSync(filePath).isDirectory();
|
||||||
});
|
});
|
||||||
@@ -768,7 +770,11 @@ function generateFeaturedCollectionsSection(collectionsDir) {
|
|||||||
* @param {string} collectionId - Collection ID
|
* @param {string} collectionId - Collection ID
|
||||||
* @param {{ name: string, displayName: string }[]} registryNames - Pre-loaded MCP registry names
|
* @param {{ name: string, displayName: string }[]} registryNames - Pre-loaded MCP registry names
|
||||||
*/
|
*/
|
||||||
function generateCollectionReadme(collection, collectionId, registryNames = []) {
|
function generateCollectionReadme(
|
||||||
|
collection,
|
||||||
|
collectionId,
|
||||||
|
registryNames = []
|
||||||
|
) {
|
||||||
if (!collection || !collection.items) {
|
if (!collection || !collection.items) {
|
||||||
return `# ${collectionId}\n\nCollection not found or invalid.`;
|
return `# ${collectionId}\n\nCollection not found or invalid.`;
|
||||||
}
|
}
|
||||||
@@ -820,20 +826,23 @@ function generateCollectionReadme(collection, collectionId, registryNames = [])
|
|||||||
? "Instruction"
|
? "Instruction"
|
||||||
: item.kind === "agent"
|
: item.kind === "agent"
|
||||||
? "Agent"
|
? "Agent"
|
||||||
|
: item.kind === "skill"
|
||||||
|
? "Skill"
|
||||||
: "Prompt";
|
: "Prompt";
|
||||||
const link = `../${item.path}`;
|
const link = `../${item.path}`;
|
||||||
|
|
||||||
// Create install badges for each item
|
// Create install badges for each item (skills don't use chat install badges)
|
||||||
const badges = makeBadges(
|
const badgeType =
|
||||||
item.path,
|
|
||||||
item.kind === "instruction"
|
item.kind === "instruction"
|
||||||
? "instructions"
|
? "instructions"
|
||||||
: item.kind === "chat-mode"
|
: item.kind === "chat-mode"
|
||||||
? "mode"
|
? "mode"
|
||||||
: item.kind === "agent"
|
: item.kind === "agent"
|
||||||
? "agent"
|
? "agent"
|
||||||
: "prompt"
|
: item.kind === "skill"
|
||||||
);
|
? null
|
||||||
|
: "prompt";
|
||||||
|
const badges = badgeType ? makeBadges(item.path, badgeType) : "";
|
||||||
|
|
||||||
const usageDescription = item.usage
|
const usageDescription = item.usage
|
||||||
? `${description} [see usage](#${title
|
? `${description} [see usage](#${title
|
||||||
@@ -891,15 +900,21 @@ function buildCollectionRow({
|
|||||||
kind,
|
kind,
|
||||||
registryNames = [],
|
registryNames = [],
|
||||||
}) {
|
}) {
|
||||||
|
const titleCell = badges
|
||||||
|
? `[${title}](${link})<br />${badges}`
|
||||||
|
: `[${title}](${link})`;
|
||||||
|
|
||||||
if (hasAgents) {
|
if (hasAgents) {
|
||||||
// Only agents currently have MCP servers; future migration may extend to chat modes.
|
// Only agents currently have MCP servers; future migration may extend to chat modes.
|
||||||
const mcpServers =
|
const mcpServers =
|
||||||
kind === "agent" ? extractMcpServerConfigs(filePath) : [];
|
kind === "agent" ? extractMcpServerConfigs(filePath) : [];
|
||||||
const mcpServerCell =
|
const mcpServerCell =
|
||||||
mcpServers.length > 0 ? generateMcpServerLinks(mcpServers, registryNames) : "";
|
mcpServers.length > 0
|
||||||
return `| [${title}](${link})<br />${badges} | ${typeDisplay} | ${usageDescription} | ${mcpServerCell} |\n`;
|
? generateMcpServerLinks(mcpServers, registryNames)
|
||||||
|
: "";
|
||||||
|
return `| ${titleCell} | ${typeDisplay} | ${usageDescription} | ${mcpServerCell} |\n`;
|
||||||
}
|
}
|
||||||
return `| [${title}](${link})<br />${badges} | ${typeDisplay} | ${usageDescription} |\n`;
|
return `| ${titleCell} | ${typeDisplay} | ${usageDescription} |\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility: write file only if content changed
|
// Utility: write file only if content changed
|
||||||
@@ -921,7 +936,13 @@ function writeFileIfChanged(filePath, content) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build per-category README content using existing generators, upgrading headings to H1
|
// Build per-category README content using existing generators, upgrading headings to H1
|
||||||
function buildCategoryReadme(sectionBuilder, dirPath, headerLine, usageLine, registryNames = []) {
|
function buildCategoryReadme(
|
||||||
|
sectionBuilder,
|
||||||
|
dirPath,
|
||||||
|
headerLine,
|
||||||
|
usageLine,
|
||||||
|
registryNames = []
|
||||||
|
) {
|
||||||
const section = sectionBuilder(dirPath, registryNames);
|
const section = sectionBuilder(dirPath, registryNames);
|
||||||
if (section && section.trim()) {
|
if (section && section.trim()) {
|
||||||
// Upgrade the first markdown heading level from ## to # for standalone README files
|
// Upgrade the first markdown heading level from ## to # for standalone README files
|
||||||
@@ -1077,7 +1098,9 @@ async function main() {
|
|||||||
writeFileIfChanged(mainReadmePath, readmeContent);
|
writeFileIfChanged(mainReadmePath, readmeContent);
|
||||||
console.log("Main README.md updated with featured collections");
|
console.log("Main README.md updated with featured collections");
|
||||||
} else {
|
} else {
|
||||||
console.warn("README.md not found, skipping featured collections update");
|
console.warn(
|
||||||
|
"README.md not found, skipping featured collections update"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("No featured collections found to add to README.md");
|
console.log("No featured collections found to add to README.md");
|
||||||
@@ -1095,4 +1118,3 @@ main().catch((error) => {
|
|||||||
console.error(error.stack);
|
console.error(error.stack);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user