/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import type React from 'react'; import { useCallback } from 'react'; import { Box, Text } from 'ink'; import { theme } from '../semantic-colors.js'; import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js'; import type { LoadedSettings } from '../../config/settings.js'; import { SettingScope } from '../../config/settings.js'; import { AuthType, clearCachedCredentialFile, debugLogger, type Config, } from '@google/gemini-cli-core'; import { useKeypress } from '../hooks/useKeypress.js'; import { AuthState } from '../types.js'; import { runExitCleanup } from '../../utils/cleanup.js'; import { validateAuthMethodWithSettings } from './useAuth.js'; interface AuthDialogProps { config: Config; settings: LoadedSettings; setAuthState: (state: AuthState) => void; authError: string | null; onAuthError: (error: string | null) => void; } export function AuthDialog({ config, settings, setAuthState, authError, onAuthError, }: AuthDialogProps): React.JSX.Element { let items = [ { label: 'Login with Google', value: AuthType.LOGIN_WITH_GOOGLE, key: AuthType.LOGIN_WITH_GOOGLE, }, ...(process.env['CLOUD_SHELL'] === 'true' ? [ { label: 'Use Cloud Shell user credentials', value: AuthType.CLOUD_SHELL, key: AuthType.CLOUD_SHELL, }, ] : []), { label: 'Use Gemini API Key', value: AuthType.USE_GEMINI, key: AuthType.USE_GEMINI, }, { label: 'Vertex AI', value: AuthType.USE_VERTEX_AI, key: AuthType.USE_VERTEX_AI, }, ]; if (settings.merged.security?.auth?.enforcedType) { items = items.filter( (item) => item.value === settings.merged.security?.auth?.enforcedType, ); } let defaultAuthType = null; const defaultAuthTypeEnv = process.env['GEMINI_DEFAULT_AUTH_TYPE']; if ( defaultAuthTypeEnv && Object.values(AuthType).includes(defaultAuthTypeEnv as AuthType) ) { defaultAuthType = defaultAuthTypeEnv as AuthType; } let initialAuthIndex = items.findIndex((item) => { if (settings.merged.security?.auth?.selectedType) { return item.value === settings.merged.security.auth.selectedType; } if (defaultAuthType) { return item.value === defaultAuthType; } if (process.env['GEMINI_API_KEY']) { return item.value === AuthType.USE_GEMINI; } return item.value === AuthType.LOGIN_WITH_GOOGLE; }); if (settings.merged.security?.auth?.enforcedType) { initialAuthIndex = 0; } const onSelect = useCallback( async (authType: AuthType | undefined, scope: SettingScope) => { if (authType) { await clearCachedCredentialFile(); settings.setValue(scope, 'security.auth.selectedType', authType); if ( authType === AuthType.LOGIN_WITH_GOOGLE && config.isBrowserLaunchSuppressed() ) { runExitCleanup(); debugLogger.log( ` ---------------------------------------------------------------- Logging in with Google... Please restart Gemini CLI to continue. ---------------------------------------------------------------- `, ); process.exit(0); } } if (authType === AuthType.USE_GEMINI) { setAuthState(AuthState.AwaitingApiKeyInput); return; } setAuthState(AuthState.Unauthenticated); }, [settings, config, setAuthState], ); const handleAuthSelect = (authMethod: AuthType) => { const error = validateAuthMethodWithSettings(authMethod, settings); if (error) { onAuthError(error); } else { onSelect(authMethod, SettingScope.User); } }; useKeypress( (key) => { if (key.name === 'escape') { // Prevent exit if there is an error message. // This means they user is not authenticated yet. if (authError) { return; } if (settings.merged.security?.auth?.selectedType === undefined) { // Prevent exiting if no auth method is set onAuthError( 'You must select an auth method to proceed. Press Ctrl+C twice to exit.', ); return; } onSelect(undefined, SettingScope.User); } }, { isActive: true }, ); return ( ? Get started How would you like to authenticate for this project? { onAuthError(null); }} /> {authError && ( {authError} )} (Use Enter to select) Terms of Services and Privacy Notice for Gemini CLI { 'https://github.com/google-gemini/gemini-cli/blob/main/docs/tos-privacy.md' } ); }