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
JavaScript
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();
}