import { useState, useEffect, useCallback, useRef } from 'react'; import { createLogger } from '@automaker/utils/logger'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { useSetupStore } from '@/store/setup-store'; import { getElectronAPI } from '@/lib/electron'; import { CheckCircle2, ArrowRight, ArrowLeft, ExternalLink, Copy, RefreshCw, AlertTriangle, XCircle, Terminal, } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner'; import { toast } from 'sonner'; import { StatusBadge } from '../components'; const logger = createLogger('OpencodeSetupStep'); interface OpencodeSetupStepProps { onNext: () => void; onBack: () => void; onSkip: () => void; } interface OpencodeCliStatus { installed: boolean; version?: string | null; path?: string | null; auth?: { authenticated: boolean; method: string; }; installCommand?: string; loginCommand?: string; } export function OpencodeSetupStep({ onNext, onBack, onSkip }: OpencodeSetupStepProps) { const { opencodeCliStatus, setOpencodeCliStatus } = useSetupStore(); const [isChecking, setIsChecking] = useState(false); const [isLoggingIn, setIsLoggingIn] = useState(false); const pollIntervalRef = useRef(null); const checkStatus = useCallback(async () => { setIsChecking(true); try { const api = getElectronAPI(); if (!api.setup?.getOpencodeStatus) { return; } const result = await api.setup.getOpencodeStatus(); if (result.success) { const status: OpencodeCliStatus = { installed: result.installed ?? false, version: result.version, path: result.path, auth: result.auth, installCommand: result.installCommand, loginCommand: result.loginCommand, }; setOpencodeCliStatus(status); if (result.auth?.authenticated) { toast.success('OpenCode CLI is ready!'); } } } catch (error) { logger.error('Failed to check OpenCode status:', error); } finally { setIsChecking(false); } }, [setOpencodeCliStatus]); useEffect(() => { checkStatus(); // Cleanup polling on unmount return () => { if (pollIntervalRef.current) { clearInterval(pollIntervalRef.current); } }; }, [checkStatus]); const copyCommand = (command: string) => { navigator.clipboard.writeText(command); toast.success('Command copied to clipboard'); }; const handleLogin = async () => { setIsLoggingIn(true); try { // Copy login command to clipboard and show instructions const loginCommand = opencodeCliStatus?.loginCommand || 'opencode auth login'; await navigator.clipboard.writeText(loginCommand); toast.info('Login command copied! Paste in terminal to authenticate.'); // Poll for auth status let attempts = 0; const maxAttempts = 60; // 2 minutes with 2s interval pollIntervalRef.current = setInterval(async () => { attempts++; try { const api = getElectronAPI(); if (!api.setup?.getOpencodeStatus) { return; } const result = await api.setup.getOpencodeStatus(); if (result.auth?.authenticated) { if (pollIntervalRef.current) { clearInterval(pollIntervalRef.current); pollIntervalRef.current = null; } setOpencodeCliStatus({ ...opencodeCliStatus, installed: result.installed ?? true, version: result.version, path: result.path, auth: result.auth, } as OpencodeCliStatus); setIsLoggingIn(false); toast.success('Successfully logged in to OpenCode!'); } } catch { // Ignore polling errors } if (attempts >= maxAttempts) { if (pollIntervalRef.current) { clearInterval(pollIntervalRef.current); pollIntervalRef.current = null; } setIsLoggingIn(false); toast.error('Login timed out. Please try again.'); } }, 2000); } catch (error) { logger.error('Login failed:', error); toast.error('Failed to start login process'); setIsLoggingIn(false); } }; const isReady = opencodeCliStatus?.installed && opencodeCliStatus?.auth?.authenticated; const getStatusBadge = () => { if (isChecking) { return ; } if (opencodeCliStatus?.auth?.authenticated) { return ; } if (opencodeCliStatus?.installed) { return ; } return ; }; return (

OpenCode CLI Setup

Optional - Use OpenCode as an AI provider

{/* Info Banner */}

This step is optional

Configure OpenCode CLI for access to free tier models and connected providers. You can skip this and use other providers, or configure it later in Settings.

{/* Status Card */}
OpenCode CLI Status Optional
{getStatusBadge()}
{opencodeCliStatus?.installed ? opencodeCliStatus.auth?.authenticated ? `Authenticated via ${opencodeCliStatus.auth.method === 'api_key' ? 'API Key' : 'Browser Login'}${opencodeCliStatus.version ? ` (v${opencodeCliStatus.version})` : ''}` : 'Installed but not authenticated' : 'Not installed on your system'}
{/* Success State */} {isReady && (

OpenCode CLI is ready!

You can use OpenCode models for AI tasks. {opencodeCliStatus?.version && ( Version: {opencodeCliStatus.version} )}

)} {/* Not Installed */} {!opencodeCliStatus?.installed && !isChecking && (

OpenCode CLI not found

Install the OpenCode CLI to use free tier models and connected providers.

Install OpenCode CLI:

{opencodeCliStatus?.installCommand || 'npm install -g opencode'}
View installation docs
)} {/* Installed but not authenticated */} {opencodeCliStatus?.installed && !opencodeCliStatus?.auth?.authenticated && !isChecking && (

OpenCode CLI not authenticated

Run the login command to authenticate with OpenCode.

Run the login command in your terminal, then complete authentication in your browser:

{opencodeCliStatus?.loginCommand || 'opencode auth login'}
)} {/* Loading State */} {isChecking && (

Checking OpenCode CLI status...

)}
{/* Navigation */}
{/* Info note */}

You can always configure OpenCode later in Settings

); }