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
JavaScript
/**
* 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