UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

322 lines (321 loc) 11.3 kB
#!/usr/bin/env node import path from 'path'; import fs from 'fs/promises'; import { program } from 'commander'; import { extractCodeMapConfig } from './configValidator.js'; import { initializeCaches, clearCaches } from './parser.js'; import { createDirectoryStructure } from './directoryUtils.js'; program .name('cache-cli') .description('CLI tool for managing the code-map-generator file-based cache') .version('1.0.0'); program .command('stats') .description('Show cache statistics') .option('-c, --config <path>', 'Path to the configuration file') .option('-d, --cache-dir <path>', 'Path to the cache directory (overrides config)') .option('-v, --verbose', 'Show verbose output') .action(async (options) => { try { const config = await loadConfig(options); const stats = await getCacheStats(config); displayStats(stats, options.verbose || false); } catch (error) { handleError(error); } }); program .command('clear') .description('Clear the entire cache') .option('-c, --config <path>', 'Path to the configuration file') .option('-d, --cache-dir <path>', 'Path to the cache directory (overrides config)') .option('-f, --force', 'Force clearing without confirmation') .action(async (options) => { try { const config = await loadConfig(options); await clearCache(config, options.force || false); } catch (error) { handleError(error); } }); program .command('prune') .description('Remove old cache entries') .option('-c, --config <path>', 'Path to the configuration file') .option('-d, --cache-dir <path>', 'Path to the cache directory (overrides config)') .option('-a, --age <days>', 'Maximum age in days (default: 7)', '7') .option('-v, --verbose', 'Show verbose output') .action(async (options) => { try { const config = await loadConfig(options); const maxAge = parseInt(options.age || '7') * 24 * 60 * 60 * 1000; await pruneCache(config, maxAge, options.verbose || false); } catch (error) { handleError(error); } }); program .command('validate') .description('Validate the cache integrity') .option('-c, --config <path>', 'Path to the configuration file') .option('-d, --cache-dir <path>', 'Path to the cache directory (overrides config)') .option('-v, --verbose', 'Show verbose output') .action(async (options) => { try { const config = await loadConfig(options); await validateCache(config, options.verbose || false); } catch (error) { handleError(error); } }); async function loadConfig(options) { let config; if (options.config) { try { const configFile = await fs.readFile(options.config, 'utf-8'); const configJson = JSON.parse(configFile); config = await extractCodeMapConfig(configJson); } catch (error) { throw new Error(`Failed to load configuration from ${options.config}: ${error instanceof Error ? error.message : String(error)}`); } } else { config = { allowedMappingDirectory: process.cwd(), cache: { enabled: true, maxEntries: 10000, maxAge: 7 * 24 * 60 * 60 * 1000, } }; } if (options.cacheDir) { if (!config.cache) { config.cache = { enabled: true }; } config.cache.cacheDir = options.cacheDir; } return config; } async function getCacheStats(config) { await initializeCaches(config); const dirStructure = await createDirectoryStructure(config, 'cache-cli'); const cacheDir = dirStructure.cacheDir; const stats = { totalSize: 0, totalFiles: 0, directories: {} }; try { await fs.access(cacheDir); } catch { return stats; } const subdirs = await fs.readdir(cacheDir, { withFileTypes: true }); for (const dirent of subdirs) { if (dirent.isDirectory()) { const subdirPath = path.join(cacheDir, dirent.name); const subdirStats = await getDirectoryStats(subdirPath); stats.directories[dirent.name] = subdirStats; stats.totalSize += subdirStats.size; stats.totalFiles += subdirStats.files; } } return stats; } async function getDirectoryStats(dirPath) { const stats = { size: 0, files: 0 }; try { await fs.access(dirPath); } catch { return stats; } const files = await fs.readdir(dirPath, { withFileTypes: true }); for (const file of files) { if (file.isFile()) { const filePath = path.join(dirPath, file.name); const fileStat = await fs.stat(filePath); stats.size += fileStat.size; stats.files += 1; if (!stats.oldest || fileStat.mtime < stats.oldest.mtime) { stats.oldest = { file: file.name, mtime: fileStat.mtime }; } if (!stats.newest || fileStat.mtime > stats.newest.mtime) { stats.newest = { file: file.name, mtime: fileStat.mtime }; } } else if (file.isDirectory()) { const subdirStats = await getDirectoryStats(path.join(dirPath, file.name)); stats.size += subdirStats.size; stats.files += subdirStats.files; if (subdirStats.oldest && (!stats.oldest || subdirStats.oldest.mtime < stats.oldest.mtime)) { stats.oldest = subdirStats.oldest; } if (subdirStats.newest && (!stats.newest || subdirStats.newest.mtime > stats.newest.mtime)) { stats.newest = subdirStats.newest; } } } return stats; } function displayStats(stats, verbose) { console.log('Cache Statistics:'); console.log(`Total Size: ${formatSize(stats.totalSize)}`); console.log(`Total Files: ${stats.totalFiles}`); if (verbose) { console.log('\nDirectory Breakdown:'); for (const [dir, dirStats] of Object.entries(stats.directories)) { console.log(`\n${dir}:`); console.log(` Size: ${formatSize(dirStats.size)}`); console.log(` Files: ${dirStats.files}`); if (dirStats.oldest) { console.log(` Oldest File: ${dirStats.oldest.file} (${formatDate(dirStats.oldest.mtime)})`); } if (dirStats.newest) { console.log(` Newest File: ${dirStats.newest.file} (${formatDate(dirStats.newest.mtime)})`); } } } } async function clearCache(config, force) { if (!force) { console.log('This will clear the entire cache. Are you sure? (y/n)'); const answer = await new Promise((resolve) => { process.stdin.once('data', (data) => { resolve(data.toString().trim().toLowerCase()); }); }); if (answer !== 'y' && answer !== 'yes') { console.log('Operation cancelled.'); return; } } await clearCaches(); console.log('Cache cleared successfully.'); } async function pruneCache(config, maxAge, verbose) { await initializeCaches(config); const dirStructure = await createDirectoryStructure(config, 'cache-cli'); const cacheDir = dirStructure.cacheDir; try { await fs.access(cacheDir); } catch { console.log('Cache directory does not exist.'); return; } const now = Date.now(); let prunedFiles = 0; let prunedSize = 0; async function pruneDirectory(dirPath) { const files = await fs.readdir(dirPath, { withFileTypes: true }); for (const file of files) { const filePath = path.join(dirPath, file.name); if (file.isFile()) { const fileStat = await fs.stat(filePath); if (now - fileStat.mtimeMs > maxAge) { if (verbose) { console.log(`Pruning: ${filePath}`); } prunedSize += fileStat.size; prunedFiles += 1; await fs.unlink(filePath); } } else if (file.isDirectory()) { await pruneDirectory(filePath); const remainingFiles = await fs.readdir(filePath); if (remainingFiles.length === 0) { if (verbose) { console.log(`Removing empty directory: ${filePath}`); } await fs.rmdir(filePath); } } } } await pruneDirectory(cacheDir); console.log(`Pruned ${prunedFiles} files (${formatSize(prunedSize)}).`); } async function validateCache(config, verbose) { await initializeCaches(config); const dirStructure = await createDirectoryStructure(config, 'cache-cli'); const cacheDir = dirStructure.cacheDir; try { await fs.access(cacheDir); } catch { console.log('Cache directory does not exist.'); return; } let validFiles = 0; let invalidFiles = 0; async function validateDirectory(dirPath) { const files = await fs.readdir(dirPath, { withFileTypes: true }); for (const file of files) { const filePath = path.join(dirPath, file.name); if (file.isFile()) { if (file.name.endsWith('.json')) { try { const fileContent = await fs.readFile(filePath, 'utf-8'); JSON.parse(fileContent); validFiles += 1; if (verbose) { console.log(`Valid: ${filePath}`); } } catch (error) { invalidFiles += 1; console.log(`Invalid: ${filePath} - ${error instanceof Error ? error.message : String(error)}`); await fs.unlink(filePath); } } else { validFiles += 1; } } else if (file.isDirectory()) { await validateDirectory(filePath); } } } await validateDirectory(cacheDir); console.log(`Validation complete: ${validFiles} valid files, ${invalidFiles} invalid files removed.`); } function formatSize(size) { const units = ['B', 'KB', 'MB', 'GB', 'TB']; let unitIndex = 0; let formattedSize = size; while (formattedSize >= 1024 && unitIndex < units.length - 1) { formattedSize /= 1024; unitIndex += 1; } return `${formattedSize.toFixed(2)} ${units[unitIndex]}`; } function formatDate(date) { return date.toISOString().replace('T', ' ').substring(0, 19); } function handleError(error) { console.error(`Error: ${error instanceof Error ? error.message : String(error)}`); process.exit(1); } program.parse(process.argv); if (!process.argv.slice(2).length) { program.outputHelp(); }