mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-20 18:35:14 +00:00
- Create website/data/tools.yml with 6 tools: - Awesome Copilot MCP Server - Awesome GitHub Copilot Browser (VS Code extension) - APM - Agent Package Manager (CLI) - Workspace Architect (npm CLI) - Prompt Registry (VS Code extension) - GitHub Node for Visual Studio - Add .schemas/tools.schema.json for YAML validation - Update eng/generate-website-data.mjs to generate tools.json - Add parseYamlFile() to eng/yaml-parser.mjs - Refactor tools.astro to use external TypeScript module - Create website/src/scripts/pages/tools.ts with: - FuzzySearch integration for search - Category filtering - Copy configuration functionality
224 lines
6.6 KiB
JavaScript
224 lines
6.6 KiB
JavaScript
// YAML parser for collection files and frontmatter parsing using vfile-matter
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import yaml from "js-yaml";
|
|
import { VFile } from "vfile";
|
|
import { matter } from "vfile-matter";
|
|
|
|
function safeFileOperation(operation, filePath, defaultValue = null) {
|
|
try {
|
|
return operation();
|
|
} catch (error) {
|
|
console.error(`Error processing file ${filePath}: ${error.message}`);
|
|
return defaultValue;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse a collection YAML file (.collection.yml)
|
|
* Collections are pure YAML files without frontmatter delimiters
|
|
* @param {string} filePath - Path to the collection file
|
|
* @returns {object|null} Parsed collection object or null on error
|
|
*/
|
|
function parseCollectionYaml(filePath) {
|
|
return safeFileOperation(
|
|
() => {
|
|
const content = fs.readFileSync(filePath, "utf8");
|
|
|
|
// Collections are pure YAML files, parse directly with js-yaml
|
|
return yaml.load(content, { schema: yaml.JSON_SCHEMA });
|
|
},
|
|
filePath,
|
|
null
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Parse frontmatter from a markdown file using vfile-matter
|
|
* Works with any markdown file that has YAML frontmatter (agents, prompts, instructions)
|
|
* @param {string} filePath - Path to the markdown file
|
|
* @returns {object|null} Parsed frontmatter object or null on error
|
|
*/
|
|
function parseFrontmatter(filePath) {
|
|
return safeFileOperation(
|
|
() => {
|
|
const content = fs.readFileSync(filePath, "utf8");
|
|
const file = new VFile({ path: filePath, value: content });
|
|
|
|
// Parse the frontmatter using vfile-matter
|
|
matter(file);
|
|
|
|
// The frontmatter is now available in file.data.matter
|
|
const frontmatter = file.data.matter;
|
|
|
|
// Normalize string fields that can accumulate trailing newlines/spaces
|
|
if (frontmatter) {
|
|
if (typeof frontmatter.name === "string") {
|
|
frontmatter.name = frontmatter.name.replace(/[\r\n]+$/g, "").trim();
|
|
}
|
|
if (typeof frontmatter.title === "string") {
|
|
frontmatter.title = frontmatter.title.replace(/[\r\n]+$/g, "").trim();
|
|
}
|
|
if (typeof frontmatter.description === "string") {
|
|
// Remove only trailing whitespace/newlines; preserve internal formatting
|
|
frontmatter.description = frontmatter.description.replace(
|
|
/[\s\r\n]+$/g,
|
|
""
|
|
);
|
|
}
|
|
}
|
|
|
|
return frontmatter;
|
|
},
|
|
filePath,
|
|
null
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Extract agent metadata including MCP server information
|
|
* @param {string} filePath - Path to the agent file
|
|
* @returns {object|null} Agent metadata object with name, description, tools, and mcp-servers
|
|
*/
|
|
function extractAgentMetadata(filePath) {
|
|
const frontmatter = parseFrontmatter(filePath);
|
|
|
|
if (!frontmatter) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
name: typeof frontmatter.name === "string" ? frontmatter.name : null,
|
|
description:
|
|
typeof frontmatter.description === "string"
|
|
? frontmatter.description
|
|
: null,
|
|
tools: frontmatter.tools || [],
|
|
mcpServers: frontmatter["mcp-servers"] || {},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Extract MCP server names from an agent file
|
|
* @param {string} filePath - Path to the agent file
|
|
* @returns {string[]} Array of MCP server names
|
|
*/
|
|
function extractMcpServers(filePath) {
|
|
const metadata = extractAgentMetadata(filePath);
|
|
|
|
if (!metadata || !metadata.mcpServers) {
|
|
return [];
|
|
}
|
|
|
|
return Object.keys(metadata.mcpServers);
|
|
}
|
|
|
|
/**
|
|
* Extract full MCP server configs from an agent file
|
|
* @param {string} filePath - Path to the agent file
|
|
* @returns {Array<{name:string,type?:string,command?:string,args?:string[],url?:string,headers?:object}>}
|
|
*/
|
|
function extractMcpServerConfigs(filePath) {
|
|
const metadata = extractAgentMetadata(filePath);
|
|
if (!metadata || !metadata.mcpServers) return [];
|
|
return Object.entries(metadata.mcpServers).map(([name, cfg]) => {
|
|
// Ensure we don't mutate original cfg
|
|
const copy = { ...cfg };
|
|
return {
|
|
name,
|
|
type: typeof copy.type === "string" ? copy.type : undefined,
|
|
command: typeof copy.command === "string" ? copy.command : undefined,
|
|
args: Array.isArray(copy.args) ? copy.args : undefined,
|
|
url: typeof copy.url === "string" ? copy.url : undefined,
|
|
headers:
|
|
typeof copy.headers === "object" && copy.headers !== null
|
|
? copy.headers
|
|
: undefined,
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Parse SKILL.md frontmatter and list bundled assets in a skill folder
|
|
* @param {string} skillPath - Path to skill folder
|
|
* @returns {object|null} Skill metadata with name, description, and assets array
|
|
*/
|
|
function parseSkillMetadata(skillPath) {
|
|
return safeFileOperation(
|
|
() => {
|
|
const skillFile = path.join(skillPath, "SKILL.md");
|
|
if (!fs.existsSync(skillFile)) {
|
|
return null;
|
|
}
|
|
|
|
const frontmatter = parseFrontmatter(skillFile);
|
|
|
|
// Validate required fields
|
|
if (!frontmatter?.name || !frontmatter?.description) {
|
|
console.warn(
|
|
`Invalid skill at ${skillPath}: missing name or description in frontmatter`
|
|
);
|
|
return null;
|
|
}
|
|
|
|
// List bundled assets (all files except SKILL.md), recursing through subdirectories
|
|
const getAllFiles = (dirPath, arrayOfFiles = []) => {
|
|
const files = fs.readdirSync(dirPath);
|
|
|
|
files.forEach((file) => {
|
|
const filePath = path.join(dirPath, file);
|
|
if (fs.statSync(filePath).isDirectory()) {
|
|
arrayOfFiles = getAllFiles(filePath, arrayOfFiles);
|
|
} else {
|
|
const relativePath = path.relative(skillPath, filePath);
|
|
if (relativePath !== "SKILL.md") {
|
|
// Normalize path separators to forward slashes for cross-platform consistency
|
|
arrayOfFiles.push(relativePath.replace(/\\/g, '/'));
|
|
}
|
|
}
|
|
});
|
|
|
|
return arrayOfFiles;
|
|
};
|
|
|
|
const assets = getAllFiles(skillPath).sort();
|
|
|
|
return {
|
|
name: frontmatter.name,
|
|
description: frontmatter.description,
|
|
assets,
|
|
path: skillPath,
|
|
};
|
|
},
|
|
skillPath,
|
|
null
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Parse a generic YAML file (used for tools.yml and other config files)
|
|
* @param {string} filePath - Path to the YAML file
|
|
* @returns {object|null} Parsed YAML object or null on error
|
|
*/
|
|
function parseYamlFile(filePath) {
|
|
return safeFileOperation(
|
|
() => {
|
|
const content = fs.readFileSync(filePath, "utf8");
|
|
return yaml.load(content, { schema: yaml.JSON_SCHEMA });
|
|
},
|
|
filePath,
|
|
null
|
|
);
|
|
}
|
|
|
|
export {
|
|
parseCollectionYaml,
|
|
parseFrontmatter,
|
|
extractAgentMetadata,
|
|
extractMcpServers,
|
|
extractMcpServerConfigs,
|
|
parseSkillMetadata,
|
|
parseYamlFile,
|
|
safeFileOperation,
|
|
};
|