import fs from 'fs'; import path from 'path'; import matter from 'gray-matter'; import { fileURLToPath } from 'url'; import { resolveCategory, sortCategories } from './category-rules.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const ROOT_DIR = path.resolve(__dirname, '..'); const SKILLS_DIR = path.resolve(ROOT_DIR, '../skills'); export interface SkillMetadata { name: string; description: string; category?: string; keywords?: string[]; license?: string; argumentHint?: string; author?: string; version?: string; } export interface Skill { id: string; name: string; description: string; category: string; keywords: string[]; path: string; relativePath: string; argumentHint?: string; metadata: SkillMetadata; } export interface SkillsData { skills: Skill[]; categories: string[]; totalCount: number; generatedAt: string; } function parseFrontmatter(content: string): SkillMetadata | null { try { const { data } = matter(content); if (!data.name || !data.description) { return null; } return { name: data.name, description: data.description, category: data.category || 'uncategorized', keywords: data.keywords || [], license: data.license, argumentHint: data['argument-hint'], author: data.author, version: data.version, }; } catch { return null; } } function slugify(name: string): string { return name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''); } function findSkillFiles(dir: string): string[] { const files: string[] = []; if (!fs.existsSync(dir)) { return files; } const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { // Skip certain directories if (entry.name === 'node_modules' || entry.name.startsWith('.')) { continue; } // Check if directory has SKILL.md const skillPath = path.join(fullPath, 'SKILL.md'); if (fs.existsSync(skillPath)) { files.push(skillPath); } // Recurse into subdirectories (for nested skills like mioflow-skills) files.push(...findSkillFiles(fullPath)); } } return files; } function parseAllSkills(): SkillsData { const skillFiles = findSkillFiles(SKILLS_DIR); const skills: Skill[] = []; const categoriesSet = new Set(); for (const filePath of skillFiles) { try { const content = fs.readFileSync(filePath, 'utf-8'); const metadata = parseFrontmatter(content); if (!metadata) { console.warn(`⚠ Skipping ${filePath} - missing frontmatter`); continue; } const relativePath = path.relative(SKILLS_DIR, filePath); const category = resolveCategory(relativePath, metadata.category); categoriesSet.add(category); skills.push({ id: slugify(metadata.name), name: metadata.name, description: metadata.description, category, keywords: metadata.keywords || [], path: filePath, relativePath, argumentHint: metadata.argumentHint, metadata: { ...metadata, category }, }); } catch (error) { console.error(`✖ Error parsing ${filePath}:`, error); } } // Sort skills alphabetically by name skills.sort((a, b) => a.name.localeCompare(b.name)); return { skills, categories: sortCategories(Array.from(categoriesSet)), totalCount: skills.length, generatedAt: new Date().toISOString(), }; } function main() { console.log('🔍 Parsing skills from:', SKILLS_DIR); const data = parseAllSkills(); // Output directory const outputDir = path.join(ROOT_DIR, 'src', 'data'); const outputFile = path.join(outputDir, 'skills.json'); // Ensure output directory exists fs.mkdirSync(outputDir, { recursive: true }); // Write skills data fs.writeFileSync(outputFile, JSON.stringify(data, null, 2)); console.log(`✅ Parsed ${data.totalCount} skills in ${data.categories.length} categories`); console.log(`📁 Output: ${outputFile}`); } main();