| |
| |
| |
| |
| |
|
|
| 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 { |
| SettingScope, |
| type LoadableSettingScope, |
| type LoadedSettings, |
| } 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 { validateAuthMethodWithSettings } from './useAuth.js'; |
| import { relaunchApp } 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: 'Sign in 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: AuthType | null = 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: LoadableSettingScope) => { |
| if (exiting) { |
| return; |
| } |
| if (authType) { |
| const needsRestart = |
| authType === AuthType.LOGIN_WITH_GOOGLE || |
| (authType === AuthType.USE_VERTEX_AI && |
| process.env['CLOUD_SHELL'] === 'true'); |
|
|
| if (needsRestart) { |
| 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(relaunchApp, 100); |
| return; |
| } |
|
|
| if (authType === AuthType.USE_GEMINI) { |
| |
| |
| |
| setAuthState(AuthState.AwaitingApiKeyInput); |
| return; |
| } |
| } |
| setAuthState(AuthState.Unauthenticated); |
| }, |
| [settings, config, setAuthState, exiting, setAuthContext], |
| ); |
|
|
| const handleAuthSelect = async (authMethod: AuthType) => { |
| const error = await validateAuthMethodWithSettings( |
| authMethod, |
| settings, |
| ).catch((e) => (e instanceof Error ? e.message : String(e))); |
| if (error) { |
| onAuthError(error); |
| } else { |
| |
| onSelect(authMethod, SettingScope.User); |
| } |
| }; |
|
|
| useKeypress( |
| (key) => { |
| if (key.name === 'escape') { |
| |
| |
| if (authError) { |
| return true; |
| } |
| if (settings.merged.security.auth.selectedType === undefined) { |
| |
| onAuthError( |
| 'You must select an auth method to proceed. Press Ctrl+C twice to exit.', |
| ); |
| return true; |
| } |
| |
| onSelect(undefined, SettingScope.User); |
| return true; |
| } |
| return false; |
| }, |
| { isActive: true }, |
| ); |
|
|
| if (exiting) { |
| return ( |
| <Box |
| borderStyle="round" |
| borderColor={theme.ui.focus} |
| flexDirection="row" |
| padding={1} |
| width="100%" |
| alignItems="flex-start" |
| > |
| <Text color={theme.text.primary}> |
| Logging in with Google... Restarting Gemini CLI to continue. |
| </Text> |
| </Box> |
| ); |
| } |
|
|
| return ( |
| <Box |
| borderStyle="round" |
| borderColor={theme.ui.focus} |
| flexDirection="row" |
| padding={1} |
| width="100%" |
| alignItems="flex-start" |
| > |
| <Text color={theme.text.accent}>? </Text> |
| <Box flexDirection="column" flexGrow={1}> |
| <Text bold color={theme.text.primary}> |
| Get started |
| </Text> |
| <Box marginTop={1}> |
| <Text color={theme.text.primary}> |
| How would you like to authenticate for this project? |
| </Text> |
| </Box> |
| <Box marginTop={1}> |
| <RadioButtonSelect |
| items={items} |
| initialIndex={initialAuthIndex} |
| onSelect={handleAuthSelect} |
| onHighlight={() => { |
| onAuthError(null); |
| }} |
| /> |
| </Box> |
| {authError && ( |
| <Box marginTop={1}> |
| <Text color={theme.status.error}>{authError}</Text> |
| </Box> |
| )} |
| <Box marginTop={1}> |
| <Text color={theme.text.secondary}>(Use Enter to select)</Text> |
| </Box> |
| <Box marginTop={1}> |
| <Text color={theme.text.primary}> |
| Terms of Services and Privacy Notice for Gemini CLI |
| </Text> |
| </Box> |
| <Box marginTop={1}> |
| <Text color={theme.text.link}> |
| {'https://geminicli.com/docs/resources/tos-privacy/'} |
| </Text> |
| </Box> |
| </Box> |
| </Box> |
| ); |
| } |
|
|