UNPKG

aios-core

Version:

Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework

941 lines (797 loc) 28.9 kB
#!/usr/bin/env node /** * AIOS-FullStack CLI * Main entry point - Standalone (no external dependencies for npx compatibility) * Version: 4.0.0 */ const path = require('path'); const fs = require('fs'); const { execSync } = require('child_process'); // Read package.json for version const packageJsonPath = path.join(__dirname, '..', 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); // Parse arguments const args = process.argv.slice(2); const command = args[0]; // Helper: Run initialization wizard async function runWizard(options = {}) { // Use the v4 wizard from packages/installer/src/wizard/index.js const wizardPath = path.join(__dirname, '..', 'packages', 'installer', 'src', 'wizard', 'index.js'); if (!fs.existsSync(wizardPath)) { // Fallback to legacy wizard if new wizard not found const legacyScript = path.join(__dirname, 'aios-init.js'); if (fs.existsSync(legacyScript)) { if (!options.quiet) { console.log('⚠️ Using legacy wizard (src/wizard not found)'); } // Legacy wizard doesn't support options, pass via env vars process.env.AIOS_INSTALL_FORCE = options.force ? '1' : ''; process.env.AIOS_INSTALL_QUIET = options.quiet ? '1' : ''; process.env.AIOS_INSTALL_DRY_RUN = options.dryRun ? '1' : ''; require(legacyScript); return; } console.error('❌ Initialization wizard not found'); console.error('Please ensure AIOS-FullStack is installed correctly.'); process.exit(1); } try { // Run the v4 wizard with options const { runWizard: executeWizard } = require(wizardPath); await executeWizard(options); } catch (error) { console.error('❌ Wizard error:', error.message); process.exit(1); } } // Helper: Show help function showHelp() { console.log(` AIOS-FullStack v${packageJson.version} AI-Orchestrated System for Full Stack Development USAGE: npx aios-core@latest # Run installation wizard npx aios-core@latest install # Install in current project npx aios-core@latest init <name> # Create new project npx aios-core@latest update # Update to latest version npx aios-core@latest validate # Validate installation integrity npx aios-core@latest info # Show system info npx aios-core@latest doctor # Run diagnostics npx aios-core@latest --version # Show version npx aios-core@latest --version -d # Show detailed version info npx aios-core@latest --help # Show this help UPDATE: aios update # Update to latest version aios update --check # Check for updates without applying aios update --dry-run # Preview what would be updated aios update --force # Force update even if up-to-date aios update --verbose # Show detailed output VALIDATION: aios validate # Validate installation integrity aios validate --repair # Repair missing/corrupted files aios validate --repair --dry-run # Preview repairs aios validate --detailed # Show detailed file list CONFIGURATION: aios config show # Show resolved configuration aios config show --debug # Show with source annotations aios config diff --levels L1,L2 # Compare config levels aios config migrate # Migrate monolithic to layered aios config validate # Validate config files aios config init-local # Create local-config.yaml SERVICE DISCOVERY: aios workers search <query> # Search for workers aios workers search "json" --category=data aios workers search "transform" --tags=etl,data aios workers search "api" --format=json EXAMPLES: # Install in current directory npx aios-core@latest # Install with minimal mode (only expansion-creator) npx aios-core-minimal@latest # Create new project npx aios-core@latest init my-project # Search for workers aios workers search "json csv" For more information, visit: https://github.com/SynkraAI/aios-core `); } // Helper: Show version async function showVersion() { const isDetailed = args.includes('--detailed') || args.includes('-d'); if (!isDetailed) { // Simple version output (backwards compatible) console.log(packageJson.version); return; } // Detailed version output (Story 7.2: Version Tracking) console.log(`AIOS-FullStack v${packageJson.version}`); console.log('Package: aios-core'); // Check for local installation const localVersionPath = path.join(process.cwd(), '.aios-core', 'version.json'); if (fs.existsSync(localVersionPath)) { try { const versionInfo = JSON.parse(fs.readFileSync(localVersionPath, 'utf8')); console.log('\n📦 Local Installation:'); console.log(` Version: ${versionInfo.version}`); console.log(` Mode: ${versionInfo.mode || 'unknown'}`); if (versionInfo.installedAt) { const installedDate = new Date(versionInfo.installedAt); console.log(` Installed: ${installedDate.toLocaleDateString()}`); } if (versionInfo.updatedAt) { const updatedDate = new Date(versionInfo.updatedAt); console.log(` Updated: ${updatedDate.toLocaleDateString()}`); } if (versionInfo.fileHashes) { const fileCount = Object.keys(versionInfo.fileHashes).length; console.log(` Files: ${fileCount} tracked`); } if (versionInfo.customized && versionInfo.customized.length > 0) { console.log(` Customized: ${versionInfo.customized.length} files`); } // Version comparison if (versionInfo.version !== packageJson.version) { console.log('\n⚠️ Version mismatch!'); console.log(` Local: ${versionInfo.version}`); console.log(` Latest: ${packageJson.version}`); console.log(' Run \'npx aios-core update\' to update.'); } else { console.log('\n✅ Up to date'); } } catch (error) { console.log(`\n⚠️ Could not read version.json: ${error.message}`); } } else { console.log('\n📭 No local installation found'); console.log(' Run \'npx aios-core install\' to install AIOS in this project.'); } } // Helper: Show system info function showInfo() { console.log('📊 AIOS-FullStack System Information\n'); console.log(`Version: ${packageJson.version}`); console.log(`Platform: ${process.platform}`); console.log(`Node.js: ${process.version}`); console.log(`Architecture: ${process.arch}`); console.log(`Working Directory: ${process.cwd()}`); console.log(`Install Location: ${path.join(__dirname, '..')}`); // Check if .aios-core exists const aiosCoreDir = path.join(__dirname, '..', '.aios-core'); if (fs.existsSync(aiosCoreDir)) { console.log('\n✓ AIOS Core installed'); // Count components const countFiles = (dir) => { try { return fs.readdirSync(dir).length; } catch { return 0; } }; const devDir = path.join(aiosCoreDir, 'development'); const componentBase = fs.existsSync(devDir) ? devDir : aiosCoreDir; console.log(` - Agents: ${countFiles(path.join(componentBase, 'agents'))}`); console.log(` - Tasks: ${countFiles(path.join(componentBase, 'tasks'))}`); console.log(` - Templates: ${countFiles(path.join(componentBase, 'templates'))}`); console.log(` - Workflows: ${countFiles(path.join(componentBase, 'workflows'))}`); } else { console.log('\n⚠️ AIOS Core not found'); } // Check AIOS Pro status (Task 5.1) const proDir = path.join(__dirname, '..', 'pro'); if (fs.existsSync(proDir)) { console.log('\n✓ AIOS Pro installed'); try { const { featureGate } = require(path.join(proDir, 'license', 'feature-gate')); const state = featureGate.getLicenseState(); const info = featureGate.getLicenseInfo(); const stateEmoji = { 'Active': '✅', 'Grace': '⚠️', 'Expired': '❌', 'Not Activated': '➖', }; console.log(` - License: ${stateEmoji[state] || ''} ${state}`); if (info && info.features) { const availableCount = featureGate.listAvailable().length; console.log(` - Features: ${availableCount} available`); } } catch { console.log(' - License: Unable to check status'); } } } // Helper: Run installation validation async function runValidate() { const validateArgs = args.slice(1); // Remove 'validate' from args try { // Load the validate command module const { createValidateCommand } = require('../.aios-core/cli/commands/validate/index.js'); const validateCmd = createValidateCommand(); // Parse and execute (Note: don't include 'validate' as it's the command name, not an argument) await validateCmd.parseAsync(['node', 'aios', ...validateArgs]); } catch (_error) { // Fallback: Run quick validation inline console.log('Running installation validation...\n'); try { const validatorPath = path.join( __dirname, '..', 'packages', 'installer', 'src', 'installer', 'post-install-validator.js', ); const { PostInstallValidator, formatReport } = require(validatorPath); const projectRoot = process.cwd(); const validator = new PostInstallValidator(projectRoot, path.join(__dirname, '..')); const report = await validator.validate(); console.log(formatReport(report, { colors: true })); if ( report.status === 'failed' || report.stats.missingFiles > 0 || report.stats.corruptedFiles > 0 ) { process.exit(1); } } catch (validatorError) { console.error(`❌ Validation error: ${validatorError.message}`); if (args.includes('--verbose') || args.includes('-v')) { console.error(validatorError.stack); } process.exit(2); } } } // Helper: Run update command async function runUpdate() { const updateArgs = args.slice(1); const isCheck = updateArgs.includes('--check'); const isDryRun = updateArgs.includes('--dry-run'); const isForce = updateArgs.includes('--force'); const isVerbose = updateArgs.includes('--verbose') || updateArgs.includes('-v'); try { const updaterPath = path.join(__dirname, '..', 'packages', 'installer', 'src', 'updater', 'index.js'); if (!fs.existsSync(updaterPath)) { console.error('❌ Updater module not found'); console.error('Please ensure AIOS-FullStack is installed correctly.'); process.exit(1); } const { AIOSUpdater, formatCheckResult, formatUpdateResult } = require(updaterPath); const updater = new AIOSUpdater(process.cwd(), { verbose: isVerbose, force: isForce, }); if (isCheck) { // Check only mode console.log('🔍 Checking for updates...\n'); const result = await updater.checkForUpdates(); console.log(formatCheckResult(result, { colors: true })); if (result.status === 'check_failed') { process.exit(1); } } else { // Update mode console.log('🔄 AIOS Update\n'); const result = await updater.update({ dryRun: isDryRun, onProgress: (phase, message) => { if (isVerbose) { console.log(`[${phase}] ${message}`); } }, }); console.log(formatUpdateResult(result, { colors: true })); if (!result.success && result.error !== 'Already up to date') { process.exit(1); } } } catch (error) { console.error(`❌ Update error: ${error.message}`); if (args.includes('--verbose') || args.includes('-v')) { console.error(error.stack); } process.exit(1); } } // Helper: Run doctor diagnostics (v2.0 — delegates to modular doctor) async function runDoctor(options = {}) { const { runDoctorChecks } = require(path.join(__dirname, '..', '.aios-core', 'core', 'doctor')); const result = await runDoctorChecks({ fix: options.fix || false, json: options.json || false, dryRun: options.dryRun || false, quiet: options.quiet || false, projectRoot: process.cwd(), }); console.log(result.formatted); // Exit with code 1 if any FAIL results if (result.data && result.data.summary && result.data.summary.fail > 0) { process.exit(1); } } // Helper: Format bytes to human readable function formatBytes(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } // Helper: Remove AIOS sections from .gitignore function cleanGitignore(gitignorePath) { if (!fs.existsSync(gitignorePath)) return { removed: false }; const content = fs.readFileSync(gitignorePath, 'utf8'); const lines = content.split('\n'); const newLines = []; let inAiosSection = false; let removedLines = 0; for (const line of lines) { if (line.includes('# AIOS') || line.includes('# Added by AIOS')) { inAiosSection = true; removedLines++; continue; } if (inAiosSection && line.trim() === '') { inAiosSection = false; continue; } if (inAiosSection) { removedLines++; continue; } newLines.push(line); } if (removedLines > 0) { fs.writeFileSync(gitignorePath, newLines.join('\n')); return { removed: true, lines: removedLines }; } return { removed: false }; } // Helper: Show uninstall help function showUninstallHelp() { console.log(` Usage: npx aios-core uninstall [options] Remove AIOS from the current project. Options: --force Skip confirmation prompt --keep-data Keep .aios/ directory (settings and history) --dry-run Show what would be removed without removing -h, --help Show this help message What gets removed: - .aios-core/ Framework core files - docs/stories/ Story files (if created by AIOS) - squads/ Squad definitions - .gitignore AIOS-added entries only What is preserved (with --keep-data): - .aios/ Project settings and agent history Exit Codes: 0 Uninstall successful 1 Uninstall failed or cancelled Examples: # Interactive uninstall (with confirmation) npx aios-core uninstall # Force uninstall without prompts npx aios-core uninstall --force # See what would be removed npx aios-core uninstall --dry-run # Uninstall but keep project data npx aios-core uninstall --keep-data `); } // Helper: Show doctor help function showDoctorHelp() { console.log(` Usage: npx aios-core doctor [options] Run health checks on your AIOS installation. Options: --fix Automatically fix detected issues --dry-run Show what --fix would do without making changes --json Output results as structured JSON --quiet Minimal output (exit code only) -h, --help Show this help message Checks performed: • Required directories exist (.aios-core/, .aios/) • Configuration files are valid JSON/YAML • Agent definitions are complete • Task files have required fields • Dependencies are installed Exit Codes: 0 All checks passed (or issues fixed with --fix) 1 Issues detected (run with --fix to repair) Examples: # Run health check npx aios-core doctor # Auto-fix detected issues npx aios-core doctor --fix # Preview what would be fixed npx aios-core doctor --fix --dry-run `); } // Uninstall AIOS from project async function runUninstall(options = {}) { const { force = false, keepData = false, dryRun = false, quiet = false } = options; const cwd = process.cwd(); // Items to remove const itemsToRemove = [ { path: '.aios-core', description: 'Framework core' }, { path: 'squads', description: 'Squad definitions' }, ]; // Optionally remove .aios if (!keepData) { itemsToRemove.push({ path: '.aios', description: 'Project data and settings' }); } // Check what exists const existingItems = itemsToRemove.filter(item => fs.existsSync(path.join(cwd, item.path)), ); if (existingItems.length === 0) { console.log('ℹ️ No AIOS installation found in this directory.'); return; } // Calculate total size let totalSize = 0; const itemSizes = []; for (const item of existingItems) { const itemPath = path.join(cwd, item.path); try { const stats = fs.statSync(itemPath); if (stats.isDirectory()) { // Simple recursive size calculation const getSize = (dir) => { let size = 0; try { const files = fs.readdirSync(dir); for (const file of files) { const filePath = path.join(dir, file); const stat = fs.statSync(filePath); if (stat.isDirectory()) { size += getSize(filePath); } else { size += stat.size; } } } catch { /* ignore errors */ } return size; }; const size = getSize(itemPath); totalSize += size; itemSizes.push({ ...item, size }); } else { totalSize += stats.size; itemSizes.push({ ...item, size: stats.size }); } } catch { itemSizes.push({ ...item, size: 0 }); } } // Show what will be removed if (!quiet) { console.log('\n📋 Items to be removed:\n'); for (const item of itemSizes) { const sizeStr = item.size > 0 ? ` (${formatBytes(item.size)})` : ''; console.log(` • ${item.path}/${sizeStr} - ${item.description}`); } console.log(`\n Total: ${formatBytes(totalSize)}`); // Check for .gitignore cleanup const gitignorePath = path.join(cwd, '.gitignore'); if (fs.existsSync(gitignorePath)) { const content = fs.readFileSync(gitignorePath, 'utf8'); if (content.includes('# AIOS') || content.includes('# Added by AIOS')) { console.log(' • .gitignore AIOS entries will be cleaned'); } } console.log(''); } // Dry run - stop here if (dryRun) { console.log('🔍 Dry run - no changes made.'); return; } // Confirmation if (!force) { const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); const answer = await new Promise(resolve => { rl.question('⚠️ Are you sure you want to uninstall AIOS? (y/N): ', resolve); }); rl.close(); if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') { console.log('❌ Uninstall cancelled.'); process.exit(1); } } // Perform removal if (!quiet) console.log('\n🗑️ Removing AIOS components...\n'); for (const item of existingItems) { const itemPath = path.join(cwd, item.path); try { fs.rmSync(itemPath, { recursive: true, force: true }); if (!quiet) console.log(` ✓ Removed ${item.path}/`); } catch (error) { console.error(` ✗ Failed to remove ${item.path}: ${error.message}`); } } // Clean .gitignore const gitignorePath = path.join(cwd, '.gitignore'); const gitignoreResult = cleanGitignore(gitignorePath); if (gitignoreResult.removed && !quiet) { console.log(` ✓ Cleaned ${gitignoreResult.lines} AIOS entries from .gitignore`); } // Summary if (!quiet) { console.log('\n✅ AIOS has been uninstalled.'); if (keepData) { console.log(' Your project data in .aios/ has been preserved.'); } console.log('\n To reinstall: npx aios-core install'); } } // Helper: Show install help function showInstallHelp() { console.log(` Usage: npx aios-core install [options] Install AIOS in the current directory. Options: --force Overwrite existing AIOS installation --quiet Minimal output (no banner, no prompts) - ideal for CI/CD --dry-run Simulate installation without modifying files --merge Auto-merge existing config files (brownfield mode) --no-merge Disable merge option, use legacy overwrite behavior -h, --help Show this help message Smart Merge (Brownfield): When installing in a project with existing config files (.env, CLAUDE.md), AIOS can merge new settings while preserving your customizations. - .env files: Adds new variables, preserves existing values - CLAUDE.md: Updates AIOS sections, keeps your custom rules Exit Codes: 0 Installation successful 1 Installation failed Examples: # Interactive installation npx aios-core install # Force reinstall without prompts npx aios-core install --force # Brownfield: merge configs automatically npx aios-core install --merge # Silent install for CI/CD npx aios-core install --quiet --force # Preview what would be installed npx aios-core install --dry-run `); } // Helper: Create new project // Helper: Show init help function showInitHelp() { console.log(` Usage: npx aios-core init <project-name> [options] Create a new AIOS project with the specified name. Options: --force Force creation in non-empty directory --skip-install Skip npm dependency installation --template <name> Use specific template (default: default) -t <name> Shorthand for --template -h, --help Show this help message Available Templates: default Full installation with all agents, tasks, and workflows minimal Essential files only (dev agent + basic tasks) enterprise Everything + dashboards + team integrations Examples: npx aios-core init my-project npx aios-core init my-project --template minimal npx aios-core init my-project --force --skip-install npx aios-core init . --template enterprise `); } async function initProject() { // 1. Parse ALL args after 'init' const initArgs = args.slice(1); // 2. Handle --help FIRST (before creating any directories) if (initArgs.includes('--help') || initArgs.includes('-h')) { showInitHelp(); return; } // 3. Parse flags const isForce = initArgs.includes('--force'); const skipInstall = initArgs.includes('--skip-install'); // Template with argument const templateIndex = initArgs.findIndex((a) => a === '--template' || a === '-t'); let template = 'default'; if (templateIndex !== -1) { template = initArgs[templateIndex + 1]; if (!template || template.startsWith('-')) { console.error('❌ --template requires a template name'); console.error('Available templates: default, minimal, enterprise'); process.exit(1); } } // Validate template const validTemplates = ['default', 'minimal', 'enterprise']; if (!validTemplates.includes(template)) { console.error(`❌ Unknown template: ${template}`); console.error(`Available templates: ${validTemplates.join(', ')}`); process.exit(1); } // 4. Extract project name (anything that doesn't start with - and isn't a template value) const projectName = initArgs.find((arg, i) => { if (arg.startsWith('-')) return false; // Skip if it's the value after --template const prevArg = initArgs[i - 1]; if (prevArg === '--template' || prevArg === '-t') return false; return true; }); if (!projectName) { console.error('❌ Project name is required'); console.log('\nUsage: npx aios-core init <project-name> [options]'); console.log('Run with --help for more information.'); process.exit(1); } // 5. Handle "." to install in current directory const isCurrentDir = projectName === '.'; const targetPath = isCurrentDir ? process.cwd() : path.join(process.cwd(), projectName); const displayName = isCurrentDir ? path.basename(process.cwd()) : projectName; // 6. Check if directory exists if (fs.existsSync(targetPath) && !isCurrentDir) { const contents = fs.readdirSync(targetPath).filter((f) => !f.startsWith('.')); if (contents.length > 0 && !isForce) { console.error(`❌ Directory already exists and is not empty: ${projectName}`); console.error('Use --force to overwrite.'); process.exit(1); } if (contents.length > 0 && isForce) { console.log(`⚠️ Using --force: overwriting existing directory: ${projectName}`); } else { console.log(`✓ Using existing empty directory: ${projectName}`); } } else if (!fs.existsSync(targetPath)) { fs.mkdirSync(targetPath, { recursive: true }); console.log(`✓ Created directory: ${projectName}`); } console.log(`Creating new AIOS project: ${displayName}`); if (template !== 'default') { console.log(`Template: ${template}`); } if (skipInstall) { console.log('Skip install: enabled'); } console.log(''); // 7. Change to project directory (if not already there) if (!isCurrentDir) { process.chdir(targetPath); } // 8. Run the initialization wizard with options await runWizard({ template, skipInstall, force: isForce, }); } // Command routing (async main function) async function main() { switch (command) { case 'workers': // Service Discovery CLI - Story 2.7 try { const { run } = require('../.aios-core/cli/index.js'); await run(process.argv); } catch (error) { console.error(`❌ Workers command error: ${error.message}`); process.exit(1); } break; case 'config': // Layered Configuration CLI - Story PRO-4 try { const { run } = require('../.aios-core/cli/index.js'); await run(process.argv); } catch (error) { console.error(`❌ Config command error: ${error.message}`); process.exit(1); } break; case 'pro': // AIOS Pro License Management - Story PRO-6 try { const { run } = require('../.aios-core/cli/index.js'); await run(process.argv); } catch (error) { console.error(`❌ Pro command error: ${error.message}`); process.exit(1); } break; case 'install': { // Install in current project with flag support const installArgs = args.slice(1); if (installArgs.includes('--help') || installArgs.includes('-h')) { showInstallHelp(); break; } const installOptions = { force: installArgs.includes('--force'), quiet: installArgs.includes('--quiet'), dryRun: installArgs.includes('--dry-run'), forceMerge: installArgs.includes('--merge'), noMerge: installArgs.includes('--no-merge'), }; if (!installOptions.quiet) { console.log('AIOS-FullStack Installation\n'); } await runWizard(installOptions); break; } case 'uninstall': { // Uninstall AIOS from project const uninstallArgs = args.slice(1); if (uninstallArgs.includes('--help') || uninstallArgs.includes('-h')) { showUninstallHelp(); break; } const uninstallOptions = { force: uninstallArgs.includes('--force'), keepData: uninstallArgs.includes('--keep-data'), dryRun: uninstallArgs.includes('--dry-run'), quiet: uninstallArgs.includes('--quiet'), }; await runUninstall(uninstallOptions); break; } case 'init': { // Create new project (flags parsed inside initProject) await initProject(); break; } case 'info': showInfo(); break; case 'doctor': { // Run health check with flag support const doctorArgs = args.slice(1); if (doctorArgs.includes('--help') || doctorArgs.includes('-h')) { showDoctorHelp(); break; } const doctorOptions = { fix: doctorArgs.includes('--fix'), json: doctorArgs.includes('--json'), dryRun: doctorArgs.includes('--dry-run'), quiet: doctorArgs.includes('--quiet'), }; await runDoctor(doctorOptions); break; } case 'validate': // Post-installation validation - Story 6.19 await runValidate(); break; case 'update': // Update to latest version - Epic 7 await runUpdate(); break; case '--version': case '-v': case '-V': await showVersion(); break; case '--help': case '-h': showHelp(); break; case undefined: // No arguments - run wizard directly (npx default behavior) console.log('AIOS-FullStack Installation\n'); await runWizard(); break; default: console.error(`❌ Unknown command: ${command}`); console.log('\nRun with --help to see available commands'); process.exit(1); } } // Execute main function main().catch((error) => { console.error('❌ Fatal error:', error.message); process.exit(1); });