Add tools catalog with YAML schema and website page

- 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
This commit is contained in:
Aaron Powell
2026-01-29 13:48:42 +11:00
parent 36a26b01e1
commit c8d342cc62
8 changed files with 1156 additions and 41 deletions

View File

@@ -22,12 +22,14 @@ import {
parseFrontmatter,
parseCollectionYaml,
parseSkillMetadata,
parseYamlFile,
} from "./yaml-parser.mjs";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const WEBSITE_DATA_DIR = path.join(ROOT_FOLDER, "website", "public", "data");
const WEBSITE_SOURCE_DATA_DIR = path.join(ROOT_FOLDER, "website", "data");
/**
* Ensure the output directory exists
@@ -435,6 +437,63 @@ function generateCollectionsData() {
};
}
/**
* Generate tools metadata from website/data/tools.yml
*/
function generateToolsData() {
const toolsFile = path.join(WEBSITE_SOURCE_DATA_DIR, "tools.yml");
if (!fs.existsSync(toolsFile)) {
console.warn("No tools.yml file found at", toolsFile);
return { items: [], filters: { categories: [], tags: [] } };
}
const data = parseYamlFile(toolsFile);
if (!data || !data.tools) {
return { items: [], filters: { categories: [], tags: [] } };
}
const allCategories = new Set();
const allTags = new Set();
const tools = data.tools.map((tool) => {
const category = tool.category || "Other";
allCategories.add(category);
const tags = tool.tags || [];
tags.forEach((t) => allTags.add(t));
return {
id: tool.id,
name: tool.name,
description: tool.description || "",
category: category,
featured: tool.featured || false,
requirements: tool.requirements || [],
features: tool.features || [],
links: tool.links || {},
configuration: tool.configuration || null,
tags: tags,
};
});
// Sort with featured first, then alphabetically
const sortedTools = tools.sort((a, b) => {
if (a.featured && !b.featured) return -1;
if (!a.featured && b.featured) return 1;
return a.name.localeCompare(b.name);
});
return {
items: sortedTools,
filters: {
categories: Array.from(allCategories).sort(),
tags: Array.from(allTags).sort(),
},
};
}
/**
* Generate a combined index for search
*/
@@ -529,6 +588,10 @@ async function main() {
const collections = collectionsData.items;
console.log(`✓ Generated ${collections.length} collections (${collectionsData.filters.tags.length} tags)`);
const toolsData = generateToolsData();
const tools = toolsData.items;
console.log(`✓ Generated ${tools.length} tools (${toolsData.filters.categories.length} categories)`);
const searchIndex = generateSearchIndex(agents, prompts, instructions, skills, collections);
console.log(`✓ Generated search index with ${searchIndex.length} items`);
@@ -558,6 +621,11 @@ async function main() {
JSON.stringify(collectionsData, null, 2)
);
fs.writeFileSync(
path.join(WEBSITE_DATA_DIR, "tools.json"),
JSON.stringify(toolsData, null, 2)
);
fs.writeFileSync(
path.join(WEBSITE_DATA_DIR, "search-index.json"),
JSON.stringify(searchIndex, null, 2)
@@ -572,6 +640,7 @@ async function main() {
instructions: instructions.length,
skills: skills.length,
collections: collections.length,
tools: tools.length,
total: searchIndex.length,
},
};
@@ -581,7 +650,7 @@ async function main() {
JSON.stringify(manifest, null, 2)
);
console.log(`\n✓ All data written to website/data/`);
console.log(`\n✓ All data written to website/public/data/`);
}
main().catch((err) => {

View File

@@ -195,6 +195,22 @@ function parseSkillMetadata(skillPath) {
);
}
/**
* 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,
@@ -202,5 +218,6 @@ export {
extractMcpServers,
extractMcpServerConfigs,
parseSkillMetadata,
parseYamlFile,
safeFileOperation,
};