UNPKG

capx-compose

Version:

🚀 CLI tool to scaffold AI application projects with best practices and templates for Supabase, Vercel AI, Firebase, Solana, and EVM development. Create production-ready AI apps in seconds.

692 lines (607 loc) 28.1 kB
#!/usr/bin/env node const { Command } = require('commander'); const pkg = require('../package.json'); const { promptUser } = require('../lib/prompt'); const { enhancedPromptUser } = require('../lib/enhanced-prompt'); const { scaffoldProject } = require('../lib/scaffold'); const { listTemplates, validatePluginCombination } = require('../lib/templateDiscovery'); const { PLUGIN_METADATA } = require('../lib/configuration-optimizer'); const chalk = require('chalk'); const TemplateManager = require('../lib/TemplateManager'); const path = require('path'); const { logSuccess, logError, logInfo, logWarning, logHeader, logHighlight, logDim, colors, icons } = require('../lib/console'); function validateProjectName(name) { // npm package name rules: lowercase, numbers, hyphens, no leading dot/underscore, not empty, not reserved const reserved = [ 'node_modules', 'favicon.ico', 'package.json', 'index.js', 'test', 'lib', 'bin', 'src', 'dist', 'public', 'next', 'react', 'react-dom', 'npm', 'npx' ]; if (!name || typeof name !== 'string') { return { valid: false, message: 'Project name is required.' }; } if (name.length > 214) { return { valid: false, message: 'Project name must be less than 214 characters.' }; } if (/^\.|^_/.test(name)) { return { valid: false, message: 'Project name cannot start with a dot or underscore.' }; } if (!/^[a-z0-9\-]+$/.test(name)) { return { valid: false, message: 'Project name must use only lowercase letters, numbers, and hyphens.' }; } if (reserved.includes(name)) { return { valid: false, message: `Project name '${name}' is a reserved or invalid npm name.` }; } return { valid: true }; } function getValidCombinationExamples() { return [ 'capx-compose my-app --plugins=supabase', 'capx-compose my-app --plugins=vercel-ai', 'capx-compose my-app --plugins=supabase,vercel-ai', 'capx-compose my-app --plugins=firebase,vercel-kv', 'capx-compose my-app --plugins=supabase,evm,vercel-ai', 'capx-compose my-app --plugins=firebase,solana,vercel-kv', 'capx-compose my-app --plugins=goat', 'capx-compose my-app --plugins=evm,goat', 'capx-compose my-app --plugins=solana,goat,vercel-ai', 'capx-compose my-app --plugins=solana-agent-kit', 'capx-compose my-app --plugins=solana,solana-agent-kit', 'capx-compose my-app --plugins=privy,evm', 'capx-compose my-app --plugins=privy,solana', 'capx-compose my-app --plugins=sui', 'capx-compose my-app --plugins=sui,vercel-ai', 'capx-compose my-app --plugins=supabase,sui' ]; } function getValidCombinations() { return [ { plugins: ['supabase'], description: 'Database & Auth only' }, { plugins: ['firebase'], description: 'Firebase services only' }, { plugins: ['vercel-ai'], description: 'AI/Chat functionality only' }, { plugins: ['vercel-kv'], description: 'Key-value storage only' }, { plugins: ['supabase', 'vercel-ai'], description: 'Database + AI' }, { plugins: ['firebase', 'vercel-ai'], description: 'Firebase + AI' }, { plugins: ['supabase', 'vercel-kv'], description: 'Database + Cache' }, { plugins: ['firebase', 'vercel-kv'], description: 'Firebase + Cache' }, { plugins: ['supabase', 'evm'], description: 'Database + Ethereum (Web3)' }, { plugins: ['firebase', 'solana'], description: 'Firebase + Solana (Web3)' }, { plugins: ['supabase', 'evm', 'vercel-ai'], description: 'Full-stack Web3 + AI' }, { plugins: ['firebase', 'solana', 'vercel-kv'], description: 'Firebase + Solana + Cache' }, { plugins: ['goat'], description: 'GOAT AI Agent (Web3)' }, { plugins: ['evm', 'goat'], description: 'Wallet Adapter + GOAT Agent (Web3)' }, { plugins: ['solana', 'goat'], description: 'Wallet Adapter + GOAT Agent (Web3)' }, { plugins: ['supabase', 'goat'], description: 'Database + GOAT AI Agent' }, { plugins: ['solana-agent-kit'], description: 'Solana AI Agent (Web3)' }, { plugins: ['solana', 'solana-agent-kit'], description: 'Solana Wallet + AI Agent (Web3)' }, { plugins: ['supabase', 'solana-agent-kit'], description: 'Database + Solana AI Agent' }, { plugins: ['privy', 'evm'], description: 'Privy auth + Ethereum wallets' }, { plugins: ['privy', 'solana'], description: 'Privy auth + Solana wallets' }, { plugins: ['sui'], description: 'SUI Blockchain (Web3)' }, { plugins: ['sui', 'vercel-ai'], description: 'SUI + AI Chat (Web3)' }, { plugins: ['supabase', 'sui'], description: 'Database + SUI (Web3)' } ]; } function showNextStepsGuide(projectName, config, installDependencies) { logHeader('📋 NEXT STEPS GUIDE'); // Calculate dynamic width based on content const maxWidth = Math.max( 47, ` cd ${projectName}`.length + 10, ` http://localhost:3000`.length + 10 ); const border = '─'.repeat(maxWidth); const padLine = (text, width = maxWidth) => { const padding = width - text.length - 2; return text + ' '.repeat(Math.max(0, padding)); }; console.log(chalk.cyan('┌' + border + '┐')); console.log(chalk.cyan('│') + chalk.bold(padLine(' 🎯 QUICK START GUIDE')) + chalk.cyan('│')); console.log(chalk.cyan('│' + padLine('') + '│')); console.log(chalk.cyan('│') + padLine(' 1. Navigate to project:') + chalk.cyan('│')); console.log(chalk.cyan('│') + chalk.dim(padLine(` cd ${projectName}`)) + chalk.cyan('│')); console.log(chalk.cyan('│' + padLine('') + '│')); if (!installDependencies) { console.log(chalk.cyan('│') + padLine(' 2. Install dependencies:') + chalk.cyan('│')); console.log(chalk.cyan('│') + chalk.dim(padLine(' npm install')) + chalk.cyan('│')); console.log(chalk.cyan('│' + padLine('') + '│')); console.log(chalk.cyan('│') + padLine(' 3. Set up environment variables (see below)') + chalk.cyan('│')); console.log(chalk.cyan('│' + padLine('') + '│')); console.log(chalk.cyan('│') + padLine(' 4. Start development server:') + chalk.cyan('│')); console.log(chalk.cyan('│') + chalk.dim(padLine(' npm run dev')) + chalk.cyan('│')); console.log(chalk.cyan('│' + padLine('') + '│')); console.log(chalk.cyan('│') + padLine(' 5. Open in browser:') + chalk.cyan('│')); console.log(chalk.cyan('│') + chalk.dim(padLine(' http://localhost:3000')) + chalk.cyan('│')); } else { console.log(chalk.cyan('│') + padLine(' 2. Set up environment variables (see below)') + chalk.cyan('│')); console.log(chalk.cyan('│' + padLine('') + '│')); console.log(chalk.cyan('│') + padLine(' 3. Start development server:') + chalk.cyan('│')); console.log(chalk.cyan('│') + chalk.dim(padLine(' npm run dev')) + chalk.cyan('│')); console.log(chalk.cyan('│' + padLine('') + '│')); console.log(chalk.cyan('│') + padLine(' 4. Open in browser:') + chalk.cyan('│')); console.log(chalk.cyan('│') + chalk.dim(padLine(' http://localhost:3000')) + chalk.cyan('│')); } console.log(chalk.cyan('└' + border + '┘')); console.log(''); } function getRequiredEnvVars(plugins) { const envVars = []; if (plugins.includes('supabase')) { envVars.push('NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url'); envVars.push('NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key'); } if (plugins.includes('firebase')) { envVars.push('NEXT_PUBLIC_FIREBASE_API_KEY=your_firebase_api_key'); envVars.push('NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com'); envVars.push('NEXT_PUBLIC_FIREBASE_PROJECT_ID=your_project_id'); envVars.push('NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your_project.appspot.com'); envVars.push('NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your_sender_id'); envVars.push('NEXT_PUBLIC_FIREBASE_APP_ID=your_app_id'); } if (plugins.includes('vercel-ai')) { // Check if OPENAI_API_KEY already exists before adding if (!envVars.some(env => env.startsWith('OPENAI_API_KEY='))) { envVars.push('OPENAI_API_KEY=your_openai_api_key'); } } if (plugins.includes('vercel-kv')) { envVars.push('KV_REST_API_URL=your_kv_rest_api_url'); envVars.push('KV_REST_API_TOKEN=your_kv_rest_api_token'); } if (plugins.includes('solana')) { envVars.push('NEXT_PUBLIC_SOLANA_NETWORK=devnet'); envVars.push('NEXT_PUBLIC_RPC_ENDPOINT=https://api.devnet.solana.com'); } if (plugins.includes('evm')) { envVars.push('NEXT_PUBLIC_ETHEREUM_NETWORK=sepolia'); envVars.push('NEXT_PUBLIC_INFURA_PROJECT_ID=your_infura_project_id'); envVars.push('NEXT_PUBLIC_ALCHEMY_API_KEY=your_alchemy_api_key'); } if (plugins.includes('goat')) { // Check if OPENAI_API_KEY already exists before adding if (!envVars.some(env => env.startsWith('OPENAI_API_KEY='))) { envVars.push('OPENAI_API_KEY=your_openai_api_key'); } envVars.push('GOAT_CHAIN=evm # (or solana)'); // Check if RPC_PROVIDER_URL already exists before adding if (!envVars.some(env => env.startsWith('RPC_PROVIDER_URL='))) { envVars.push('RPC_PROVIDER_URL=https://sepolia.base.org # (your RPC endpoint)'); } envVars.push('WALLET_PRIVATE_KEY=0x123... # (for EVM chains)'); // Check if SOLANA_PRIVATE_KEY already exists before adding if (!envVars.some(env => env.startsWith('SOLANA_PRIVATE_KEY='))) { envVars.push('SOLANA_PRIVATE_KEY=base58... # (for Solana)'); } } if (plugins.includes('solana-agent-kit')) { // Check if OPENAI_API_KEY already exists before adding if (!envVars.some(env => env.startsWith('OPENAI_API_KEY='))) { envVars.push('OPENAI_API_KEY=your_openai_api_key'); } // Check if RPC_PROVIDER_URL already exists before adding if (!envVars.some(env => env.startsWith('RPC_PROVIDER_URL='))) { envVars.push('RPC_PROVIDER_URL=https://api.devnet.solana.com'); } // Check if SOLANA_PRIVATE_KEY already exists before adding if (!envVars.some(env => env.startsWith('SOLANA_PRIVATE_KEY='))) { envVars.push('SOLANA_PRIVATE_KEY=base58_encoded_private_key'); } } if (plugins.includes('privy')) { envVars.push('NEXT_PUBLIC_PRIVY_APP_ID=your_privy_app_id'); } if (plugins.includes('sui')) { envVars.push('NEXT_PUBLIC_SUI_NETWORK=testnet # (mainnet, testnet, devnet)'); envVars.push('NEXT_PUBLIC_SUI_RPC_URL=https://fullnode.testnet.sui.io # (optional custom RPC)'); } return envVars; } function showEnvironmentVariables(plugins) { const envVars = getRequiredEnvVars(plugins); if (envVars.length === 0) return; logHeader('🔑 REQUIRED ENVIRONMENT VARIABLES'); logInfo('Create a .env file in the project root with:'); console.log(chalk.dim('```')); envVars.forEach(env => console.log(chalk.yellow(env))); console.log(chalk.dim('```')); console.log(''); } function getFeaturesToExplore(plugins) { const features = []; if (plugins.includes('supabase')) { features.push('🔐 Authentication (sign up, sign in, sign out)'); features.push('📊 Database operations (create, read, update, delete)'); features.push('⚡ Real-time subscriptions and live updates'); features.push('🔒 Row Level Security (RLS) policies'); } if (plugins.includes('firebase')) { features.push('🔐 Firebase authentication with multiple providers'); features.push('🗄️ Firestore database operations and queries'); features.push('📱 Real-time data synchronization'); features.push('☁️ Cloud Functions integration'); } if (plugins.includes('vercel-ai')) { features.push('💬 AI chat interface with streaming responses'); features.push('🤖 OpenAI GPT integration and completions'); features.push('📡 Real-time AI response streaming'); features.push('🎯 Custom AI prompts and fine-tuning'); } if (plugins.includes('vercel-kv')) { features.push('🗃️ Redis cache operations (set, get, delete, expire)'); features.push('⚡ Performance improvements and response caching'); features.push('📈 Session management and user data persistence'); features.push('🔄 Rate limiting and request throttling'); } if (plugins.includes('solana')) { features.push('🔗 Solana wallet integration (Phantom, Solflare)'); features.push('💰 Token operations and balance checking'); features.push('📝 Transaction signing and program interaction'); features.push('🏪 NFT minting and marketplace functionality'); } if (plugins.includes('evm')) { features.push('🦊 MetaMask wallet integration'); features.push('⛓️ Multi-network support (Ethereum, Base, Polygon)'); features.push('📄 Smart contract interaction and deployment'); features.push('🪙 ERC-20 token operations and transfers'); } if (plugins.includes('goat')) { features.push('🐐 AI agent with autonomous wallet management'); features.push('💬 Natural language blockchain operations'); features.push('🔄 Automated token swaps and transfers'); features.push('📊 Real-time balance monitoring and reporting'); } if (plugins.includes('solana-agent-kit')) { features.push('⚡ Advanced Solana AI agent with 60+ operations'); features.push('🔄 Jupiter DEX integration for token swaps'); features.push('💰 SOL and SPL token management'); features.push('📈 Real-time price data and market analytics'); } if (plugins.includes('privy')) { features.push('🔐 Email authentication with verification codes'); features.push('👛 Embedded wallet creation and management'); features.push('💸 Send ETH transactions and sign messages'); features.push('🔑 Secure private key export (optional)'); } if (plugins.includes('sui')) { features.push('🌊 SUI wallet integration (Sui Wallet, Suiet, Martian)'); features.push('💎 SUI token operations and balance checking'); features.push('🚀 Move smart contract interactions'); features.push('⚡ High-performance transactions (120k TPS)'); } return features; } function showFeaturesToExplore(plugins) { const features = getFeaturesToExplore(plugins); if (features.length === 0) return; logHeader('✨ FEATURES TO EXPLORE'); features.forEach(feature => console.log(` ${feature}`)); console.log(''); } function getProjectComplexity(plugins) { const hasBlockchain = plugins.some(p => ['evm', 'solana', 'sui', 'privy'].includes(p)); const hasMultipleServices = plugins.length >= 3; const hasAI = plugins.includes('vercel-ai') || plugins.includes('goat'); if (hasBlockchain && hasMultipleServices && hasAI) { return 'Expert'; } else if (hasBlockchain && hasMultipleServices) { return 'Advanced'; } else if (hasBlockchain || hasMultipleServices) { return 'Medium'; } else { return 'Simple'; } } function showTestingGuidance(plugins) { const complexity = getProjectComplexity(plugins); logHeader('💡 TESTING GUIDANCE'); switch(complexity) { case 'Simple': logInfo('This is a simple setup - perfect for beginners!'); console.log('• Focus on basic functionality and UI components'); console.log('• Test core features without complex integrations'); break; case 'Medium': logInfo('Medium complexity - requires some configuration'); console.log('• Set up all required environment variables'); console.log('• Test integration between different services'); break; case 'Advanced': logInfo('Advanced setup - requires careful configuration'); console.log('• Ensure all services are properly configured'); console.log('• Test complex workflows and edge cases'); break; case 'Expert': logInfo('Expert level - requires deep understanding'); console.log('• Advanced configuration may be needed'); console.log('• Test performance and scalability aspects'); break; } console.log(''); } const program = new Command(); program .name('capx-compose') .version(pkg.version) .description('Scaffold a production-ready Next.js AI application') .arguments('<project-name>') .option('--use-pnpm', 'Use pnpm as package manager') .option('--use-yarn', 'Use yarn as package manager') .option('--skip-install', 'Skip automatic dependency installation') .option('--eslint', 'Include ESLint configuration') .option('--no-eslint', 'Skip ESLint configuration') .option('-y, --yes', 'Answer yes to all prompts') .option('--plugins <list>', `Plugins: 1-4 from [${Object.keys(PLUGIN_METADATA).join(',')}]`) .option('--dependency-strategy <strategy>', 'Dependency conflict resolution strategy (smart, highest, lowest, compatible)', 'smart') .option('--silent', 'Suppress enhanced output (useful for programmatic usage)') .addHelpText('after', ` Examples: ${getValidCombinationExamples().join('\n ')}`) .action(async (projectName, options) => { try { // Clear screen and show header console.clear(); logHeader('🚀 capx-compose - AI Application Generator', true); const validation = validateProjectName(projectName); if (!validation.valid) { logError(validation.message); process.exit(1); } logHighlight(`Creating project: ${colors.bold(projectName)}`); console.log(''); let config; // Parse and validate plugins if provided via CLI let cliPlugins = null; if (options.plugins) { cliPlugins = options.plugins.split(/[,\s]+/).filter(Boolean); // Auto-detect project type based on plugins const hasBlockchain = cliPlugins.some(p => ['evm', 'solana', 'sui', 'goat', 'solana-agent-kit', 'privy'].includes(p)); const detectedProjectType = hasBlockchain ? 'web3' : 'web2'; // Validate plugin combination const validation = validatePluginCombination(cliPlugins, detectedProjectType); if (!validation.valid) { logError(validation.message); console.log(''); logInfo('Valid examples:'); getValidCombinationExamples().forEach(example => logDim(` ${example}`)); process.exit(1); } } if (options.yes) { // Auto-detect project type based on plugins const hasBlockchain = cliPlugins && cliPlugins.some(p => ['evm', 'solana', 'sui', 'goat', 'solana-agent-kit', 'privy'].includes(p)); const projectType = hasBlockchain ? 'web3' : 'web2'; config = { projectType, plugins: cliPlugins || ['vercel-ai'], // Default to vercel-ai if no plugins specified typescript: true, // Always enabled tailwind: true, // Always enabled eslint: false, // Default to false for --yes mode installDependencies: true, // Default to installing dependencies packageManager: null, // Default to auto-detect goatChain: cliPlugins && cliPlugins.includes('goat') ? 'evm' : undefined // Default GOAT to EVM }; logInfo(`Using default configuration (${projectType.toUpperCase()})`); logDim(`Plugins: ${config.plugins.join(', ')}`); } else { logInfo('Starting interactive setup...'); // Use enhanced prompt system by default, fallback to original if needed try { config = await enhancedPromptUser(); } catch (error) { logWarning('Enhanced prompt failed, falling back to basic prompt...'); config = await promptUser(); } // If --plugins is provided, override plugins from prompt if (cliPlugins) { config.plugins = cliPlugins; // Update project type if needed const hasBlockchain = cliPlugins.some(p => ['evm', 'solana', 'sui', 'goat', 'solana-agent-kit', 'privy'].includes(p)); config.projectType = hasBlockchain ? 'web3' : 'web2'; } // Clear screen and show Capx-branded success message with preserved context console.clear(); logHeader('🚀 capx-compose - Configuration Complete', true); logSuccess('Configuration confirmed! Starting project generation...'); console.log(''); // Show preserved context logInfo(`📋 Project Type: ${config.projectType.toUpperCase()}`); // Detect blockchain from selected plugins if (config.projectType === 'web3') { const selectedBlockchain = config.plugins.includes('evm') ? 'EVM' : config.plugins.includes('solana') ? 'SOLANA' : config.plugins.includes('sui') ? 'SUI' : null; if (selectedBlockchain) { logInfo(`⛓️ Blockchain: ${selectedBlockchain}`); } } logInfo(`🔧 Plugins: ${config.plugins.join(', ')}`); console.log(''); } // Handle ESLint CLI flags (override prompt/default) if (options.eslint) { config.eslint = true; } else if (options.noEslint) { config.eslint = false; } // Get templates const templates = listTemplates({ showHidden: false }); let selectedTemplates = []; // Add all selected plugin templates (unified approach for web2 and web3) for (const plugin of config.plugins || []) { const t = templates.find(t => t.sdk === plugin); if (t) { selectedTemplates.push(t); } else { logError(`No template found for plugin '${plugin}'`); } } if (selectedTemplates.length === 0) { logError('No matching templates found for your selection.'); process.exit(1); } console.log(''); logInfo(`Initializing with ${selectedTemplates.length} template${selectedTemplates.length > 1 ? 's' : ''}...`); // Determine package manager preference let packageManager = null; if (options.usePnpm) { packageManager = 'pnpm'; } else if (options.useYarn) { packageManager = 'yarn'; } else if (config.packageManager) { // Use package manager from prompt if no CLI flag specified packageManager = config.packageManager; } // Determine if dependencies should be installed // CLI flags take precedence over prompt configuration let installDependencies; if (options.skipInstall) { installDependencies = false; } else if (config.hasOwnProperty('installDependencies')) { // Use prompt configuration if available installDependencies = config.installDependencies; } else { // Default to true if neither CLI flag nor prompt specified installDependencies = true; } await scaffoldProject(projectName, { ...config, projectName, templates: selectedTemplates, installDependencies, packageManager, dependencyStrategy: options.dependencyStrategy || 'smart', silent: options.silent || false }); // Enhanced success message with detailed guidance (only if not silent) if (!options.silent) { console.log(''); logSuccess(`Project "${projectName}" created successfully! ${icons.rocket}`); console.log(''); // Show project location const projectPath = path.resolve(projectName); logInfo(`📁 Location: ${colors.dim(projectPath)}`); console.log(''); // Show detailed next steps guide showNextStepsGuide(projectName, config, installDependencies); // Show environment variables if needed showEnvironmentVariables(config.plugins); // Show features to explore showFeaturesToExplore(config.plugins); // Show testing guidance based on complexity showTestingGuidance(config.plugins); console.log(''); logHighlight('Happy coding! 🎉'); } } catch (error) { console.log(''); logError('An unexpected error occurred:'); logDim(error.message); process.exit(1); } }); // Template management subcommands const plugins = new Command('plugins') .description('Plugin system operations and debugging tools'); // plugins list plugins .command('list') .description('List available plugins') .option('--all', 'Show hidden plugins') .option('--invalid', 'Show invalid plugins as well') .action(async (opts) => { try { await TemplateManager.initialize(); const showHidden = !!opts.all; const onlyValid = !opts.invalid; const templates = await TemplateManager.listTemplates({ showHidden, onlyValid }); if (!templates.length) { console.log(chalk.yellow('No plugins found.')); return; } console.log(chalk.bold('\nAvailable Plugins:')); for (const t of templates) { const label = `${t.sdk}`; console.log(chalk.green('✔️'), chalk.cyan(label), '-', chalk.gray(t.config.description)); } console.log(); } catch (error) { console.error(chalk.red('Error listing plugins:'), error.message); process.exit(1); } }); // plugins validate plugins .command('validate') .description('Validate all plugins and print a report') .option('--json', 'Output as JSON') .action(async (opts) => { try { await TemplateManager.initialize(); if (opts.json) { const report = await TemplateManager.validateAll({ json: true }); console.log(JSON.stringify(report, null, 2)); } else { await TemplateManager.validateAll({ json: false }); } } catch (error) { console.error(chalk.red('Error validating plugins:'), error.message); process.exit(1); } }); // plugins show <plugin> plugins .command('show <plugin>') .description('Show details for a specific plugin') .action(async (plugin) => { try { await TemplateManager.initialize(); const t = await TemplateManager.findTemplate(plugin, 'default', { showHidden: true, onlyValid: false }); if (!t) { console.log(chalk.red('Plugin not found.')); return; } console.log(chalk.bold(`\n${t.sdk} Plugin`)); console.log(chalk.gray(t.config.description)); console.log(chalk.bold('Path:'), t.path); console.log(chalk.bold('Packages:'), (t.config.packages || []).map(p => p.name).join(', ') || 'None'); if (t.config.devPackages) { console.log(chalk.bold('Dev Packages:'), t.config.devPackages.map(p => p.name).join(', ') || 'None'); } console.log(chalk.bold('Files:')); for (const [src, dest] of Object.entries(t.config.files || {})) { console.log(' ', chalk.cyan(src), '→', chalk.magenta(dest)); } if (t.config.envVars && t.config.envVars.length) { console.log(chalk.bold('Environment Variables:')); for (const v of t.config.envVars) { console.log(' ', chalk.yellow(v.name), '-', v.description, v.required ? chalk.red('(required)') : ''); } } if (t.config.tags) { console.log(chalk.bold('Tags:'), t.config.tags.join(', ')); } if (t.config.visible === false) { console.log(chalk.redBright('This plugin is hidden.')); } console.log(); } catch (error) { console.error(chalk.red('Error showing plugin:'), error.message); process.exit(1); } }); // plugins invalidate-cache plugins .command('invalidate-cache') .description('Clear the plugin cache (for development)') .action(() => { try { TemplateManager.invalidateCache(); console.log(chalk.green('Plugin cache invalidated.')); } catch (error) { console.error(chalk.red('Error invalidating cache:'), error.message); process.exit(1); } }); program.addCommand(plugins); program.parse(process.argv); if (!process.argv.slice(2).length) { program.outputHelp(); }