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 { useSetupStore } from '@/store/setup-store'; 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 { AnthropicIcon } from '@/components/ui/provider-icon'; interface ClaudeSetupStepProps { onNext: () => void; onBack: () => void; onSkip: () => void; } interface ClaudeSetupContentProps { /** Hide header and navigation for embedded use */ embedded?: boolean; } type VerificationStatus = 'idle' | 'verifying' | 'verified' | 'error'; // Claude Setup Step // Users can either: // 1. Have Claude CLI installed and authenticated (verified by running a test query) // 2. Provide an Anthropic API key manually export function ClaudeSetupStep({ onNext, onBack, onSkip }: ClaudeSetupStepProps) { const { claudeCliStatus, claudeAuthStatus, setClaudeCliStatus, setClaudeAuthStatus, setClaudeInstallProgress, } = useSetupStore(); const { setApiKeys, apiKeys } = useAppStore(); const [apiKey, setApiKey] = useState(''); // CLI Verification state const [cliVerificationStatus, setCliVerificationStatus] = useState('idle'); const [cliVerificationError, setCliVerificationError] = useState(null); // API Key Verification state const [apiKeyVerificationStatus, setApiKeyVerificationStatus] = useState('idle'); const [apiKeyVerificationError, setApiKeyVerificationError] = useState(null); // Delete API Key state const [isDeletingApiKey, setIsDeletingApiKey] = useState(false); // Memoize API functions to prevent infinite loops const statusApi = useCallback( () => getElectronAPI().setup?.getClaudeStatus() || Promise.reject(), [] ); const installApi = useCallback( () => getElectronAPI().setup?.installClaude() || Promise.reject(), [] ); const getStoreState = useCallback(() => useSetupStore.getState().claudeCliStatus, []); // Use custom hooks const { isChecking, checkStatus } = useCliStatus({ cliType: 'claude', statusApi, setCliStatus: setClaudeCliStatus, setAuthStatus: setClaudeAuthStatus, }); const onInstallSuccess = useCallback(() => { checkStatus(); }, [checkStatus]); const { isInstalling, installProgress, install } = useCliInstallation({ cliType: 'claude', installApi, onProgressEvent: getElectronAPI().setup?.onInstallProgress, onSuccess: onInstallSuccess, getStoreState, }); const { isSaving: isSavingApiKey, saveToken: saveApiKeyToken } = useTokenSave({ provider: 'anthropic', onSuccess: () => { setClaudeAuthStatus({ authenticated: true, method: 'api_key', hasCredentialsFile: false, apiKeyValid: true, }); setApiKeys({ ...apiKeys, anthropic: apiKey }); toast.success('API key saved successfully!'); }, }); // Verify CLI authentication by running a test query (uses CLI credentials only, not API key) const verifyCliAuth = useCallback(async () => { setCliVerificationStatus('verifying'); setCliVerificationError(null); try { const api = getElectronAPI(); if (!api.setup?.verifyClaudeAuth) { setCliVerificationStatus('error'); setCliVerificationError('Verification API not available'); return; } // Pass "cli" to verify CLI authentication only (ignores any API key) const result = await api.setup.verifyClaudeAuth('cli'); // Check for "Limit reached" error - treat as unverified const hasLimitReachedError = result.error?.toLowerCase().includes('limit reached') || result.error?.toLowerCase().includes('rate limit'); if (result.authenticated && !hasLimitReachedError) { setCliVerificationStatus('verified'); setClaudeAuthStatus({ authenticated: true, method: 'cli_authenticated', hasCredentialsFile: claudeAuthStatus?.hasCredentialsFile || false, }); toast.success('Claude CLI authentication verified!'); } else { setCliVerificationStatus('error'); setCliVerificationError( hasLimitReachedError ? 'Rate limit reached. Please try again later.' : result.error || 'Authentication failed' ); setClaudeAuthStatus({ authenticated: false, method: 'none', hasCredentialsFile: claudeAuthStatus?.hasCredentialsFile || false, }); } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Verification failed'; // Also check for limit reached in caught errors const isLimitError = errorMessage.toLowerCase().includes('limit reached') || errorMessage.toLowerCase().includes('rate limit'); setCliVerificationStatus('error'); setCliVerificationError( isLimitError ? 'Rate limit reached. Please try again later.' : errorMessage ); } }, [claudeAuthStatus, setClaudeAuthStatus]); // Verify API Key authentication (uses API key only) const verifyApiKeyAuth = useCallback(async () => { setApiKeyVerificationStatus('verifying'); setApiKeyVerificationError(null); try { const api = getElectronAPI(); if (!api.setup?.verifyClaudeAuth) { setApiKeyVerificationStatus('error'); setApiKeyVerificationError('Verification API not available'); return; } // Pass "api_key" to verify API key authentication only const result = await api.setup.verifyClaudeAuth('api_key'); if (result.authenticated) { setApiKeyVerificationStatus('verified'); setClaudeAuthStatus({ authenticated: true, method: 'api_key', hasCredentialsFile: false, apiKeyValid: true, }); toast.success('API key authentication verified!'); } else { setApiKeyVerificationStatus('error'); setApiKeyVerificationError(result.error || 'Authentication failed'); } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Verification failed'; setApiKeyVerificationStatus('error'); setApiKeyVerificationError(errorMessage); } }, [setClaudeAuthStatus]); // Delete API Key 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('anthropic'); if (result.success) { // Clear local state setApiKey(''); setApiKeys({ ...apiKeys, anthropic: '' }); setApiKeyVerificationStatus('idle'); setApiKeyVerificationError(null); setClaudeAuthStatus({ authenticated: false, method: 'none', hasCredentialsFile: claudeAuthStatus?.hasCredentialsFile || false, }); 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, setApiKeys, claudeAuthStatus, setClaudeAuthStatus]); // Sync install progress to store useEffect(() => { setClaudeInstallProgress({ isInstalling, output: installProgress.output, }); }, [isInstalling, installProgress, setClaudeInstallProgress]); // Check status on mount useEffect(() => { checkStatus(); }, [checkStatus]); const copyCommand = (command: string) => { navigator.clipboard.writeText(command); toast.success('Command copied to clipboard'); }; // User is ready if either method is verified const hasApiKey = !!apiKeys.anthropic || claudeAuthStatus?.method === 'api_key' || claudeAuthStatus?.method === 'api_key_env'; const isCliVerified = cliVerificationStatus === 'verified'; const isApiKeyVerified = apiKeyVerificationStatus === 'verified'; const isReady = isCliVerified || isApiKeyVerified; const getAuthMethodLabel = () => { if (isApiKeyVerified) return 'API Key'; if (isCliVerified) return 'Claude CLI'; return null; }; // Helper to get status badge for CLI const getCliStatusBadge = () => { if (cliVerificationStatus === 'verified') { return ; } if (cliVerificationStatus === 'error') { return ; } if (isChecking) { return ; } if (claudeCliStatus?.installed) { // Installed but not yet verified - show yellow unverified badge return ; } return ; }; // Helper to get status badge for API Key const getApiKeyStatusBadge = () => { if (apiKeyVerificationStatus === 'verified') { return ; } if (apiKeyVerificationStatus === 'error') { return ; } if (hasApiKey) { // API key configured but not yet verified - show yellow unverified badge return ; } return ; }; return (

Claude Code Setup

Configure for code generation

{/* Requirements Info */}
Authentication Methods
Choose one of the following methods to authenticate with Claude:
{/* Option 1: Claude CLI */}

Claude CLI

Use Claude Code subscription

{getCliStatusBadge()}
{/* CLI Install Section */} {!claudeCliStatus?.installed && (

Install Claude CLI

curl -fsSL https://claude.ai/install.sh | bash
irm https://claude.ai/install.ps1 | iex
{isInstalling && }
)} {/* CLI Version Info */} {claudeCliStatus?.installed && claudeCliStatus?.version && (

Version: {claudeCliStatus.version}

)} {/* CLI Verification Status */} {cliVerificationStatus === 'verifying' && (

Verifying CLI authentication...

Running a test query

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

CLI Authentication verified!

Your Claude CLI is working correctly.

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

Verification failed

{cliVerificationError}

{cliVerificationError.includes('login') && (

Run this command in your terminal:

claude login
)}
)} {/* CLI Verify Button - Hide if CLI is verified */} {cliVerificationStatus !== 'verified' && ( )}
{/* Option 2: API Key */}

Anthropic API Key

Pay-per-use with your own API key

{getApiKeyStatusBadge()}
{/* API Key Input */}
setApiKey(e.target.value)} className="bg-input border-border text-foreground" data-testid="anthropic-api-key-input" />

Don't have an API key?{' '} Get one from Anthropic Console

{hasApiKey && ( )}
{/* API Key Verification Status */} {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

{apiKeyVerificationError}

)} {/* API Key Verify Button - Hide if API key is verified */} {apiKeyVerificationStatus !== 'verified' && ( )}
{/* Navigation */}
); }