/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import type React from 'react'; import { useCallback, useState } from 'react'; import { Box, Text } from 'ink'; import { theme } from '../semantic-colors.js'; import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js'; import type { LoadableSettingScope, LoadedSettings, } from '../../config/settings.js'; import { SettingScope } from '../../config/settings.js'; import { AuthType, clearCachedCredentialFile, 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'; import { RELAUNCH_EXIT_CODE } from '../../utils/processUtils.js'; interface AuthDialogProps { config: Config; settings: LoadedSettings; setAuthState: (state: AuthState) => void; authError: string | null; onAuthError: (error: string | null) => void; setAuthContext: (context: { requiresRestart?: boolean }) => void; } export function AuthDialog({ config, settings, setAuthState, authError, onAuthError, setAuthContext, }: AuthDialogProps): React.JSX.Element { const [exiting, setExiting] = useState(false); 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.COMPUTE_ADC, key: AuthType.COMPUTE_ADC, }, ] : process.env['GEMINI_CLI_USE_COMPUTE_ADC'] === 'true' ? [ { label: 'Use metadata server application default credentials', value: AuthType.COMPUTE_ADC, key: AuthType.COMPUTE_ADC, }, ] : []), { 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 && // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion Object.values(AuthType).includes(defaultAuthTypeEnv as AuthType) ) { // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion 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: LoadableSettingScope) => { if (exiting) { return; } if (authType) { if (authType === AuthType.LOGIN_WITH_GOOGLE) { setAuthContext({ requiresRestart: true }); } else { setAuthContext({}); } await clearCachedCredentialFile(); settings.setValue(scope, 'security.auth.selectedType', authType); if ( authType === AuthType.LOGIN_WITH_GOOGLE && config.isBrowserLaunchSuppressed() ) { setExiting(true); setTimeout(async () => { await runExitCleanup(); process.exit(RELAUNCH_EXIT_CODE); }, 100); return; } if (authType === AuthType.USE_GEMINI) { if (process.env['GEMINI_API_KEY'] !== undefined) { setAuthState(AuthState.Unauthenticated); return; } else { setAuthState(AuthState.AwaitingApiKeyInput); return; } } } setAuthState(AuthState.Unauthenticated); }, [settings, config, setAuthState, exiting, setAuthContext], ); const handleAuthSelect = (authMethod: AuthType) => { const error = validateAuthMethodWithSettings(authMethod, settings); if (error) { onAuthError(error); } else { // eslint-disable-next-line @typescript-eslint/no-floating-promises 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 true; } 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 true; } // eslint-disable-next-line @typescript-eslint/no-floating-promises onSelect(undefined, SettingScope.User); return true; } return false; }, { isActive: true }, ); if (exiting) { return ( Logging in with Google... Restarting Gemini CLI to continue. ); } 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://geminicli.com/docs/resources/tos-privacy/'} ); }