UNPKG

erosolar-cli

Version:

Unified AI agent framework for the command line - Multi-provider support with schema-driven tools, code intelligence, and transparent reasoning

1,041 lines (1,020 loc) 59.5 kB
/** * Cloud Deployment Tools - Unified cloud CLI capabilities with auto-detection and auto-fix * * Supports: * - Firebase (Hosting, Functions, Firestore, Storage, Auth) * - Aliyun/Alibaba Cloud (OSS, FC, ACR, etc.) * - AWS (S3, Lambda, CloudFront, etc.) * - GCP (Cloud Run, Storage, Functions) * - Azure (Static Web Apps, Functions, Blob Storage) * - Vercel, Netlify, Cloudflare Pages * * Features: * - Automatic CLI detection and installation checking * - Authentication status verification * - Auto-fix for common issues * - AI-driven error resolution * * @license MIT * @author Bo Shang */ import { spawn } from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; import { verifiedSuccess, verifiedFailure, unverifiedResult, requiresUserAction, partialSuccess, analyzeOutput, OutputPatterns, verifyUrlAccessible, } from '../core/resultVerification.js'; const CLOUD_PROVIDERS = { firebase: { id: 'firebase', name: 'Firebase', cliCommand: 'firebase', versionFlag: '--version', installCommand: 'npm install -g firebase-tools', installUrl: 'https://firebase.google.com/docs/cli', loginCommand: 'firebase login', checkAuthCommand: 'firebase projects:list', configFile: 'firebase.json', envVars: ['FIREBASE_TOKEN', 'GOOGLE_APPLICATION_CREDENTIALS'], }, aliyun: { id: 'aliyun', name: 'Alibaba Cloud (Aliyun)', cliCommand: 'aliyun', versionFlag: 'version', installCommand: 'brew install aliyun-cli || curl -O https://aliyuncli.alicdn.com/aliyun-cli-linux-amd64-*.tgz', installUrl: 'https://www.alibabacloud.com/help/doc-detail/139508.htm', loginCommand: 'aliyun configure', checkAuthCommand: 'aliyun ecs DescribeRegions', configFile: '~/.aliyun/config.json', envVars: ['ALIBABA_CLOUD_ACCESS_KEY_ID', 'ALIBABA_CLOUD_ACCESS_KEY_SECRET'], }, aws: { id: 'aws', name: 'Amazon Web Services', cliCommand: 'aws', versionFlag: '--version', installCommand: 'brew install awscli || pip install awscli', installUrl: 'https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html', loginCommand: 'aws configure', checkAuthCommand: 'aws sts get-caller-identity', configFile: '~/.aws/credentials', envVars: ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN'], }, gcloud: { id: 'gcloud', name: 'Google Cloud Platform', cliCommand: 'gcloud', versionFlag: '--version', installCommand: 'brew install --cask google-cloud-sdk', installUrl: 'https://cloud.google.com/sdk/docs/install', loginCommand: 'gcloud auth login', checkAuthCommand: 'gcloud auth list', configFile: '~/.config/gcloud/credentials.db', envVars: ['GOOGLE_APPLICATION_CREDENTIALS', 'CLOUDSDK_CORE_PROJECT'], }, azure: { id: 'azure', name: 'Microsoft Azure', cliCommand: 'az', versionFlag: '--version', installCommand: 'brew install azure-cli || curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash', installUrl: 'https://docs.microsoft.com/en-us/cli/azure/install-azure-cli', loginCommand: 'az login', checkAuthCommand: 'az account show', configFile: '~/.azure/accessTokens.json', envVars: ['AZURE_CLIENT_ID', 'AZURE_CLIENT_SECRET', 'AZURE_TENANT_ID'], }, vercel: { id: 'vercel', name: 'Vercel', cliCommand: 'vercel', versionFlag: '--version', installCommand: 'npm install -g vercel', installUrl: 'https://vercel.com/docs/cli', loginCommand: 'vercel login', checkAuthCommand: 'vercel whoami', envVars: ['VERCEL_TOKEN'], }, netlify: { id: 'netlify', name: 'Netlify', cliCommand: 'netlify', versionFlag: '--version', installCommand: 'npm install -g netlify-cli', installUrl: 'https://docs.netlify.com/cli/get-started/', loginCommand: 'netlify login', checkAuthCommand: 'netlify status', envVars: ['NETLIFY_AUTH_TOKEN'], }, cloudflare: { id: 'cloudflare', name: 'Cloudflare', cliCommand: 'wrangler', versionFlag: '--version', installCommand: 'npm install -g wrangler', installUrl: 'https://developers.cloudflare.com/workers/wrangler/install-and-update/', loginCommand: 'wrangler login', checkAuthCommand: 'wrangler whoami', envVars: ['CLOUDFLARE_API_TOKEN', 'CLOUDFLARE_ACCOUNT_ID'], }, fly: { id: 'fly', name: 'Fly.io', cliCommand: 'flyctl', versionFlag: 'version', installCommand: 'brew install flyctl || curl -L https://fly.io/install.sh | sh', installUrl: 'https://fly.io/docs/hands-on/install-flyctl/', loginCommand: 'flyctl auth login', checkAuthCommand: 'flyctl auth whoami', envVars: ['FLY_API_TOKEN'], }, railway: { id: 'railway', name: 'Railway', cliCommand: 'railway', versionFlag: '--version', installCommand: 'npm install -g @railway/cli', installUrl: 'https://docs.railway.app/develop/cli', loginCommand: 'railway login', checkAuthCommand: 'railway whoami', envVars: ['RAILWAY_TOKEN'], }, supabase: { id: 'supabase', name: 'Supabase', cliCommand: 'supabase', versionFlag: '--version', installCommand: 'npm install -g supabase', installUrl: 'https://supabase.com/docs/guides/cli', loginCommand: 'supabase login', checkAuthCommand: 'supabase projects list', envVars: ['SUPABASE_ACCESS_TOKEN'], }, }; async function runCommand(command, timeout = 30000) { return new Promise((resolve) => { const child = spawn('sh', ['-c', command], { timeout, env: { ...process.env }, }); let stdout = ''; let stderr = ''; child.stdout?.on('data', (data) => { stdout += data.toString(); }); child.stderr?.on('data', (data) => { stderr += data.toString(); }); child.on('close', (code) => { resolve({ stdout: stdout.trim(), stderr: stderr.trim(), exitCode: code ?? 1 }); }); child.on('error', (err) => { resolve({ stdout: '', stderr: err.message, exitCode: 1 }); }); }); } async function checkCLIInstalled(provider) { try { const result = await runCommand(`which ${provider.cliCommand} && ${provider.cliCommand} ${provider.versionFlag} 2>&1 | head -5`); if (result.exitCode === 0 && result.stdout) { // Extract version from output const versionMatch = result.stdout.match(/(\d+\.\d+\.\d+[\w.-]*)/); return { installed: true, version: versionMatch?.[1] || result.stdout.split('\n')[1] || 'unknown', }; } return { installed: false, error: result.stderr || 'CLI not found in PATH' }; } catch (error) { return { installed: false, error: error instanceof Error ? error.message : String(error) }; } } async function checkAuthentication(provider) { try { const result = await runCommand(provider.checkAuthCommand, 15000); if (result.exitCode === 0) { return { authenticated: true, details: result.stdout.slice(0, 500), }; } // Check for specific auth errors const errorLower = (result.stderr + result.stdout).toLowerCase(); if (errorLower.includes('not logged in') || errorLower.includes('authentication') || errorLower.includes('credentials') || errorLower.includes('login') || errorLower.includes('token') || errorLower.includes('unauthorized')) { return { authenticated: false, error: `Not authenticated. Run: ${provider.loginCommand}`, }; } return { authenticated: false, error: result.stderr || result.stdout }; } catch (error) { return { authenticated: false, error: error instanceof Error ? error.message : String(error) }; } } async function checkConfigFile(provider, workingDir) { if (!provider.configFile) { return true; } // Check for project-level config const projectConfig = path.join(workingDir, provider.configFile); if (fs.existsSync(projectConfig)) { return true; } // Check for user-level config (expand ~) if (provider.configFile.startsWith('~')) { const homeConfig = provider.configFile.replace('~', process.env['HOME'] || ''); if (fs.existsSync(homeConfig)) { return true; } } return false; } async function getFullCLIStatus(providerId, workingDir) { const provider = CLOUD_PROVIDERS[providerId]; if (!provider) { return { installed: false, authenticated: false, configExists: false, errors: [`Unknown provider: ${providerId}`], suggestions: [`Available providers: ${Object.keys(CLOUD_PROVIDERS).join(', ')}`], }; } const status = { installed: false, authenticated: false, configExists: false, errors: [], suggestions: [], }; // Check installation const installCheck = await checkCLIInstalled(provider); status.installed = installCheck.installed; status.version = installCheck.version; if (!installCheck.installed) { status.errors.push(`${provider.name} CLI not installed`); status.suggestions.push(`Install with: ${provider.installCommand}`); status.suggestions.push(`Documentation: ${provider.installUrl}`); return status; } // Check authentication const authCheck = await checkAuthentication(provider); status.authenticated = authCheck.authenticated; status.authDetails = authCheck.details; if (!authCheck.authenticated) { status.errors.push(authCheck.error || 'Not authenticated'); status.suggestions.push(`Authenticate with: ${provider.loginCommand}`); if (provider.envVars?.length) { status.suggestions.push(`Or set environment variables: ${provider.envVars.join(', ')}`); } } // Check config file status.configExists = await checkConfigFile(provider, workingDir); if (!status.configExists && provider.configFile) { status.errors.push(`Config file not found: ${provider.configFile}`); if (provider.configFile.includes('firebase.json')) { status.suggestions.push('Initialize project with: firebase init'); } else if (provider.configFile.includes('aliyun')) { status.suggestions.push('Configure with: aliyun configure'); } } return status; } async function autoFixIssues(providerId, workingDir) { const fixes = []; const provider = CLOUD_PROVIDERS[providerId]; if (!provider) { return ['Unknown provider']; } const status = await getFullCLIStatus(providerId, workingDir); // Try to install if not present if (!status.installed) { fixes.push(`Attempting to install ${provider.name} CLI...`); // Try npm install first for npm-based CLIs if (provider.installCommand.includes('npm install')) { const result = await runCommand(provider.installCommand, 120000); if (result.exitCode === 0) { fixes.push(`✅ Successfully installed ${provider.name} CLI`); } else { fixes.push(`❌ Failed to install: ${result.stderr}`); fixes.push(`Manual install: ${provider.installCommand}`); } } else { fixes.push(`Manual installation required: ${provider.installCommand}`); fixes.push(`Visit: ${provider.installUrl}`); } } // Check for CI environment variables if not authenticated if (!status.authenticated && provider.envVars) { const hasEnvAuth = provider.envVars.some((v) => process.env[v]); if (hasEnvAuth) { fixes.push(`Environment credentials detected for ${provider.name}`); } else { fixes.push(`Authentication required. Run: ${provider.loginCommand}`); // For headless/CI environments if (process.env['CI'] || !process.stdin.isTTY) { fixes.push(`For CI/headless environments, set: ${provider.envVars.join(' or ')}`); } } } return fixes; } export function createCloudTools(workingDir = process.cwd()) { return [ { name: 'cloud_status', description: `Check the status of cloud CLIs on this system. Detects and reports on: - CLI installation status and versions - Authentication status - Project configuration - Required environment variables Supports: Firebase, Aliyun, AWS, GCP, Azure, Vercel, Netlify, Cloudflare, Fly.io, Railway, Supabase Use this before attempting any cloud deployment to ensure everything is properly configured.`, parameters: { type: 'object', properties: { providers: { type: 'array', items: { type: 'string' }, description: 'Specific providers to check (default: all). Options: firebase, aliyun, aws, gcloud, azure, vercel, netlify, cloudflare, fly, railway, supabase', }, }, }, handler: async (args) => { const providersToCheck = args['providers'] || Object.keys(CLOUD_PROVIDERS); const results = ['☁️ Cloud CLI Status Report\n']; for (const providerId of providersToCheck) { const provider = CLOUD_PROVIDERS[providerId]; if (!provider) { results.push(`❓ Unknown provider: ${providerId}`); continue; } results.push(`\n--- ${provider.name} (${provider.cliCommand}) ---`); const status = await getFullCLIStatus(providerId, workingDir); if (status.installed) { results.push(`✅ Installed: v${status.version}`); } else { results.push(`❌ Not installed`); } if (status.installed) { if (status.authenticated) { results.push(`✅ Authenticated`); if (status.authDetails) { results.push(` ${status.authDetails.split('\n')[0]}`); } } else { results.push(`❌ Not authenticated`); } if (status.configExists) { results.push(`✅ Project config found`); } else if (provider.configFile) { results.push(`⚠️ No project config (${provider.configFile})`); } } if (status.errors.length > 0) { results.push(`\nIssues:`); for (const err of status.errors) { results.push(` ⚠️ ${err}`); } } if (status.suggestions.length > 0) { results.push(`\nSuggestions:`); for (const sug of status.suggestions) { results.push(` 💡 ${sug}`); } } } return results.join('\n'); }, }, { name: 'cloud_fix', description: `Automatically fix cloud CLI issues. Attempts to: - Install missing CLIs (via npm/brew where possible) - Detect environment credentials - Provide step-by-step fix instructions for issues that can't be auto-fixed Use this when cloud_status reports issues.`, parameters: { type: 'object', properties: { provider: { type: 'string', description: 'The cloud provider to fix. Options: firebase, aliyun, aws, gcloud, azure, vercel, netlify, cloudflare, fly, railway, supabase', }, }, required: ['provider'], }, handler: async (args) => { const providerId = args['provider']?.toLowerCase(); if (!CLOUD_PROVIDERS[providerId]) { return `Unknown provider: ${providerId}\n\nAvailable: ${Object.keys(CLOUD_PROVIDERS).join(', ')}`; } const fixes = await autoFixIssues(providerId, workingDir); return `🔧 Auto-Fix Results for ${CLOUD_PROVIDERS[providerId]?.name}\n\n${fixes.join('\n')}`; }, }, { name: 'firebase_deploy', description: `Deploy to Firebase (Hosting, Functions, Firestore rules, Storage rules). Automatically: - Checks Firebase CLI status - Validates firebase.json configuration - Handles authentication issues - Provides detailed deployment logs Supports: - firebase deploy --only hosting - firebase deploy --only functions - firebase deploy --only firestore - firebase deploy --only storage - firebase deploy (all)`, parameters: { type: 'object', properties: { target: { type: 'string', description: 'Deployment target: "hosting", "functions", "firestore", "storage", "all" (default: all)', }, project: { type: 'string', description: 'Firebase project ID (uses default if not specified)', }, dry_run: { type: 'boolean', description: 'Preview deployment without actually deploying (default: false)', }, }, }, handler: async (args) => { const target = args['target'] || 'all'; const project = args['project']; const dryRun = args['dry_run'] === true; const startTime = Date.now(); const checks = []; // First check status const status = await getFullCLIStatus('firebase', workingDir); checks.push({ check: 'Firebase CLI installed', passed: status.installed, details: status.installed ? `v${status.version}` : 'Not found', }); if (!status.installed) { return requiresUserAction('Firebase CLI not installed', 'The Firebase CLI (firebase-tools) must be installed before deployment.', [ 'Run: npm install -g firebase-tools', 'Or use cloud_fix tool with provider="firebase"', 'Documentation: https://firebase.google.com/docs/cli', ], Date.now() - startTime); } checks.push({ check: 'Firebase authenticated', passed: status.authenticated, details: status.authenticated ? 'Credentials valid' : 'Not logged in', }); if (!status.authenticated) { return requiresUserAction('Firebase authentication required', 'You must authenticate with Firebase before deploying.\nThe CLI is not currently logged in.', [ 'Interactive login: firebase login', 'CI/Headless: firebase login:ci and set FIREBASE_TOKEN', 'Service account: Set GOOGLE_APPLICATION_CREDENTIALS environment variable', ], Date.now() - startTime); } checks.push({ check: 'firebase.json exists', passed: status.configExists, details: status.configExists ? 'Found' : 'Not found', }); if (!status.configExists) { return requiresUserAction('Firebase configuration missing', `No firebase.json found in ${workingDir}.\nThis file is required to deploy to Firebase.`, [ 'Initialize Firebase: firebase init', 'Select the services you want to deploy (Hosting, Functions, etc.)', ], Date.now() - startTime); } // Build deploy command let cmd = 'firebase deploy'; if (target !== 'all') { cmd += ` --only ${target}`; } if (project) { cmd += ` --project ${project}`; } if (dryRun) { cmd += ' --dry-run'; } cmd += ' 2>&1'; const result = await runCommand(`cd "${workingDir}" && ${cmd}`, 300000); // 5 min timeout const durationMs = Date.now() - startTime; const output = result.stdout + result.stderr; // Analyze output with Firebase-specific patterns const analysis = analyzeOutput(output, OutputPatterns.firebase, result.exitCode); checks.push({ check: 'Deployment command', passed: result.exitCode === 0, details: `Exit code: ${result.exitCode}`, }); // Check for hosting URL in output (indicates actual successful deployment) const hostingUrlMatch = output.match(/Hosting URL:\s*(https?:\/\/[^\s]+)/i); const functionUrlMatch = output.match(/Function URL[^:]*:\s*(https?:\/\/[^\s]+)/i); if (hostingUrlMatch && hostingUrlMatch[1]) { // Verify the deployed URL is actually accessible const urlCheck = await verifyUrlAccessible(hostingUrlMatch[1], 200, 15000); checks.push(urlCheck); if (urlCheck.passed && result.exitCode === 0) { return verifiedSuccess('Firebase deployment completed and verified', `Target: ${target}\n${project ? `Project: ${project}\n` : ''}${dryRun ? '(Dry run)\n' : ''}\nHosting URL: ${hostingUrlMatch[1]}\n\nDeployment Output:\n${output}`, checks, durationMs); } else if (result.exitCode === 0) { // Deployment said success but URL not accessible return partialSuccess('Firebase deployment completed but URL verification failed', `The deployment command succeeded but the hosting URL is not yet accessible.\nThis may be due to propagation delay.\n\nHosting URL: ${hostingUrlMatch[1]}\n\nDeployment Output:\n${output}`, checks, ['Wait 1-2 minutes for deployment to propagate', `Then verify: ${hostingUrlMatch[1]}`], durationMs); } } if (functionUrlMatch && result.exitCode === 0) { checks.push({ check: 'Function deployment', passed: true, details: functionUrlMatch[1], }); return verifiedSuccess('Firebase Functions deployment completed', `Target: ${target}\n${project ? `Project: ${project}\n` : ''}\nFunction URL: ${functionUrlMatch[1]}\n\nDeployment Output:\n${output}`, checks, durationMs); } // Exit code 0 but no URL found - might be config-only deployment if (result.exitCode === 0 && analysis.isSuccess) { checks.push({ check: 'Output analysis', passed: true, details: 'Success pattern detected', }); return verifiedSuccess('Firebase deployment completed', `Target: ${target}\n${project ? `Project: ${project}\n` : ''}${dryRun ? '(Dry run)\n' : ''}\nDeployment Output:\n${output}`, checks, durationMs); } // Exit code 0 but output suggests problems if (result.exitCode === 0 && analysis.isFailure) { const firebaseProvider = CLOUD_PROVIDERS['firebase']; const errorSuggestions = firebaseProvider ? analyzeDeploymentError(output, firebaseProvider).split('\n').slice(1).map(s => s.replace(/^[•]\s*/, '')) : []; return verifiedFailure('Firebase deployment command succeeded but output indicates errors', `The command exited with code 0 but the output contains error indicators.\n\nOutput:\n${output}`, errorSuggestions, checks, durationMs); } // Exit code 0 but can't verify if (result.exitCode === 0) { return unverifiedResult('Firebase deployment may have completed', `The command exited with code 0 but we cannot verify the deployment succeeded.\nNo hosting URL or success patterns found in output.\n\nOutput:\n${output}`, ['Manually check Firebase Console: https://console.firebase.google.com', 'Verify your deployment target is configured in firebase.json'], durationMs); } // Deployment failed - analyze the error const firebaseProviderForError = CLOUD_PROVIDERS['firebase']; const errorAnalysis = analyzeDeploymentError(output, firebaseProviderForError); const suggestedFixes = []; if (output.includes('authentication') || output.includes('login') || output.includes('not logged in')) { suggestedFixes.push('Run: firebase login --reauth'); } if (output.includes('permission') || output.includes('denied') || output.includes('PERMISSION_DENIED')) { suggestedFixes.push('Check Firebase project permissions at https://console.firebase.google.com'); } if (output.includes('quota') || output.includes('limit')) { suggestedFixes.push('Check billing/quota at https://console.firebase.google.com/billing'); } if (output.includes('build') || output.includes('compile')) { suggestedFixes.push('Fix build errors first: npm run build'); } return verifiedFailure(`Firebase deployment failed with exit code ${result.exitCode}`, `Target: ${target}\n${project ? `Project: ${project}\n` : ''}\n${errorAnalysis}\n\nFull Output:\n${output}`, suggestedFixes.length > 0 ? suggestedFixes : ['Review the error output above', 'Check Firebase documentation for the specific error'], checks, durationMs); }, }, { name: 'aliyun_deploy', description: `Deploy to Alibaba Cloud (Aliyun). Supports: - OSS (Object Storage Service) - static file hosting - FC (Function Compute) - serverless functions - ACR (Container Registry) - Docker images - ECS (Elastic Compute Service) - VM management Automatically handles authentication and common issues.`, parameters: { type: 'object', properties: { service: { type: 'string', description: 'Service to deploy: "oss", "fc", "acr", "ecs"', }, action: { type: 'string', description: 'Action to perform (service-specific)', }, region: { type: 'string', description: 'Aliyun region (e.g., cn-hangzhou, cn-shanghai)', }, args: { type: 'string', description: 'Additional CLI arguments', }, }, required: ['service', 'action'], }, handler: async (args) => { const service = args['service']; const action = args['action']; const region = args['region']; const extraArgs = args['args']; const startTime = Date.now(); const checks = []; // Check status const status = await getFullCLIStatus('aliyun', workingDir); checks.push({ check: 'Aliyun CLI installed', passed: status.installed, details: status.installed ? `v${status.version}` : 'Not found', }); if (!status.installed) { return requiresUserAction('Aliyun CLI not installed', 'The Aliyun CLI must be installed before running commands.', [ 'macOS: brew install aliyun-cli', 'Linux: curl -O https://aliyuncli.alicdn.com/aliyun-cli-linux-latest-amd64.tgz', 'Docs: https://www.alibabacloud.com/help/doc-detail/139508.htm', ], Date.now() - startTime); } checks.push({ check: 'Aliyun authenticated', passed: status.authenticated, details: status.authenticated ? 'Credentials valid' : 'Not configured', }); if (!status.authenticated) { return requiresUserAction('Aliyun not configured', 'You must configure Aliyun credentials before running commands.', [ 'Run: aliyun configure', 'Enter your AccessKey ID and Secret', 'Get keys at: https://ram.console.aliyun.com/manage/ak', 'Or set: ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET', ], Date.now() - startTime); } // Build command let cmd = `aliyun ${service} ${action}`; if (region) { cmd += ` --region ${region}`; } if (extraArgs) { cmd += ` ${extraArgs}`; } cmd += ' 2>&1'; const result = await runCommand(cmd, 120000); const durationMs = Date.now() - startTime; const output = result.stdout + result.stderr; checks.push({ check: 'Command execution', passed: result.exitCode === 0, details: `Exit code: ${result.exitCode}`, }); // Analyze output for success/failure patterns const analysis = analyzeOutput(output, OutputPatterns.command, result.exitCode); if (result.exitCode === 0 && analysis.confidence !== 'low') { // Try to parse JSON response for additional verification try { const jsonResponse = JSON.parse(output.trim()); if (jsonResponse.RequestId) { checks.push({ check: 'API response received', passed: true, details: `RequestId: ${jsonResponse.RequestId}`, }); } } catch { // Not JSON, but command succeeded } return verifiedSuccess(`Aliyun ${service} ${action} completed`, `Service: ${service}\nAction: ${action}${region ? `\nRegion: ${region}` : ''}\n\nOutput:\n${output}`, checks, durationMs); } if (result.exitCode === 0 && analysis.confidence === 'low') { return unverifiedResult(`Aliyun ${service} ${action} may have completed`, `The command exited with code 0 but we cannot confirm success.\n\nOutput:\n${output}`, ['Manually verify the operation in Aliyun Console'], durationMs); } // Error analysis const suggestedFixes = []; if (output.includes('InvalidAccessKeyId') || output.includes('SignatureDoesNotMatch')) { suggestedFixes.push('Invalid credentials. Run `aliyun configure` with correct AccessKey'); } if (output.includes('Forbidden')) { suggestedFixes.push('Permission denied. Check RAM policies at https://ram.console.aliyun.com'); } if (output.includes('InvalidRegionId')) { suggestedFixes.push('Invalid region. Check available regions with: aliyun ecs DescribeRegions'); } return verifiedFailure(`Aliyun ${service} ${action} failed with exit code ${result.exitCode}`, `Service: ${service}\nAction: ${action}${region ? `\nRegion: ${region}` : ''}\n\nOutput:\n${output}`, suggestedFixes.length > 0 ? suggestedFixes : ['Review the error message above'], checks, durationMs); }, }, { name: 'cloud_deploy', description: `Universal cloud deployment command that works across providers. Automatically: - Detects project type and configuration - Selects appropriate deployment target - Handles provider-specific requirements - Auto-fixes common issues Supports deployment to: - Firebase, Vercel, Netlify for web apps - AWS Lambda, GCP Functions, Azure Functions for serverless - Cloudflare Workers/Pages for edge deployments - Fly.io, Railway for containers`, parameters: { type: 'object', properties: { provider: { type: 'string', description: 'Cloud provider: firebase, vercel, netlify, cloudflare, fly, railway, aws, gcloud, azure', }, command: { type: 'string', description: 'Deployment command (e.g., "deploy", "deploy --prod")', }, auto_fix: { type: 'boolean', description: 'Automatically attempt to fix issues (default: true)', }, }, required: ['provider'], }, handler: async (args) => { const providerId = args['provider']?.toLowerCase(); const command = args['command'] || 'deploy'; const autoFix = args['auto_fix'] !== false; const startTime = Date.now(); const checks = []; const provider = CLOUD_PROVIDERS[providerId]; if (!provider) { return verifiedFailure(`Unknown cloud provider: ${providerId}`, `The provider "${providerId}" is not recognized.`, [`Available providers: ${Object.keys(CLOUD_PROVIDERS).join(', ')}`], [], Date.now() - startTime); } // Check and potentially fix status let status = await getFullCLIStatus(providerId, workingDir); checks.push({ check: `${provider.name} CLI installed`, passed: status.installed, details: status.installed ? `v${status.version}` : 'Not found', }); if (!status.installed && autoFix) { const fixes = await autoFixIssues(providerId, workingDir); // Re-check after fix attempt status = await getFullCLIStatus(providerId, workingDir); if (!status.installed) { return requiresUserAction(`${provider.name} CLI could not be auto-installed`, `Automatic installation failed. Manual installation required.\n\nAuto-fix attempts:\n${fixes.join('\n')}`, [ `Install manually: ${provider.installCommand}`, `Documentation: ${provider.installUrl}`, ], Date.now() - startTime); } } if (!status.installed) { return requiresUserAction(`${provider.name} CLI not installed`, `The ${provider.name} CLI must be installed before deployment.`, [ `Install: ${provider.installCommand}`, `Docs: ${provider.installUrl}`, ], Date.now() - startTime); } checks.push({ check: `${provider.name} authenticated`, passed: status.authenticated, details: status.authenticated ? 'Credentials valid' : 'Not logged in', }); if (!status.authenticated) { return requiresUserAction(`${provider.name} authentication required`, `You must authenticate with ${provider.name} before deploying.`, [ `Login: ${provider.loginCommand}`, `For CI, set: ${provider.envVars?.join(' or ') || 'appropriate credentials'}`, ], Date.now() - startTime); } // Execute deployment const cmd = `${provider.cliCommand} ${command} 2>&1`; const result = await runCommand(`cd "${workingDir}" && ${cmd}`, 300000); const durationMs = Date.now() - startTime; const output = result.stdout + result.stderr; checks.push({ check: 'Deployment command', passed: result.exitCode === 0, details: `Exit code: ${result.exitCode}`, }); // Analyze output const analysis = analyzeOutput(output, OutputPatterns.command, result.exitCode); if (result.exitCode === 0 && analysis.isSuccess) { return verifiedSuccess(`${provider.name} deployment completed`, `Command: ${provider.cliCommand} ${command}\n\nOutput:\n${output}`, checks, durationMs); } if (result.exitCode === 0 && analysis.isFailure) { return verifiedFailure(`${provider.name} deployment command succeeded but output indicates errors`, `The command exited with code 0 but the output contains error indicators.\n\nOutput:\n${output}`, [], checks, durationMs); } if (result.exitCode === 0) { // Can't determine success - mark as unverified return unverifiedResult(`${provider.name} deployment may have completed`, `The command exited with code 0 but we cannot confirm success.\n\nOutput:\n${output}`, [`Manually verify deployment at ${provider.name} console`], durationMs); } // Deployment failed const errorAnalysis = analyzeDeploymentError(output, provider); const suggestedActions = errorAnalysis .split('\n') .filter(line => line.includes('•') || line.includes('Fix:')) .map(line => line.replace(/^[•\s]+/, '').replace(/^\s*Fix:\s*/i, '')); return verifiedFailure(`${provider.name} deployment failed with exit code ${result.exitCode}`, `Command: ${provider.cliCommand} ${command}\n\n${errorAnalysis}\n\nFull Output:\n${output}`, suggestedActions.length > 0 ? suggestedActions : ['Review the error output', 'Check provider documentation'], checks, durationMs); }, }, { name: 'cloud_init', description: `Initialize a cloud project configuration. Creates configuration files and sets up project structure for: - Firebase (firebase.json, .firebaserc) - Vercel (vercel.json) - Netlify (netlify.toml) - Cloudflare (wrangler.toml) Auto-detects project type (React, Vue, Angular, Next.js, etc.) and configures appropriately.`, parameters: { type: 'object', properties: { provider: { type: 'string', description: 'Cloud provider to initialize: firebase, vercel, netlify, cloudflare', }, project_type: { type: 'string', description: 'Project type: react, vue, angular, nextjs, static, node (auto-detected if not specified)', }, }, required: ['provider'], }, handler: async (args) => { const providerId = args['provider']?.toLowerCase(); const projectType = args['project_type']; // Detect project type if not specified const detectedType = projectType || detectProjectType(workingDir); const configs = { firebase: () => generateFirebaseConfig(detectedType, workingDir), vercel: () => generateVercelConfig(detectedType), netlify: () => generateNetlifyConfig(detectedType), cloudflare: () => generateCloudflareConfig(detectedType), }; if (!configs[providerId]) { return `Initialization not supported for: ${providerId}\n\nSupported: firebase, vercel, netlify, cloudflare`; } const config = configs[providerId](); return `📝 ${CLOUD_PROVIDERS[providerId]?.name || providerId} Configuration\n\nProject type: ${detectedType}\n\n${config}`; }, }, { name: 'cloud_login', description: `Interactive login for cloud providers. Supports browser-based OAuth login for: - Firebase (firebase login) - Google Cloud (gcloud auth login) - AWS (aws configure) - Azure (az login) - Vercel (vercel login) - Netlify (netlify login) - Cloudflare (wrangler login) - Aliyun (aliyun configure) - Fly.io (flyctl auth login) - Railway (railway login) - Supabase (supabase login) Options: - reauth: Force re-authentication even if already logged in - no_localhost: Use device code flow (for remote/headless machines with browser access elsewhere) - ci_mode: Get instructions for CI/CD environment setup Use this when cloud_status shows authentication issues.`, parameters: { type: 'object', properties: { provider: { type: 'string', description: 'Cloud provider to login: firebase, gcloud, aws, azure, vercel, netlify, cloudflare, aliyun, fly, railway, supabase', }, reauth: { type: 'boolean', description: 'Force re-authentication (default: false)', }, no_localhost: { type: 'boolean', description: 'Use device code flow instead of localhost redirect (default: false)', }, ci_mode: { type: 'boolean', description: 'Show CI/CD setup instructions instead of interactive login (default: false)', }, }, required: ['provider'], }, handler: async (args) => { const providerId = args['provider']?.toLowerCase(); const reauth = args['reauth'] === true; const noLocalhost = args['no_localhost'] === true; const ciMode = args['ci_mode'] === true; const provider = CLOUD_PROVIDERS[providerId]; if (!provider) { return `Unknown provider: ${providerId}\n\nAvailable: ${Object.keys(CLOUD_PROVIDERS).join(', ')}`; } // Check if CLI is installed const installCheck = await checkCLIInstalled(provider); if (!installCheck.installed) { return `${provider.name} CLI not installed.\n\nInstall with: ${provider.installCommand}\nDocs: ${provider.installUrl}`; } // Build login command based on provider and options const loginCommands = { firebase: { standard: 'firebase login', reauth: 'firebase login --reauth', no_localhost: 'firebase login --no-localhost', ci: 'firebase login:ci', }, gcloud: { standard: 'gcloud auth login', reauth: 'gcloud auth login --force', no_localhost: 'gcloud auth login --no-browser', ci: 'gcloud auth activate-service-account --key-file=', }, aws: { standard: 'aws configure', reauth: 'aws configure', no_localhost: 'aws configure', ci: 'aws configure set', }, azure: { standard: 'az login', reauth: 'az login', no_localhost: 'az login --use-device-code', ci: 'az login --service-principal', }, vercel: { standard: 'vercel login', reauth: 'vercel login', no_localhost: 'vercel login', ci: 'vercel login --token', }, netlify: { standard: 'netlify login', reauth: 'netlify login', no_localhost: 'netlify login', ci: 'netlify login --new', }, cloudflare: { standard: 'wrangler login', reauth: 'wrangler login', no_localhost: 'wrangler login', ci: 'wrangler login', }, aliyun: { standard: 'aliyun configure', reauth: 'aliyun configure', no_localhost: 'aliyun configure', ci: 'aliyun configure set', }, fly: { standard: 'flyctl auth login', reauth: 'flyctl auth login', no_localhost: 'flyctl auth login', ci: 'flyctl auth token', }, railway: { standard: 'railway login', reauth: 'railway login', no_localhost: 'railway login', ci: 'railway login --browserless', }, supabase: { standard: 'supabase login', reauth: 'supabase login', no_localhost: 'supabase login', ci: 'supabase login', }, }; const providerCommands = loginCommands[providerId] || { standard: provider.loginCommand, reauth: provider.loginCommand, no_localhost: provider.loginCommand, ci: provider.loginCommand, }; // Select the appropriate command if (ciMode) { const cmd = providerCommands['ci'] || provider.loginCommand; return `CI/Headless Authentication for ${provider.name} For CI/CD environments, you need to set environment variables instead of interactive login. Command (for generating token): ${cmd} Environment variables to set: ${provider.envVars?.map(v => ` - ${v}`).join('\n') || ' (none defined)'} For Firebase specifically: 1. Run locally: firebase login:ci 2. Copy the token 3. Set FIREBASE_TOKEN in your CI environment For service accounts (Firebase/GCP): 1. Create a service account in Google Cloud Console 2. Download the JSON key file 3. Set GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json`; } let cmd; if (reauth) { cmd = providerCommands['reauth'] || provider.loginCommand; } else if (noLocalhost) { cmd = providerCommands['no_localhost'] || provider.loginCommand; } else { cmd = providerCommands['standard'] || provider.loginCommand; } // Use 'script' command to provide pseudo-TTY for the login command // This enables browser-based OAuth even in non-interactive shells const platform = process.platform; let wrappedCmd; if (platform === 'darwin') { // macOS: script -q /dev/null <command> wrappedCmd = `script -q /dev/null ${c