erosolar-cli 1.6.3 → 1.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/resultVerification.d.ts +138 -0
- package/dist/core/resultVerification.d.ts.map +1 -0
- package/dist/core/resultVerification.js +465 -0
- package/dist/core/resultVerification.js.map +1 -0
- package/dist/plugins/providers/anthropic/index.d.ts +9 -0
- package/dist/plugins/providers/anthropic/index.d.ts.map +1 -1
- package/dist/plugins/providers/anthropic/index.js +13 -1
- package/dist/plugins/providers/anthropic/index.js.map +1 -1
- package/dist/plugins/providers/deepseek/index.d.ts +9 -0
- package/dist/plugins/providers/deepseek/index.d.ts.map +1 -1
- package/dist/plugins/providers/deepseek/index.js +28 -1
- package/dist/plugins/providers/deepseek/index.js.map +1 -1
- package/dist/plugins/providers/google/index.d.ts +9 -0
- package/dist/plugins/providers/google/index.d.ts.map +1 -1
- package/dist/plugins/providers/google/index.js +13 -1
- package/dist/plugins/providers/google/index.js.map +1 -1
- package/dist/plugins/providers/ollama/index.d.ts +5 -1
- package/dist/plugins/providers/ollama/index.d.ts.map +1 -1
- package/dist/plugins/providers/ollama/index.js +11 -2
- package/dist/plugins/providers/ollama/index.js.map +1 -1
- package/dist/plugins/providers/openai/index.d.ts +9 -0
- package/dist/plugins/providers/openai/index.d.ts.map +1 -1
- package/dist/plugins/providers/openai/index.js +13 -1
- package/dist/plugins/providers/openai/index.js.map +1 -1
- package/dist/plugins/providers/xai/index.d.ts +9 -0
- package/dist/plugins/providers/xai/index.d.ts.map +1 -1
- package/dist/plugins/providers/xai/index.js +15 -1
- package/dist/plugins/providers/xai/index.js.map +1 -1
- package/dist/providers/anthropicProvider.d.ts +7 -0
- package/dist/providers/anthropicProvider.d.ts.map +1 -1
- package/dist/providers/anthropicProvider.js +87 -9
- package/dist/providers/anthropicProvider.js.map +1 -1
- package/dist/providers/googleProvider.d.ts +17 -0
- package/dist/providers/googleProvider.d.ts.map +1 -1
- package/dist/providers/googleProvider.js +113 -32
- package/dist/providers/googleProvider.js.map +1 -1
- package/dist/providers/openaiChatCompletionsProvider.d.ts +26 -0
- package/dist/providers/openaiChatCompletionsProvider.d.ts.map +1 -1
- package/dist/providers/openaiChatCompletionsProvider.js +162 -27
- package/dist/providers/openaiChatCompletionsProvider.js.map +1 -1
- package/dist/providers/openaiResponsesProvider.d.ts +17 -0
- package/dist/providers/openaiResponsesProvider.d.ts.map +1 -1
- package/dist/providers/openaiResponsesProvider.js +109 -25
- package/dist/providers/openaiResponsesProvider.js.map +1 -1
- package/dist/providers/resilientProvider.d.ts.map +1 -1
- package/dist/providers/resilientProvider.js +47 -1
- package/dist/providers/resilientProvider.js.map +1 -1
- package/dist/shell/interactiveShell.d.ts +6 -0
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +48 -0
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/shell/taskCompletionDetector.d.ts +4 -0
- package/dist/shell/taskCompletionDetector.d.ts.map +1 -1
- package/dist/shell/taskCompletionDetector.js +27 -1
- package/dist/shell/taskCompletionDetector.js.map +1 -1
- package/dist/tools/bashTools.d.ts.map +1 -1
- package/dist/tools/bashTools.js +26 -8
- package/dist/tools/bashTools.js.map +1 -1
- package/dist/tools/browserAutomationTools.d.ts.map +1 -1
- package/dist/tools/browserAutomationTools.js +9 -1
- package/dist/tools/browserAutomationTools.js.map +1 -1
- package/dist/tools/cloudTools.d.ts.map +1 -1
- package/dist/tools/cloudTools.js +436 -35
- package/dist/tools/cloudTools.js.map +1 -1
- package/dist/tools/codeGenerationTools.d.ts.map +1 -1
- package/dist/tools/codeGenerationTools.js +77 -7
- package/dist/tools/codeGenerationTools.js.map +1 -1
- package/dist/ui/persistentPrompt.d.ts +10 -0
- package/dist/ui/persistentPrompt.d.ts.map +1 -1
- package/dist/ui/persistentPrompt.js +82 -18
- package/dist/ui/persistentPrompt.js.map +1 -1
- package/package.json +1 -1
package/dist/tools/cloudTools.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import { spawn } from 'child_process';
|
|
24
24
|
import * as fs from 'fs';
|
|
25
25
|
import * as path from 'path';
|
|
26
|
+
import { verifiedSuccess, verifiedFailure, unverifiedResult, requiresUserAction, partialSuccess, analyzeOutput, OutputPatterns, verifyUrlAccessible, } from '../core/resultVerification.js';
|
|
26
27
|
const CLOUD_PROVIDERS = {
|
|
27
28
|
firebase: {
|
|
28
29
|
id: 'firebase',
|
|
@@ -471,16 +472,44 @@ Supports:
|
|
|
471
472
|
const target = args['target'] || 'all';
|
|
472
473
|
const project = args['project'];
|
|
473
474
|
const dryRun = args['dry_run'] === true;
|
|
475
|
+
const startTime = Date.now();
|
|
476
|
+
const checks = [];
|
|
474
477
|
// First check status
|
|
475
478
|
const status = await getFullCLIStatus('firebase', workingDir);
|
|
479
|
+
checks.push({
|
|
480
|
+
check: 'Firebase CLI installed',
|
|
481
|
+
passed: status.installed,
|
|
482
|
+
details: status.installed ? `v${status.version}` : 'Not found',
|
|
483
|
+
});
|
|
476
484
|
if (!status.installed) {
|
|
477
|
-
return
|
|
485
|
+
return requiresUserAction('Firebase CLI not installed', 'The Firebase CLI (firebase-tools) must be installed before deployment.', [
|
|
486
|
+
'Run: npm install -g firebase-tools',
|
|
487
|
+
'Or use cloud_fix tool with provider="firebase"',
|
|
488
|
+
'Documentation: https://firebase.google.com/docs/cli',
|
|
489
|
+
], Date.now() - startTime);
|
|
478
490
|
}
|
|
491
|
+
checks.push({
|
|
492
|
+
check: 'Firebase authenticated',
|
|
493
|
+
passed: status.authenticated,
|
|
494
|
+
details: status.authenticated ? 'Credentials valid' : 'Not logged in',
|
|
495
|
+
});
|
|
479
496
|
if (!status.authenticated) {
|
|
480
|
-
return
|
|
497
|
+
return requiresUserAction('Firebase authentication required', 'You must authenticate with Firebase before deploying.\nThe CLI is not currently logged in.', [
|
|
498
|
+
'Interactive login: firebase login',
|
|
499
|
+
'CI/Headless: firebase login:ci and set FIREBASE_TOKEN',
|
|
500
|
+
'Service account: Set GOOGLE_APPLICATION_CREDENTIALS environment variable',
|
|
501
|
+
], Date.now() - startTime);
|
|
481
502
|
}
|
|
503
|
+
checks.push({
|
|
504
|
+
check: 'firebase.json exists',
|
|
505
|
+
passed: status.configExists,
|
|
506
|
+
details: status.configExists ? 'Found' : 'Not found',
|
|
507
|
+
});
|
|
482
508
|
if (!status.configExists) {
|
|
483
|
-
return `No firebase.json found in ${workingDir}.\
|
|
509
|
+
return requiresUserAction('Firebase configuration missing', `No firebase.json found in ${workingDir}.\nThis file is required to deploy to Firebase.`, [
|
|
510
|
+
'Initialize Firebase: firebase init',
|
|
511
|
+
'Select the services you want to deploy (Hosting, Functions, etc.)',
|
|
512
|
+
], Date.now() - startTime);
|
|
484
513
|
}
|
|
485
514
|
// Build deploy command
|
|
486
515
|
let cmd = 'firebase deploy';
|
|
@@ -495,25 +524,76 @@ Supports:
|
|
|
495
524
|
}
|
|
496
525
|
cmd += ' 2>&1';
|
|
497
526
|
const result = await runCommand(`cd "${workingDir}" && ${cmd}`, 300000); // 5 min timeout
|
|
527
|
+
const durationMs = Date.now() - startTime;
|
|
528
|
+
const output = result.stdout + result.stderr;
|
|
529
|
+
// Analyze output with Firebase-specific patterns
|
|
530
|
+
const analysis = analyzeOutput(output, OutputPatterns.firebase, result.exitCode);
|
|
531
|
+
checks.push({
|
|
532
|
+
check: 'Deployment command',
|
|
533
|
+
passed: result.exitCode === 0,
|
|
534
|
+
details: `Exit code: ${result.exitCode}`,
|
|
535
|
+
});
|
|
536
|
+
// Check for hosting URL in output (indicates actual successful deployment)
|
|
537
|
+
const hostingUrlMatch = output.match(/Hosting URL:\s*(https?:\/\/[^\s]+)/i);
|
|
538
|
+
const functionUrlMatch = output.match(/Function URL[^:]*:\s*(https?:\/\/[^\s]+)/i);
|
|
539
|
+
if (hostingUrlMatch && hostingUrlMatch[1]) {
|
|
540
|
+
// Verify the deployed URL is actually accessible
|
|
541
|
+
const urlCheck = await verifyUrlAccessible(hostingUrlMatch[1], 200, 15000);
|
|
542
|
+
checks.push(urlCheck);
|
|
543
|
+
if (urlCheck.passed && result.exitCode === 0) {
|
|
544
|
+
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);
|
|
545
|
+
}
|
|
546
|
+
else if (result.exitCode === 0) {
|
|
547
|
+
// Deployment said success but URL not accessible
|
|
548
|
+
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);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (functionUrlMatch && result.exitCode === 0) {
|
|
552
|
+
checks.push({
|
|
553
|
+
check: 'Function deployment',
|
|
554
|
+
passed: true,
|
|
555
|
+
details: functionUrlMatch[1],
|
|
556
|
+
});
|
|
557
|
+
return verifiedSuccess('Firebase Functions deployment completed', `Target: ${target}\n${project ? `Project: ${project}\n` : ''}\nFunction URL: ${functionUrlMatch[1]}\n\nDeployment Output:\n${output}`, checks, durationMs);
|
|
558
|
+
}
|
|
559
|
+
// Exit code 0 but no URL found - might be config-only deployment
|
|
560
|
+
if (result.exitCode === 0 && analysis.isSuccess) {
|
|
561
|
+
checks.push({
|
|
562
|
+
check: 'Output analysis',
|
|
563
|
+
passed: true,
|
|
564
|
+
details: 'Success pattern detected',
|
|
565
|
+
});
|
|
566
|
+
return verifiedSuccess('Firebase deployment completed', `Target: ${target}\n${project ? `Project: ${project}\n` : ''}${dryRun ? '(Dry run)\n' : ''}\nDeployment Output:\n${output}`, checks, durationMs);
|
|
567
|
+
}
|
|
568
|
+
// Exit code 0 but output suggests problems
|
|
569
|
+
if (result.exitCode === 0 && analysis.isFailure) {
|
|
570
|
+
const firebaseProvider = CLOUD_PROVIDERS['firebase'];
|
|
571
|
+
const errorSuggestions = firebaseProvider
|
|
572
|
+
? analyzeDeploymentError(output, firebaseProvider).split('\n').slice(1).map(s => s.replace(/^[•]\s*/, ''))
|
|
573
|
+
: [];
|
|
574
|
+
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);
|
|
575
|
+
}
|
|
576
|
+
// Exit code 0 but can't verify
|
|
498
577
|
if (result.exitCode === 0) {
|
|
499
|
-
return
|
|
578
|
+
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);
|
|
500
579
|
}
|
|
501
|
-
//
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
580
|
+
// Deployment failed - analyze the error
|
|
581
|
+
const firebaseProviderForError = CLOUD_PROVIDERS['firebase'];
|
|
582
|
+
const errorAnalysis = analyzeDeploymentError(output, firebaseProviderForError);
|
|
583
|
+
const suggestedFixes = [];
|
|
584
|
+
if (output.includes('authentication') || output.includes('login') || output.includes('not logged in')) {
|
|
585
|
+
suggestedFixes.push('Run: firebase login --reauth');
|
|
506
586
|
}
|
|
507
|
-
|
|
508
|
-
|
|
587
|
+
if (output.includes('permission') || output.includes('denied') || output.includes('PERMISSION_DENIED')) {
|
|
588
|
+
suggestedFixes.push('Check Firebase project permissions at https://console.firebase.google.com');
|
|
509
589
|
}
|
|
510
|
-
|
|
511
|
-
|
|
590
|
+
if (output.includes('quota') || output.includes('limit')) {
|
|
591
|
+
suggestedFixes.push('Check billing/quota at https://console.firebase.google.com/billing');
|
|
512
592
|
}
|
|
513
|
-
|
|
514
|
-
|
|
593
|
+
if (output.includes('build') || output.includes('compile')) {
|
|
594
|
+
suggestedFixes.push('Fix build errors first: npm run build');
|
|
515
595
|
}
|
|
516
|
-
return
|
|
596
|
+
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);
|
|
517
597
|
},
|
|
518
598
|
},
|
|
519
599
|
{
|
|
@@ -554,13 +634,34 @@ Automatically handles authentication and common issues.`,
|
|
|
554
634
|
const action = args['action'];
|
|
555
635
|
const region = args['region'];
|
|
556
636
|
const extraArgs = args['args'];
|
|
637
|
+
const startTime = Date.now();
|
|
638
|
+
const checks = [];
|
|
557
639
|
// Check status
|
|
558
640
|
const status = await getFullCLIStatus('aliyun', workingDir);
|
|
641
|
+
checks.push({
|
|
642
|
+
check: 'Aliyun CLI installed',
|
|
643
|
+
passed: status.installed,
|
|
644
|
+
details: status.installed ? `v${status.version}` : 'Not found',
|
|
645
|
+
});
|
|
559
646
|
if (!status.installed) {
|
|
560
|
-
return
|
|
647
|
+
return requiresUserAction('Aliyun CLI not installed', 'The Aliyun CLI must be installed before running commands.', [
|
|
648
|
+
'macOS: brew install aliyun-cli',
|
|
649
|
+
'Linux: curl -O https://aliyuncli.alicdn.com/aliyun-cli-linux-latest-amd64.tgz',
|
|
650
|
+
'Docs: https://www.alibabacloud.com/help/doc-detail/139508.htm',
|
|
651
|
+
], Date.now() - startTime);
|
|
561
652
|
}
|
|
653
|
+
checks.push({
|
|
654
|
+
check: 'Aliyun authenticated',
|
|
655
|
+
passed: status.authenticated,
|
|
656
|
+
details: status.authenticated ? 'Credentials valid' : 'Not configured',
|
|
657
|
+
});
|
|
562
658
|
if (!status.authenticated) {
|
|
563
|
-
return
|
|
659
|
+
return requiresUserAction('Aliyun not configured', 'You must configure Aliyun credentials before running commands.', [
|
|
660
|
+
'Run: aliyun configure',
|
|
661
|
+
'Enter your AccessKey ID and Secret',
|
|
662
|
+
'Get keys at: https://ram.console.aliyun.com/manage/ak',
|
|
663
|
+
'Or set: ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET',
|
|
664
|
+
], Date.now() - startTime);
|
|
564
665
|
}
|
|
565
666
|
// Build command
|
|
566
667
|
let cmd = `aliyun ${service} ${action}`;
|
|
@@ -572,19 +673,47 @@ Automatically handles authentication and common issues.`,
|
|
|
572
673
|
}
|
|
573
674
|
cmd += ' 2>&1';
|
|
574
675
|
const result = await runCommand(cmd, 120000);
|
|
575
|
-
|
|
576
|
-
|
|
676
|
+
const durationMs = Date.now() - startTime;
|
|
677
|
+
const output = result.stdout + result.stderr;
|
|
678
|
+
checks.push({
|
|
679
|
+
check: 'Command execution',
|
|
680
|
+
passed: result.exitCode === 0,
|
|
681
|
+
details: `Exit code: ${result.exitCode}`,
|
|
682
|
+
});
|
|
683
|
+
// Analyze output for success/failure patterns
|
|
684
|
+
const analysis = analyzeOutput(output, OutputPatterns.command, result.exitCode);
|
|
685
|
+
if (result.exitCode === 0 && analysis.confidence !== 'low') {
|
|
686
|
+
// Try to parse JSON response for additional verification
|
|
687
|
+
try {
|
|
688
|
+
const jsonResponse = JSON.parse(output.trim());
|
|
689
|
+
if (jsonResponse.RequestId) {
|
|
690
|
+
checks.push({
|
|
691
|
+
check: 'API response received',
|
|
692
|
+
passed: true,
|
|
693
|
+
details: `RequestId: ${jsonResponse.RequestId}`,
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
catch {
|
|
698
|
+
// Not JSON, but command succeeded
|
|
699
|
+
}
|
|
700
|
+
return verifiedSuccess(`Aliyun ${service} ${action} completed`, `Service: ${service}\nAction: ${action}${region ? `\nRegion: ${region}` : ''}\n\nOutput:\n${output}`, checks, durationMs);
|
|
701
|
+
}
|
|
702
|
+
if (result.exitCode === 0 && analysis.confidence === 'low') {
|
|
703
|
+
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);
|
|
577
704
|
}
|
|
578
705
|
// Error analysis
|
|
579
|
-
const
|
|
580
|
-
let fix = '';
|
|
706
|
+
const suggestedFixes = [];
|
|
581
707
|
if (output.includes('InvalidAccessKeyId') || output.includes('SignatureDoesNotMatch')) {
|
|
582
|
-
|
|
708
|
+
suggestedFixes.push('Invalid credentials. Run `aliyun configure` with correct AccessKey');
|
|
583
709
|
}
|
|
584
|
-
|
|
585
|
-
|
|
710
|
+
if (output.includes('Forbidden')) {
|
|
711
|
+
suggestedFixes.push('Permission denied. Check RAM policies at https://ram.console.aliyun.com');
|
|
586
712
|
}
|
|
587
|
-
|
|
713
|
+
if (output.includes('InvalidRegionId')) {
|
|
714
|
+
suggestedFixes.push('Invalid region. Check available regions with: aliyun ecs DescribeRegions');
|
|
715
|
+
}
|
|
716
|
+
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);
|
|
588
717
|
},
|
|
589
718
|
},
|
|
590
719
|
{
|
|
@@ -624,37 +753,76 @@ Supports deployment to:
|
|
|
624
753
|
const providerId = args['provider']?.toLowerCase();
|
|
625
754
|
const command = args['command'] || 'deploy';
|
|
626
755
|
const autoFix = args['auto_fix'] !== false;
|
|
756
|
+
const startTime = Date.now();
|
|
757
|
+
const checks = [];
|
|
627
758
|
const provider = CLOUD_PROVIDERS[providerId];
|
|
628
759
|
if (!provider) {
|
|
629
|
-
return `Unknown provider: ${providerId}
|
|
760
|
+
return verifiedFailure(`Unknown cloud provider: ${providerId}`, `The provider "${providerId}" is not recognized.`, [`Available providers: ${Object.keys(CLOUD_PROVIDERS).join(', ')}`], [], Date.now() - startTime);
|
|
630
761
|
}
|
|
631
762
|
// Check and potentially fix status
|
|
632
763
|
let status = await getFullCLIStatus(providerId, workingDir);
|
|
764
|
+
checks.push({
|
|
765
|
+
check: `${provider.name} CLI installed`,
|
|
766
|
+
passed: status.installed,
|
|
767
|
+
details: status.installed ? `v${status.version}` : 'Not found',
|
|
768
|
+
});
|
|
633
769
|
if (!status.installed && autoFix) {
|
|
634
770
|
const fixes = await autoFixIssues(providerId, workingDir);
|
|
635
771
|
// Re-check after fix attempt
|
|
636
772
|
status = await getFullCLIStatus(providerId, workingDir);
|
|
637
773
|
if (!status.installed) {
|
|
638
|
-
return `${provider.name} CLI could not be installed.\n\n${fixes.join('\n')}
|
|
774
|
+
return requiresUserAction(`${provider.name} CLI could not be auto-installed`, `Automatic installation failed. Manual installation required.\n\nAuto-fix attempts:\n${fixes.join('\n')}`, [
|
|
775
|
+
`Install manually: ${provider.installCommand}`,
|
|
776
|
+
`Documentation: ${provider.installUrl}`,
|
|
777
|
+
], Date.now() - startTime);
|
|
639
778
|
}
|
|
640
779
|
}
|
|
641
780
|
if (!status.installed) {
|
|
642
|
-
return `${provider.name} CLI not installed
|
|
781
|
+
return requiresUserAction(`${provider.name} CLI not installed`, `The ${provider.name} CLI must be installed before deployment.`, [
|
|
782
|
+
`Install: ${provider.installCommand}`,
|
|
783
|
+
`Docs: ${provider.installUrl}`,
|
|
784
|
+
], Date.now() - startTime);
|
|
643
785
|
}
|
|
786
|
+
checks.push({
|
|
787
|
+
check: `${provider.name} authenticated`,
|
|
788
|
+
passed: status.authenticated,
|
|
789
|
+
details: status.authenticated ? 'Credentials valid' : 'Not logged in',
|
|
790
|
+
});
|
|
644
791
|
if (!status.authenticated) {
|
|
645
|
-
return `${provider.name}
|
|
792
|
+
return requiresUserAction(`${provider.name} authentication required`, `You must authenticate with ${provider.name} before deploying.`, [
|
|
793
|
+
`Login: ${provider.loginCommand}`,
|
|
794
|
+
`For CI, set: ${provider.envVars?.join(' or ') || 'appropriate credentials'}`,
|
|
795
|
+
], Date.now() - startTime);
|
|
646
796
|
}
|
|
647
797
|
// Execute deployment
|
|
648
798
|
const cmd = `${provider.cliCommand} ${command} 2>&1`;
|
|
649
799
|
const result = await runCommand(`cd "${workingDir}" && ${cmd}`, 300000);
|
|
800
|
+
const durationMs = Date.now() - startTime;
|
|
801
|
+
const output = result.stdout + result.stderr;
|
|
802
|
+
checks.push({
|
|
803
|
+
check: 'Deployment command',
|
|
804
|
+
passed: result.exitCode === 0,
|
|
805
|
+
details: `Exit code: ${result.exitCode}`,
|
|
806
|
+
});
|
|
807
|
+
// Analyze output
|
|
808
|
+
const analysis = analyzeOutput(output, OutputPatterns.command, result.exitCode);
|
|
809
|
+
if (result.exitCode === 0 && analysis.isSuccess) {
|
|
810
|
+
return verifiedSuccess(`${provider.name} deployment completed`, `Command: ${provider.cliCommand} ${command}\n\nOutput:\n${output}`, checks, durationMs);
|
|
811
|
+
}
|
|
812
|
+
if (result.exitCode === 0 && analysis.isFailure) {
|
|
813
|
+
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);
|
|
814
|
+
}
|
|
650
815
|
if (result.exitCode === 0) {
|
|
651
|
-
|
|
816
|
+
// Can't determine success - mark as unverified
|
|
817
|
+
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);
|
|
652
818
|
}
|
|
653
|
-
//
|
|
654
|
-
const output = result.stdout + result.stderr;
|
|
655
|
-
// AI-driven error analysis
|
|
819
|
+
// Deployment failed
|
|
656
820
|
const errorAnalysis = analyzeDeploymentError(output, provider);
|
|
657
|
-
|
|
821
|
+
const suggestedActions = errorAnalysis
|
|
822
|
+
.split('\n')
|
|
823
|
+
.filter(line => line.includes('•') || line.includes('Fix:'))
|
|
824
|
+
.map(line => line.replace(/^[•\s]+/, '').replace(/^\s*Fix:\s*/i, ''));
|
|
825
|
+
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);
|
|
658
826
|
},
|
|
659
827
|
},
|
|
660
828
|
{
|
|
@@ -700,6 +868,239 @@ Auto-detects project type (React, Vue, Angular, Next.js, etc.) and configures ap
|
|
|
700
868
|
return `📝 ${CLOUD_PROVIDERS[providerId]?.name || providerId} Configuration\n\nProject type: ${detectedType}\n\n${config}`;
|
|
701
869
|
},
|
|
702
870
|
},
|
|
871
|
+
{
|
|
872
|
+
name: 'cloud_login',
|
|
873
|
+
description: `Interactive login for cloud providers.
|
|
874
|
+
|
|
875
|
+
Supports browser-based OAuth login for:
|
|
876
|
+
- Firebase (firebase login)
|
|
877
|
+
- Google Cloud (gcloud auth login)
|
|
878
|
+
- AWS (aws configure)
|
|
879
|
+
- Azure (az login)
|
|
880
|
+
- Vercel (vercel login)
|
|
881
|
+
- Netlify (netlify login)
|
|
882
|
+
- Cloudflare (wrangler login)
|
|
883
|
+
- Aliyun (aliyun configure)
|
|
884
|
+
- Fly.io (flyctl auth login)
|
|
885
|
+
- Railway (railway login)
|
|
886
|
+
- Supabase (supabase login)
|
|
887
|
+
|
|
888
|
+
Options:
|
|
889
|
+
- reauth: Force re-authentication even if already logged in
|
|
890
|
+
- no_localhost: Use device code flow (for remote/headless machines with browser access elsewhere)
|
|
891
|
+
- ci_mode: Get instructions for CI/CD environment setup
|
|
892
|
+
|
|
893
|
+
Use this when cloud_status shows authentication issues.`,
|
|
894
|
+
parameters: {
|
|
895
|
+
type: 'object',
|
|
896
|
+
properties: {
|
|
897
|
+
provider: {
|
|
898
|
+
type: 'string',
|
|
899
|
+
description: 'Cloud provider to login: firebase, gcloud, aws, azure, vercel, netlify, cloudflare, aliyun, fly, railway, supabase',
|
|
900
|
+
},
|
|
901
|
+
reauth: {
|
|
902
|
+
type: 'boolean',
|
|
903
|
+
description: 'Force re-authentication (default: false)',
|
|
904
|
+
},
|
|
905
|
+
no_localhost: {
|
|
906
|
+
type: 'boolean',
|
|
907
|
+
description: 'Use device code flow instead of localhost redirect (default: false)',
|
|
908
|
+
},
|
|
909
|
+
ci_mode: {
|
|
910
|
+
type: 'boolean',
|
|
911
|
+
description: 'Show CI/CD setup instructions instead of interactive login (default: false)',
|
|
912
|
+
},
|
|
913
|
+
},
|
|
914
|
+
required: ['provider'],
|
|
915
|
+
},
|
|
916
|
+
handler: async (args) => {
|
|
917
|
+
const providerId = args['provider']?.toLowerCase();
|
|
918
|
+
const reauth = args['reauth'] === true;
|
|
919
|
+
const noLocalhost = args['no_localhost'] === true;
|
|
920
|
+
const ciMode = args['ci_mode'] === true;
|
|
921
|
+
const provider = CLOUD_PROVIDERS[providerId];
|
|
922
|
+
if (!provider) {
|
|
923
|
+
return `Unknown provider: ${providerId}\n\nAvailable: ${Object.keys(CLOUD_PROVIDERS).join(', ')}`;
|
|
924
|
+
}
|
|
925
|
+
// Check if CLI is installed
|
|
926
|
+
const installCheck = await checkCLIInstalled(provider);
|
|
927
|
+
if (!installCheck.installed) {
|
|
928
|
+
return `${provider.name} CLI not installed.\n\nInstall with: ${provider.installCommand}\nDocs: ${provider.installUrl}`;
|
|
929
|
+
}
|
|
930
|
+
// Build login command based on provider and options
|
|
931
|
+
const loginCommands = {
|
|
932
|
+
firebase: {
|
|
933
|
+
standard: 'firebase login',
|
|
934
|
+
reauth: 'firebase login --reauth',
|
|
935
|
+
no_localhost: 'firebase login --no-localhost',
|
|
936
|
+
ci: 'firebase login:ci',
|
|
937
|
+
},
|
|
938
|
+
gcloud: {
|
|
939
|
+
standard: 'gcloud auth login',
|
|
940
|
+
reauth: 'gcloud auth login --force',
|
|
941
|
+
no_localhost: 'gcloud auth login --no-browser',
|
|
942
|
+
ci: 'gcloud auth activate-service-account --key-file=',
|
|
943
|
+
},
|
|
944
|
+
aws: {
|
|
945
|
+
standard: 'aws configure',
|
|
946
|
+
reauth: 'aws configure',
|
|
947
|
+
no_localhost: 'aws configure',
|
|
948
|
+
ci: 'aws configure set',
|
|
949
|
+
},
|
|
950
|
+
azure: {
|
|
951
|
+
standard: 'az login',
|
|
952
|
+
reauth: 'az login',
|
|
953
|
+
no_localhost: 'az login --use-device-code',
|
|
954
|
+
ci: 'az login --service-principal',
|
|
955
|
+
},
|
|
956
|
+
vercel: {
|
|
957
|
+
standard: 'vercel login',
|
|
958
|
+
reauth: 'vercel login',
|
|
959
|
+
no_localhost: 'vercel login',
|
|
960
|
+
ci: 'vercel login --token',
|
|
961
|
+
},
|
|
962
|
+
netlify: {
|
|
963
|
+
standard: 'netlify login',
|
|
964
|
+
reauth: 'netlify login',
|
|
965
|
+
no_localhost: 'netlify login',
|
|
966
|
+
ci: 'netlify login --new',
|
|
967
|
+
},
|
|
968
|
+
cloudflare: {
|
|
969
|
+
standard: 'wrangler login',
|
|
970
|
+
reauth: 'wrangler login',
|
|
971
|
+
no_localhost: 'wrangler login',
|
|
972
|
+
ci: 'wrangler login',
|
|
973
|
+
},
|
|
974
|
+
aliyun: {
|
|
975
|
+
standard: 'aliyun configure',
|
|
976
|
+
reauth: 'aliyun configure',
|
|
977
|
+
no_localhost: 'aliyun configure',
|
|
978
|
+
ci: 'aliyun configure set',
|
|
979
|
+
},
|
|
980
|
+
fly: {
|
|
981
|
+
standard: 'flyctl auth login',
|
|
982
|
+
reauth: 'flyctl auth login',
|
|
983
|
+
no_localhost: 'flyctl auth login',
|
|
984
|
+
ci: 'flyctl auth token',
|
|
985
|
+
},
|
|
986
|
+
railway: {
|
|
987
|
+
standard: 'railway login',
|
|
988
|
+
reauth: 'railway login',
|
|
989
|
+
no_localhost: 'railway login',
|
|
990
|
+
ci: 'railway login --browserless',
|
|
991
|
+
},
|
|
992
|
+
supabase: {
|
|
993
|
+
standard: 'supabase login',
|
|
994
|
+
reauth: 'supabase login',
|
|
995
|
+
no_localhost: 'supabase login',
|
|
996
|
+
ci: 'supabase login',
|
|
997
|
+
},
|
|
998
|
+
};
|
|
999
|
+
const providerCommands = loginCommands[providerId] || {
|
|
1000
|
+
standard: provider.loginCommand,
|
|
1001
|
+
reauth: provider.loginCommand,
|
|
1002
|
+
no_localhost: provider.loginCommand,
|
|
1003
|
+
ci: provider.loginCommand,
|
|
1004
|
+
};
|
|
1005
|
+
// Select the appropriate command
|
|
1006
|
+
if (ciMode) {
|
|
1007
|
+
const cmd = providerCommands['ci'] || provider.loginCommand;
|
|
1008
|
+
return `CI/Headless Authentication for ${provider.name}
|
|
1009
|
+
|
|
1010
|
+
For CI/CD environments, you need to set environment variables instead of interactive login.
|
|
1011
|
+
|
|
1012
|
+
Command (for generating token): ${cmd}
|
|
1013
|
+
|
|
1014
|
+
Environment variables to set:
|
|
1015
|
+
${provider.envVars?.map(v => ` - ${v}`).join('\n') || ' (none defined)'}
|
|
1016
|
+
|
|
1017
|
+
For Firebase specifically:
|
|
1018
|
+
1. Run locally: firebase login:ci
|
|
1019
|
+
2. Copy the token
|
|
1020
|
+
3. Set FIREBASE_TOKEN in your CI environment
|
|
1021
|
+
|
|
1022
|
+
For service accounts (Firebase/GCP):
|
|
1023
|
+
1. Create a service account in Google Cloud Console
|
|
1024
|
+
2. Download the JSON key file
|
|
1025
|
+
3. Set GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json`;
|
|
1026
|
+
}
|
|
1027
|
+
let cmd;
|
|
1028
|
+
if (reauth) {
|
|
1029
|
+
cmd = providerCommands['reauth'] || provider.loginCommand;
|
|
1030
|
+
}
|
|
1031
|
+
else if (noLocalhost) {
|
|
1032
|
+
cmd = providerCommands['no_localhost'] || provider.loginCommand;
|
|
1033
|
+
}
|
|
1034
|
+
else {
|
|
1035
|
+
cmd = providerCommands['standard'] || provider.loginCommand;
|
|
1036
|
+
}
|
|
1037
|
+
// Use 'script' command to provide pseudo-TTY for the login command
|
|
1038
|
+
// This enables browser-based OAuth even in non-interactive shells
|
|
1039
|
+
const platform = process.platform;
|
|
1040
|
+
let wrappedCmd;
|
|
1041
|
+
if (platform === 'darwin') {
|
|
1042
|
+
// macOS: script -q /dev/null <command>
|
|
1043
|
+
wrappedCmd = `script -q /dev/null ${cmd}`;
|
|
1044
|
+
}
|
|
1045
|
+
else if (platform === 'linux') {
|
|
1046
|
+
// Linux: script -q -c "<command>" /dev/null
|
|
1047
|
+
wrappedCmd = `script -q -c "${cmd}" /dev/null`;
|
|
1048
|
+
}
|
|
1049
|
+
else {
|
|
1050
|
+
// Windows or other: try direct execution
|
|
1051
|
+
wrappedCmd = cmd;
|
|
1052
|
+
}
|
|
1053
|
+
try {
|
|
1054
|
+
const loginStartTime = Date.now();
|
|
1055
|
+
// Run with longer timeout for OAuth flow (user needs to complete in browser)
|
|
1056
|
+
const result = await runCommand(wrappedCmd, 180000);
|
|
1057
|
+
const loginDurationMs = Date.now() - loginStartTime;
|
|
1058
|
+
const loginChecks = [];
|
|
1059
|
+
loginChecks.push({
|
|
1060
|
+
check: 'Login command executed',
|
|
1061
|
+
passed: result.exitCode === 0,
|
|
1062
|
+
details: `Exit code: ${result.exitCode}`,
|
|
1063
|
+
});
|
|
1064
|
+
// CRITICAL: Don't trust string matching for "success" - actually verify authentication
|
|
1065
|
+
// The word "success" can appear in error messages like "Failed to verify success"
|
|
1066
|
+
// Always verify by actually checking authentication state
|
|
1067
|
+
const authCheck = await checkAuthentication(provider);
|
|
1068
|
+
loginChecks.push({
|
|
1069
|
+
check: 'Authentication verification',
|
|
1070
|
+
passed: authCheck.authenticated,
|
|
1071
|
+
details: authCheck.authenticated ? 'Credentials valid' : (authCheck.error || 'Not authenticated'),
|
|
1072
|
+
});
|
|
1073
|
+
if (authCheck.authenticated) {
|
|
1074
|
+
return verifiedSuccess(`${provider.name} login completed and verified`, `Provider: ${provider.name}\nCommand: ${cmd}\n\n${authCheck.details || 'Authentication verified.'}`, loginChecks, loginDurationMs);
|
|
1075
|
+
}
|
|
1076
|
+
else if (result.exitCode === 0) {
|
|
1077
|
+
// Command succeeded but auth verification failed
|
|
1078
|
+
// This can happen with race conditions or when auth takes time to propagate
|
|
1079
|
+
return unverifiedResult(`${provider.name} login command completed but verification failed`, `The login command exited successfully but we could not verify authentication.\n\nCommand output:\n${result.stdout}\n\nThis may be a temporary issue or the authentication may not have propagated yet.`, [
|
|
1080
|
+
`Wait a moment and try: ${provider.checkAuthCommand}`,
|
|
1081
|
+
`Re-run login: ${cmd}`,
|
|
1082
|
+
`Check credentials manually`,
|
|
1083
|
+
], loginDurationMs);
|
|
1084
|
+
}
|
|
1085
|
+
else {
|
|
1086
|
+
// Command failed
|
|
1087
|
+
const combined = result.stdout + result.stderr;
|
|
1088
|
+
return verifiedFailure(`${provider.name} login failed`, `Exit code: ${result.exitCode}\n\nOutput:\n${combined || '(no output)'}`, [
|
|
1089
|
+
`Try again: ${cmd}`,
|
|
1090
|
+
`For browser issues: ${providerCommands['no_localhost'] || cmd}`,
|
|
1091
|
+
`Check network connection`,
|
|
1092
|
+
`Documentation: ${provider.installUrl}`,
|
|
1093
|
+
], loginChecks, loginDurationMs);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
catch (error) {
|
|
1097
|
+
return verifiedFailure(`Error during ${provider.name} login`, error instanceof Error ? error.message : String(error), [
|
|
1098
|
+
`Run manually: ${cmd}`,
|
|
1099
|
+
`For headless environments, set: ${provider.envVars?.join(', ') || 'appropriate credentials'}`,
|
|
1100
|
+
]);
|
|
1101
|
+
}
|
|
1102
|
+
},
|
|
1103
|
+
},
|
|
703
1104
|
];
|
|
704
1105
|
}
|
|
705
1106
|
// Helper functions
|