#!/usr/bin/env node /** * skill-compiler CLI * Converts skill/framework documentation into compressed AGENTS.md indexes */ import { Command } from 'commander'; import chalk from 'chalk'; import { scanProject } from './scanner/index.js'; import { fetchDocs } from './fetcher/index.js'; import { compressIndex } from './compressor/index.js'; import { injectAgentsMd } from './injector/index.js'; import { watchProject } from './watcher/index.js'; import { loadConfig, saveConfig, configExists, createInitialConfig } from './config/index.js'; import { addCustomSkill, listCustomSkills, removeCustomSkill } from './custom/index.js'; import { searchSkills, installSkill, scanLocalSkills, syncSkillsToAgentsMd, getSuggestedSkills } from './skills-sh/index.js'; const program = new Command(); program .name('skill-compiler') .description('Converts skill/framework documentation into compressed AGENTS.md indexes') .version('0.3.0'); // ============================================================================ // INIT COMMAND // ============================================================================ program .command('init') .description('Initialize skill-compiler configuration') .option('-o, --out ', 'Output path for AGENTS.md', './AGENTS.md') .option('--only ', 'Only process specific frameworks (comma-separated)') .option('--force', 'Overwrite existing config') .action(async (options) => { const cwd = process.cwd(); if (configExists(cwd) && !options.force) { console.log(chalk.yellow('Config file already exists. Use --force to overwrite.')); return; } const config = createInitialConfig({ out: options.out, frameworks: options.only?.split(','), }); await saveConfig(cwd, config); console.log(chalk.green('โœ“ Created .skill-compiler.json')); console.log(chalk.dim('\nRun `skill-compiler` to generate AGENTS.md')); }); // ============================================================================ // COMPILE COMMAND (default) // ============================================================================ program .command('compile', { isDefault: true }) .description('Scan project and generate AGENTS.md index') .option('-o, --out ', 'Output path for AGENTS.md') .option('--only ', 'Only process specific frameworks (comma-separated)') .option('--dry-run', 'Preview changes without writing') .option('--refresh', 'Force refresh cached docs') .option('--silent', 'Suppress output') .option('--check', 'Check if AGENTS.md is up-to-date (for CI)') .action(async (options) => { try { const cwd = process.cwd(); // Load config (CLI options override config) const config = await loadConfig(cwd); const outPath = options.out || config.out || './AGENTS.md'; const only = options.only?.split(',') || config.only; if (!options.silent) { console.log(chalk.blue('๐Ÿ” Scanning project for frameworks...')); } // 1. Scan for frameworks/skills const detected = await scanProject(cwd, { only }); if (detected.length === 0) { console.log(chalk.yellow('No frameworks detected. Nothing to do.')); return; } if (!options.silent) { console.log(chalk.green(`โœ“ Found ${detected.length} framework(s): ${detected.map(d => d.name).join(', ')}`)); } // 2. Fetch docs for each framework for (const skill of detected) { if (!options.silent) { console.log(chalk.blue(`๐Ÿ“ฅ Fetching docs for ${skill.name}@${skill.version}...`)); } await fetchDocs(skill, { refresh: options.refresh }); } // 3. Compress into indexes if (!options.silent) { console.log(chalk.blue('๐Ÿ“ฆ Compressing documentation indexes...')); } const indexes = await Promise.all(detected.map(skill => compressIndex(skill))); // 4. Sync skills.sh skills const skillsShIndexes = await syncSkillsToAgentsMd(cwd); if (skillsShIndexes.length > 0 && !options.silent) { console.log(chalk.blue(`๐Ÿ“ฆ Including ${skillsShIndexes.length} skills.sh skill(s)...`)); } const allIndexes = [...indexes, ...skillsShIndexes]; // 5. Inject into AGENTS.md if (options.dryRun) { console.log(chalk.yellow('\n--- DRY RUN ---')); console.log('Would write to:', outPath); console.log('\nGenerated indexes:'); allIndexes.forEach(idx => console.log(idx.slice(0, 200) + '...\n')); } else if (options.check) { // Check mode: verify AGENTS.md is up-to-date const { readFile } = await import('fs/promises'); const { existsSync } = await import('fs'); if (!existsSync(outPath)) { console.log(chalk.red('โœ— AGENTS.md does not exist')); process.exit(1); } const current = await readFile(outPath, 'utf-8'); const expected = allIndexes.join('\n\n'); if (!current.includes(expected.slice(0, 100))) { console.log(chalk.red('โœ— AGENTS.md is out of date')); console.log(chalk.dim('Run `skill-compiler` to update')); process.exit(1); } console.log(chalk.green('โœ“ AGENTS.md is up to date')); } else { await injectAgentsMd(outPath, allIndexes); if (!options.silent) { console.log(chalk.green(`โœ“ Updated ${outPath}`)); } } } catch (error) { console.error(chalk.red('Error:'), error instanceof Error ? error.message : error); process.exit(1); } }); // ============================================================================ // WATCH COMMAND // ============================================================================ program .command('watch') .description('Watch for dependency changes and auto-update AGENTS.md') .option('-o, --out ', 'Output path for AGENTS.md') .action(async (options) => { const cwd = process.cwd(); const config = await loadConfig(cwd); const outPath = options.out || config.out || './AGENTS.md'; console.log(chalk.blue('๐Ÿ‘€ Watching for changes...')); await watchProject(cwd, outPath); }); // ============================================================================ // ADD COMMAND (local skills) // ============================================================================ program .command('add ') .description('Add a custom skill from a local path') .option('-n, --name ', 'Name for the custom skill') .option('--priority ', 'Priority sections (comma-separated)') .action(async (path, options) => { console.log(chalk.yellow('โš ๏ธ Custom skills are injected as-is without validation.')); console.log(chalk.yellow(' Poorly structured docs may degrade agent performance.')); console.log(chalk.blue(`\n๐Ÿ“ฅ Adding skill from: ${path}`)); try { const result = await addCustomSkill(process.cwd(), path, { name: options.name, priority: options.priority?.split(','), }); console.log(chalk.green(`โœ“ Added skill "${result.name}" (${result.fileCount} files)`)); console.log(chalk.dim('Run `skill-compiler` to regenerate indexes.')); } catch (error) { console.error(chalk.red('Error:'), error instanceof Error ? error.message : error); process.exit(1); } }); // ============================================================================ // LIST COMMAND // ============================================================================ program .command('list') .description('List all custom and installed skills') .action(async () => { const cwd = process.cwd(); // List custom skills const customSkills = await listCustomSkills(cwd); // List skills.sh skills const skillsShSkills = await scanLocalSkills(cwd); if (customSkills.length === 0 && skillsShSkills.length === 0) { console.log(chalk.dim('No skills configured.')); console.log(chalk.dim('Use `skill-compiler install ` to add from skills.sh')); console.log(chalk.dim('Use `skill-compiler add ` to add local skills')); return; } if (customSkills.length > 0) { console.log(chalk.bold('Custom Skills:\n')); for (const skill of customSkills) { console.log(` ${chalk.green('โ€ข')} ${skill.name}`); console.log(chalk.dim(` Path: ${skill.path}`)); if (skill.priority) { console.log(chalk.dim(` Priority: ${skill.priority.join(', ')}`)); } } } if (skillsShSkills.length > 0) { console.log(chalk.bold('\nskills.sh Skills:\n')); for (const skill of skillsShSkills) { console.log(` ${chalk.blue('โ€ข')} ${skill.name}`); if (skill.description) { console.log(chalk.dim(` ${skill.description}`)); } console.log(chalk.dim(` Path: ${skill.path}`)); } } }); // ============================================================================ // REMOVE COMMAND // ============================================================================ program .command('remove ') .description('Remove a custom skill') .action(async (name) => { const removed = await removeCustomSkill(process.cwd(), name); if (removed) { console.log(chalk.green(`โœ“ Removed skill "${name}"`)); console.log(chalk.dim('Run `skill-compiler` to regenerate indexes.')); } else { console.log(chalk.yellow(`Skill "${name}" not found.`)); } }); // ============================================================================ // SEARCH COMMAND (skills.sh) // ============================================================================ program .command('search ') .description('Search skills.sh registry') .action(async (query) => { console.log(chalk.blue(`๐Ÿ” Searching skills.sh for "${query}"...\n`)); const results = await searchSkills(query); if (results.length === 0) { console.log(chalk.yellow('No skills found.')); console.log(chalk.dim('Try a broader search term or browse https://skills.sh')); return; } console.log(chalk.bold('Results:\n')); for (const skill of results) { console.log(` ${chalk.green('โ€ข')} ${chalk.bold(skill.name)}`); if (skill.description) { console.log(chalk.dim(` ${skill.description}`)); } console.log(chalk.dim(` Repo: ${skill.repo}`)); if (skill.downloads) { console.log(chalk.dim(` Downloads: ${skill.downloads.toLocaleString()}`)); } console.log(); } console.log(chalk.dim(`Install with: skill-compiler install ${results[0].repo} --skill ${results[0].name}`)); }); // ============================================================================ // INSTALL COMMAND (skills.sh) // ============================================================================ program .command('install ') .description('Install a skill from skills.sh registry') .option('-s, --skill ', 'Specific skill name to install') .option('--global', 'Install globally instead of project-local') .action(async (repo, options) => { console.log(chalk.blue(`๐Ÿ“ฅ Installing skill from ${repo}...`)); if (options.skill) { console.log(chalk.dim(` Skill: ${options.skill}`)); } const result = await installSkill(repo, { skillName: options.skill, scope: options.global ? 'global' : 'project', }); if (result.success) { console.log(chalk.green(`โœ“ Installed skill`)); if (result.skill) { console.log(chalk.dim(` Name: ${result.skill.name}`)); console.log(chalk.dim(` Path: ${result.skill.path}`)); } console.log(chalk.dim('\nRun `skill-compiler` to include in AGENTS.md')); } else { console.error(chalk.red('Installation failed:'), result.error); process.exit(1); } }); // ============================================================================ // SYNC COMMAND (skills.sh) // ============================================================================ program .command('sync') .description('Sync installed skills.sh skills to AGENTS.md') .action(async () => { const cwd = process.cwd(); console.log(chalk.blue('๐Ÿ”„ Syncing skills.sh skills...')); const skills = await scanLocalSkills(cwd); if (skills.length === 0) { console.log(chalk.yellow('No skills.sh skills found.')); console.log(chalk.dim('Install with: skill-compiler install ')); return; } console.log(chalk.green(`โœ“ Found ${skills.length} skill(s)`)); for (const skill of skills) { console.log(chalk.dim(` โ€ข ${skill.name}`)); } const indexes = await syncSkillsToAgentsMd(cwd); console.log(chalk.green(`โœ“ Generated ${indexes.length} index(es)`)); console.log(chalk.dim('\nRun `skill-compiler` to update AGENTS.md')); }); // ============================================================================ // SUGGEST COMMAND (skills.sh) // ============================================================================ program .command('suggest') .description('Suggest skills.sh skills based on your project') .action(async () => { const cwd = process.cwd(); console.log(chalk.blue('๐Ÿ” Analyzing project for skill suggestions...\n')); const detected = await scanProject(cwd); const frameworks = detected.map(d => d.name); if (frameworks.length === 0) { console.log(chalk.yellow('No frameworks detected.')); return; } console.log(chalk.dim(`Detected: ${frameworks.join(', ')}\n`)); const suggestions = getSuggestedSkills(frameworks); if (suggestions.length === 0) { console.log(chalk.yellow('No skill suggestions available for your stack.')); console.log(chalk.dim('Browse https://skills.sh for more options.')); return; } console.log(chalk.bold('Suggested Skills:\n')); for (const skill of suggestions) { console.log(` ${chalk.green('โ€ข')} ${chalk.bold(skill.name)}`); if (skill.description) { console.log(chalk.dim(` ${skill.description}`)); } console.log(chalk.dim(` Install: skill-compiler install ${skill.repo} --skill ${skill.name}`)); console.log(); } }); // ============================================================================ // EVAL COMMAND // ============================================================================ program .command('eval') .description('Run evaluation suite (Vercel methodology)') .option('--framework ', 'Evaluate specific framework') .option('--compare ', 'Compare configurations: baseline, skill-only, agents-md') .option('--verbose', 'Show detailed results for each test') .option('--simulate', 'Use simulated results (no API key required)') .action(async (options) => { const { runEval } = await import('./eval/index.js'); console.log(chalk.blue('๐Ÿงช Running evaluation suite (Vercel methodology)...')); console.log(chalk.dim('Using Build/Lint/Test metrics to measure agent effectiveness\n')); try { await runEval(process.cwd(), { framework: options.framework, compare: options.compare, verbose: options.verbose, simulate: options.simulate, }); } catch (error) { console.error(chalk.red('Eval error:'), error instanceof Error ? error.message : error); process.exit(1); } }); program.parse();