import axios from 'axios' import React, { useEffect, useState } from 'react' import { logEvent } from 'src/services/analytics/index.js' import { Spinner } from '../components/Spinner.js' import { getOauthConfig } from '../constants/oauth.js' import { useTimeout } from '../hooks/useTimeout.js' import { Box, Text } from '@anthropic/ink' import { getSSLErrorHint } from '../services/api/errorUtils.js' import { getUserAgent } from './http.js' import { logError } from './log.js' export interface PreflightCheckResult { success: boolean error?: string sslHint?: string } async function checkEndpoints(): Promise { try { const oauthConfig = getOauthConfig() const tokenUrl = new URL(oauthConfig.TOKEN_URL) const endpoints = [ `${oauthConfig.BASE_API_URL}/api/hello`, `${tokenUrl.origin}/v1/oauth/hello`, ] const checkEndpoint = async ( url: string, ): Promise => { try { const response = await axios.get(url, { headers: { 'User-Agent': getUserAgent() }, }) if (response.status !== 200) { const hostname = new URL(url).hostname return { success: false, error: `Failed to connect to ${hostname}: Status ${response.status}`, } } return { success: true } } catch (error) { const hostname = new URL(url).hostname const sslHint = getSSLErrorHint(error) return { success: false, error: `Failed to connect to ${hostname}: ${error instanceof Error ? (error as ErrnoException).code || error.message : String(error)}`, sslHint: sslHint ?? undefined, } } } const results = await Promise.all(endpoints.map(checkEndpoint)) const failedResult = results.find(result => !result.success) if (failedResult) { // Log failure to Statsig logEvent('tengu_preflight_check_failed', { isConnectivityError: false, hasErrorMessage: !!failedResult.error, isSSLError: !!failedResult.sslHint, }) } return failedResult || { success: true } } catch (error) { logError(error as Error) // Log to Statsig logEvent('tengu_preflight_check_failed', { isConnectivityError: true, }) return { success: false, error: `Connectivity check error: ${error instanceof Error ? (error as ErrnoException).code || error.message : String(error)}`, } } } interface PreflightStepProps { onSuccess: () => void } export function PreflightStep({ onSuccess, }: PreflightStepProps): React.ReactNode { const [result, setResult] = useState(null) const [isChecking, setIsChecking] = useState(true) // delay showing the check since it's so fast that we normally // want to just immediately show the next step without a flash const showSpinner = useTimeout(1000) && isChecking useEffect(() => { async function run() { const checkResult = await checkEndpoints() setResult(checkResult) setIsChecking(false) } void run() }, []) useEffect(() => { if (result?.success) { onSuccess() } else if (result && !result.success) { const timer = setTimeout(() => process.exit(1), 100) return () => clearTimeout(timer) } }, [result, onSuccess]) return ( {isChecking && showSpinner ? ( Checking connectivity... ) : ( !result?.success && !isChecking && ( Unable to connect to Anthropic services {result?.error} {result?.sslHint ? ( {result.sslHint} See https://code.claude.com/docs/en/network-config ) : ( Please check your internet connection and network settings. Note: Claude Code might not be available in your country. Check supported countries at{' '} https://anthropic.com/supported-countries )} ) )} ) }