// @ts-nocheck import { useState, useEffect, useCallback } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from '@/components/ui/accordion'; import { useAppStore } from '@/store/app-store'; import { getElectronAPI } from '@/lib/electron'; import { CheckCircle2, Key, ArrowRight, ArrowLeft, ExternalLink, Copy, RefreshCw, Download, Info, ShieldCheck, XCircle, Trash2, } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner'; import { toast } from 'sonner'; import { StatusBadge, TerminalOutput } from '../components'; import { useCliStatus, useCliInstallation, useTokenSave } from '../hooks'; import type { ApiKeys } from '@/store/app-store'; import type { ModelProvider } from '@/store/app-store'; import type { ProviderKey } from '@/config/api-providers'; import type { CliStatus, InstallProgress, ClaudeAuthStatus, CodexAuthStatus, } from '@/store/setup-store'; import { PROVIDER_ICON_COMPONENTS } from '@/components/ui/provider-icon'; type VerificationStatus = 'idle' | 'verifying' | 'verified' | 'error'; type CliSetupAuthStatus = ClaudeAuthStatus | CodexAuthStatus; interface CliSetupConfig { cliType: ModelProvider; displayName: string; cliLabel: string; cliDescription: string; apiKeyLabel: string; apiKeyDescription: string; apiKeyProvider: ProviderKey; apiKeyPlaceholder: string; apiKeyDocsUrl: string; apiKeyDocsLabel: string; installCommands: { macos: string; windows: string; }; cliLoginCommand: string; testIds: { installButton: string; verifyCliButton: string; verifyApiKeyButton: string; apiKeyInput: string; saveApiKeyButton: string; deleteApiKeyButton: string; nextButton: string; }; buildCliAuthStatus: (previous: CliSetupAuthStatus | null) => CliSetupAuthStatus; buildApiKeyAuthStatus: (previous: CliSetupAuthStatus | null) => CliSetupAuthStatus; buildClearedAuthStatus: (previous: CliSetupAuthStatus | null) => CliSetupAuthStatus; statusApi: () => Promise; installApi: () => Promise; verifyAuthApi: ( method: 'cli' | 'api_key', apiKey?: string ) => Promise<{ success: boolean; authenticated: boolean; error?: string; details?: string; }>; apiKeyHelpText: string; } interface CliSetupStateHandlers { cliStatus: CliStatus | null; authStatus: CliSetupAuthStatus | null; setCliStatus: (status: CliStatus | null) => void; setAuthStatus: (status: CliSetupAuthStatus | null) => void; setInstallProgress: (progress: Partial) => void; getStoreState: () => CliStatus | null; } interface CliSetupStepProps { config: CliSetupConfig; state: CliSetupStateHandlers; onNext: () => void; onBack: () => void; onSkip: () => void; } export function CliSetupStep({ config, state, onNext, onBack, onSkip }: CliSetupStepProps) { const { apiKeys, setApiKeys } = useAppStore(); const { cliStatus, authStatus, setCliStatus, setAuthStatus, setInstallProgress, getStoreState } = state; const [apiKey, setApiKey] = useState(''); const [cliVerificationStatus, setCliVerificationStatus] = useState('idle'); const [cliVerificationError, setCliVerificationError] = useState(null); const [apiKeyVerificationStatus, setApiKeyVerificationStatus] = useState('idle'); const [apiKeyVerificationError, setApiKeyVerificationError] = useState(null); const [isDeletingApiKey, setIsDeletingApiKey] = useState(false); const statusApi = useCallback(() => config.statusApi(), [config]); const installApi = useCallback(() => config.installApi(), [config]); const { isChecking, checkStatus } = useCliStatus({ cliType: config.cliType, statusApi, setCliStatus, setAuthStatus, }); const onInstallSuccess = useCallback(() => { checkStatus(); }, [checkStatus]); const { isInstalling, installProgress, install } = useCliInstallation({ cliType: config.cliType, installApi, onProgressEvent: getElectronAPI().setup?.onInstallProgress, onSuccess: onInstallSuccess, getStoreState, }); const { isSaving: isSavingApiKey, saveToken: saveApiKeyToken } = useTokenSave({ provider: config.apiKeyProvider, onSuccess: () => { setAuthStatus(config.buildApiKeyAuthStatus(authStatus)); setApiKeys({ ...apiKeys, [config.apiKeyProvider]: apiKey }); toast.success('API key saved successfully!'); }, }); const verifyCliAuth = useCallback(async () => { setCliVerificationStatus('verifying'); setCliVerificationError(null); try { const result = await config.verifyAuthApi('cli'); const hasLimitOrBillingError = result.error?.toLowerCase().includes('limit reached') || result.error?.toLowerCase().includes('rate limit') || result.error?.toLowerCase().includes('credit balance') || result.error?.toLowerCase().includes('billing'); if (result.authenticated) { // Auth succeeded - even if rate limited or billing issue setCliVerificationStatus('verified'); setAuthStatus(config.buildCliAuthStatus(authStatus)); if (hasLimitOrBillingError) { // Show warning but keep auth verified toast.warning(result.error || 'Rate limit or billing issue'); } else { toast.success(`${config.displayName} CLI authentication verified!`); } } else { // Actual auth failure setCliVerificationStatus('error'); // Include detailed error if available const errorDisplay = result.details ? `${result.error}\n\nDetails: ${result.details}` : result.error || 'Authentication failed'; setCliVerificationError(errorDisplay); setAuthStatus(config.buildClearedAuthStatus(authStatus)); } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Verification failed'; setCliVerificationStatus('error'); setCliVerificationError(errorMessage); } }, [authStatus, config, setAuthStatus]); const verifyApiKeyAuth = useCallback(async () => { setApiKeyVerificationStatus('verifying'); setApiKeyVerificationError(null); try { const result = await config.verifyAuthApi('api_key', apiKey); const hasLimitOrBillingError = result.error?.toLowerCase().includes('limit reached') || result.error?.toLowerCase().includes('rate limit') || result.error?.toLowerCase().includes('credit balance') || result.error?.toLowerCase().includes('billing'); if (result.authenticated) { // Auth succeeded - even if rate limited or billing issue setApiKeyVerificationStatus('verified'); setAuthStatus(config.buildApiKeyAuthStatus(authStatus)); if (hasLimitOrBillingError) { // Show warning but keep auth verified toast.warning(result.error || 'Rate limit or billing issue'); } else { toast.success('API key authentication verified!'); } } else { // Actual auth failure setApiKeyVerificationStatus('error'); // Include detailed error if available const errorDisplay = result.details ? `${result.error}\n\nDetails: ${result.details}` : result.error || 'Authentication failed'; setApiKeyVerificationError(errorDisplay); } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Verification failed'; setApiKeyVerificationStatus('error'); setApiKeyVerificationError(errorMessage); } }, [authStatus, config, setAuthStatus]); const deleteApiKey = useCallback(async () => { setIsDeletingApiKey(true); try { const api = getElectronAPI(); if (!api.setup?.deleteApiKey) { toast.error('Delete API not available'); return; } const result = await api.setup.deleteApiKey(config.apiKeyProvider); if (result.success) { setApiKey(''); setApiKeys({ ...apiKeys, [config.apiKeyProvider]: '' }); setApiKeyVerificationStatus('idle'); setApiKeyVerificationError(null); setAuthStatus(config.buildClearedAuthStatus(authStatus)); toast.success('API key deleted successfully'); } else { toast.error(result.error || 'Failed to delete API key'); } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Failed to delete API key'; toast.error(errorMessage); } finally { setIsDeletingApiKey(false); } }, [apiKeys, authStatus, config, setApiKeys, setAuthStatus]); useEffect(() => { setInstallProgress({ isInstalling, output: installProgress.output, }); }, [isInstalling, installProgress, setInstallProgress]); useEffect(() => { checkStatus(); }, [checkStatus]); const copyCommand = (command: string) => { navigator.clipboard.writeText(command); toast.success('Command copied to clipboard'); }; const hasApiKey = !!(apiKeys as ApiKeys)[config.apiKeyProvider] || authStatus?.method === 'api_key' || authStatus?.method === 'api_key_env'; const isCliVerified = cliVerificationStatus === 'verified'; const isApiKeyVerified = apiKeyVerificationStatus === 'verified'; const isReady = isCliVerified || isApiKeyVerified; const ProviderIcon = PROVIDER_ICON_COMPONENTS[config.cliType]; const getCliStatusBadge = () => { if (cliVerificationStatus === 'verified') { return ; } if (cliVerificationStatus === 'error') { return ; } if (isChecking) { return ; } if (cliStatus?.installed) { return ; } return ; }; const getApiKeyStatusBadge = () => { if (apiKeyVerificationStatus === 'verified') { return ; } if (apiKeyVerificationStatus === 'error') { return ; } if (hasApiKey) { return ; } return ; }; return (

{config.displayName} Setup

Configure authentication for code generation

Authentication Methods
Choose one of the following methods to authenticate:

{config.cliLabel}

{config.cliDescription}

{getCliStatusBadge()}
{!cliStatus?.installed && (

Install {config.cliLabel}

{config.installCommands.macos}
{config.installCommands.windows}
{isInstalling && }
)} {cliStatus?.installed && cliStatus?.version && (

Version: {cliStatus.version}

)} {cliVerificationStatus === 'verifying' && (

Verifying CLI authentication...

Running a test query

)} {cliVerificationStatus === 'verified' && (

CLI Authentication verified!

Your {config.displayName} CLI is working correctly.

)} {cliVerificationStatus === 'error' && cliVerificationError && (

Verification failed

{(() => { const parts = cliVerificationError.split('\n\nDetails: '); const mainError = parts[0]; const details = parts[1]; const errorLower = cliVerificationError.toLowerCase(); // Check if this is actually a usage limit issue, not an auth problem const isUsageLimitIssue = errorLower.includes('usage limit') || errorLower.includes('rate limit') || errorLower.includes('limit reached') || errorLower.includes('too many requests') || errorLower.includes('credit balance') || errorLower.includes('billing') || errorLower.includes('insufficient credits') || errorLower.includes('upgrade to pro'); // Categorize error and provide helpful suggestions // IMPORTANT: Don't suggest re-authentication for usage limits! const getHelpfulSuggestion = () => { // Usage limit issue - NOT an authentication problem if (isUsageLimitIssue) { return { title: 'Usage limit issue (not authentication)', message: 'Your login credentials are working fine. This is a rate limit or billing error.', action: 'Wait a few minutes and try again, or check your billing', }; } // Token refresh failures if ( errorLower.includes('tokenrefresh') || errorLower.includes('token refresh') ) { return { title: 'Token refresh failed', message: 'Your OAuth token needs to be refreshed.', action: 'Re-authenticate', command: config.cliLoginCommand, }; } // Connection/transport issues if (errorLower.includes('transport channel closed')) { return { title: 'Connection issue', message: 'The connection to the authentication server was interrupted.', action: 'Try again or re-authenticate', command: config.cliLoginCommand, }; } // Invalid API key if (errorLower.includes('invalid') && errorLower.includes('api key')) { return { title: 'Invalid API key', message: 'Your API key is incorrect or has been revoked.', action: 'Check your API key or get a new one', }; } // Expired token if (errorLower.includes('expired')) { return { title: 'Token expired', message: 'Your authentication token has expired.', action: 'Re-authenticate', command: config.cliLoginCommand, }; } // Authentication required if (errorLower.includes('login') || errorLower.includes('authenticate')) { return { title: 'Authentication required', message: 'You need to authenticate with your account.', action: 'Run the login command', command: config.cliLoginCommand, }; } return null; }; const suggestion = getHelpfulSuggestion(); return ( <>

{mainError}

{details && (

Technical details:

                                  {details}
                                
)} {suggestion && (
💡 {suggestion.title}

{suggestion.message}

{suggestion.command && ( <>

{suggestion.action}:

{suggestion.command}
)} {!suggestion.command && (

→ {suggestion.action}

)}
)} ); })()}
)} {cliVerificationStatus !== 'verified' && ( )}

{config.apiKeyLabel}

{config.apiKeyDescription}

{getApiKeyStatusBadge()}
setApiKey(e.target.value)} className="bg-input border-border text-foreground" data-testid={config.testIds.apiKeyInput} />

{config.apiKeyHelpText}{' '} {config.apiKeyDocsLabel}

{hasApiKey && ( )}
{apiKeyVerificationStatus === 'verifying' && (

Verifying API key...

Running a test query

)} {apiKeyVerificationStatus === 'verified' && (

API Key verified!

Your API key is working correctly.

)} {apiKeyVerificationStatus === 'error' && apiKeyVerificationError && (

Verification failed

{(() => { const parts = apiKeyVerificationError.split('\n\nDetails: '); const mainError = parts[0]; const details = parts[1]; return ( <>

{mainError}

{details && (

Technical details:

                                  {details}
                                
)} ); })()}
)} {apiKeyVerificationStatus !== 'verified' && ( )}
); }