UNPKG

@vibe-dev-kit/cli

Version:

Advanced Command-line toolkit that analyzes your codebase and deploys project-aware rules, memories, commands and agents to any AI coding assistant - VDK is the world's first Vibe Development Kit

287 lines (250 loc) 9.38 kB
#!/usr/bin/env node /** * VDK CLI * ----------------------- * This is the main entry point for the VDK command-line interface. * It orchestrates commands for initializing projects, managing rules, and deploying to the VDK Hub. * * Repository: https://github.com/entro314-labs/VDK-CLI */ import fs from 'node:fs/promises' import { createRequire } from 'node:module' import path from 'node:path' import { fileURLToPath } from 'node:url' import { Command } from 'commander' import dotenv from 'dotenv' import { downloadRule, fetchRuleList } from './src/blueprints-client.js' import { runScanner } from './src/scanner/index.js' import { banner, boxes, colors, format, headers, spinners, status, tables, } from './src/utils/cli-styles.js' // Get the directory where cli.js is located (VDK CLI directory) const __dirname = path.dirname(fileURLToPath(import.meta.url)) // Try loading .env.local first from CLI directory, then fall back to .env dotenv.config({ path: path.join(__dirname, '.env.local') }) dotenv.config({ path: path.join(__dirname, '.env') }) const require = createRequire(import.meta.url) const pkg = require('./package.json') const program = new Command() program .name('vdk') .description("VDK CLI: The world's first Vibe Development Kit - One Context, All AI Assistants") .version(pkg.version) program .command('init') .description('Initialize VDK and generate project-aware AI rules by scanning the project') .option('-p, --projectPath <path>', 'Path to the project to scan', process.cwd()) .option('-o, --outputPath <path>', 'Path where generated rules should be saved', './.ai/rules') .option('-d, --deep', 'Enable deep scanning for more thorough pattern detection', false) .option('-i, --ignorePattern <patterns...>', 'Glob patterns to ignore', [ '**/node_modules/**', '**/dist/**', '**/build/**', ]) .option( '--use-gitignore', 'Automatically parse .gitignore files for additional ignore patterns', true ) .option('-t, --template <name>', 'Name of the rule template to use', 'default') .option('--overwrite', 'Overwrite existing rule files without prompting', false) .option('--ide-integration', 'Enable IDE integration setup', true) .option('--watch', 'Enable watch mode for continuous IDE integration updates', false) .option('-v, --verbose', 'Enable verbose output for debugging', false) .option( '--categories <categories...>', 'Specific command categories to fetch (e.g., development, testing, workflow)' ) .option( '--preset <preset>', 'Preset command collection (minimal, full, development, production)', 'auto' ) .option('--interactive', 'Enable interactive category selection', false) .action(async (options) => { try { const results = await runScanner(options) // Create the VDK configuration file const configPath = path.join(options.projectPath, 'vdk.config.json') const config = { project: { name: results.projectName, }, ide: results.initializedIDEs[0] || 'generic', rulesPath: options.outputPath, lastUpdated: new Date().toISOString(), } await fs.writeFile(configPath, JSON.stringify(config, null, 2)) console.log(status.success(`VDK configuration created at ${format.path(configPath)}`)) // Handle watch mode if (options.watch && results.ideIntegration) { console.log(status.info('Watch mode enabled. Press Ctrl+C to exit.')) process.on('SIGINT', () => { results.ideIntegration.shutdown() process.exit(0) }) // Keep the process running in watch mode await new Promise(() => {}) } } catch (_error) { // The scanner engine already logs errors, so we just exit to prevent double logging process.exit(1) } }) program .command('deploy') .description('Deploy project-aware rules (Under Development)') .action(() => { console.log( boxes.warning( 'This command is under development.\nThe `deploy` command will be used to send your VDK rules to the VDK Hub.', 'Coming Soon' ) ) }) program .command('update') .description('Update VDK blueprints from the VDK-Blueprints repository') .option('-o, --outputPath <path>', 'Path to the rules directory', './.ai/rules') .action(async (options) => { const rulesDir = path.resolve(options.outputPath) console.log(headers.section('VDK Blueprint Update')) const spinner = spinners.updating('Checking for updates in VDK-Blueprints repository...') spinner.start() try { // Ensure local rules directory exists await fs.mkdir(rulesDir, { recursive: true }) const remoteRules = await fetchRuleList() if (remoteRules.length === 0) { spinner.fail('No blueprints found in the VDK-Blueprints repository or failed to connect.') return } const localRules = await fs.readdir(rulesDir).catch(() => []) let updatedCount = 0 let newCount = 0 spinner.text = `Found ${format.count(remoteRules.length)} blueprints. Comparing with local rules...` for (const remoteRule of remoteRules) { spinner.text = `Processing ${remoteRule.name}...` const localPath = path.join(rulesDir, remoteRule.name) const ruleContent = await downloadRule(remoteRule.download_url) if (ruleContent) { if (localRules.includes(remoteRule.name)) { await fs.writeFile(localPath, ruleContent) updatedCount++ } else { await fs.writeFile(localPath, ruleContent) newCount++ } } } spinner.stop() if (newCount > 0 || updatedCount > 0) { console.log(status.success('Update complete!')) if (newCount > 0) { console.log(status.progress(`Added ${format.count(newCount)} new rule(s)`)) } if (updatedCount > 0) { console.log(status.progress(`Updated ${format.count(updatedCount)} existing rule(s)`)) } } else { console.log(status.success('Your rules are already up to date')) } } catch (error) { if (spinner.isSpinning) { spinner.fail('Update failed') } console.log(boxes.error(`An error occurred during the update:\n${error.message}`)) process.exit(1) } }) program .command('status') .description('Check the status of your VDK setup') .option('-c, --configPath <path>', 'Path to the VDK configuration file', './vdk.config.json') .option('-o, --outputPath <path>', 'Path to the rules directory', './.ai/rules') .action(async (options) => { console.log(headers.section('VDK Status Check')) const configPath = path.resolve(options.configPath) const rulesDir = path.resolve(options.outputPath) const spinner = spinners.scanning('Checking VDK status...') spinner.start() const statusTable = tables.status() let isConfigured = false // 1. Check for VDK configuration file try { await fs.access(configPath) const config = JSON.parse(await fs.readFile(configPath, 'utf8')) statusTable.push([ 'VDK Configuration', status.success('Found'), `${format.keyValue('Project', config.project.name)}\n${format.keyValue('IDE', config.ide)}`, ]) isConfigured = true } catch (_error) { statusTable.push([ 'VDK Configuration', status.warning('Missing'), `Run ${colors.primary('vdk init')} to get started`, ]) } // 2. Check local and remote rules try { const localRules = await fs.readdir(rulesDir).catch(() => []) statusTable.push([ 'Local Rules', status.success('Found'), `${format.count(localRules.length)} rules in ${format.path(rulesDir)}`, ]) const remoteRules = await fetchRuleList() if (remoteRules.length > 0) { const remoteRuleNames = remoteRules.map((r) => r.name) const newRules = remoteRuleNames.filter((r) => !localRules.includes(r)) if (newRules.length > 0) { statusTable.push([ 'VDK Hub', status.warning('Updates Available'), `${format.count(newRules.length)} new rules available\nRun ${colors.primary('vdk update')} to sync`, ]) } else { statusTable.push([ 'VDK Hub', status.success('Up to Date'), `${format.count(remoteRules.length)} total rules in sync`, ]) } } else { statusTable.push([ 'VDK Hub', status.error('Unreachable'), 'Could not connect to VDK-Blueprints repository', ]) } } catch (error) { statusTable.push(['Rule Status', status.error('Error'), error.message]) } spinner.stop() console.log(statusTable.toString()) if (!isConfigured) { console.log( `\n${boxes.info( `Get started by running:\n${colors.primary('vdk init')}\n\nThis will scan your project and create project-aware AI rules.`, 'Quick Start' )}` ) } }) // Show banner when no arguments provided if (process.argv.slice(2).length === 0) { console.log(banner()) } program.parse(process.argv) if (process.argv.slice(2).length === 0) { program.outputHelp() }