2025-04-18 17:44:24 -07:00
/ * *
* @license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
2025-08-20 12:49:15 -07:00
import { useCallback , useEffect , useMemo , useState , useRef } from 'react' ;
2025-08-26 12:01:31 -06:00
import {
Box ,
type DOMElement ,
measureElement ,
Static ,
Text ,
useStdin ,
useStdout ,
} from 'ink' ;
import {
StreamingState ,
type HistoryItem ,
MessageType ,
ToolCallStatus ,
type HistoryItemWithoutId ,
} from './types.js' ;
2025-05-15 22:56:03 -07:00
import { useTerminalSize } from './hooks/useTerminalSize.js' ;
Initial commit of Gemini Code CLI
This commit introduces the initial codebase for the Gemini Code CLI, a command-line interface designed to facilitate interaction with the Gemini API for software engineering tasks.
The code was migrated from a previous git repository as a single squashed commit.
Core Features & Components:
* **Gemini Integration:** Leverages the `@google/genai` SDK to interact with the Gemini models, supporting chat history, streaming responses, and function calling (tools).
* **Terminal UI:** Built with Ink (React for CLIs) providing an interactive chat interface within the terminal, including input prompts, message display, loading indicators, and tool interaction elements.
* **Tooling Framework:** Implements a robust tool system allowing Gemini to interact with the local environment. Includes tools for:
* File system listing (`ls`)
* File reading (`read-file`)
* Content searching (`grep`)
* File globbing (`glob`)
* File editing (`edit`)
* File writing (`write-file`)
* Executing bash commands (`terminal`)
* **State Management:** Handles the streaming state of Gemini responses and manages the conversation history.
* **Configuration:** Parses command-line arguments (`yargs`) and loads environment variables (`dotenv`) for setup.
* **Project Structure:** Organized into `core`, `ui`, `tools`, `config`, and `utils` directories using TypeScript. Includes basic build (`tsc`) and start scripts.
This initial version establishes the foundation for a powerful CLI tool enabling developers to use Gemini for coding assistance directly in their terminal environment.
---
Created by yours truly: __Gemini Code__
2025-04-15 21:41:08 -07:00
import { useGeminiStream } from './hooks/useGeminiStream.js' ;
import { useLoadingIndicator } from './hooks/useLoadingIndicator.js' ;
2025-04-22 18:57:47 -07:00
import { useThemeCommand } from './hooks/useThemeCommand.js' ;
2025-06-19 16:52:22 -07:00
import { useAuthCommand } from './hooks/useAuthCommand.js' ;
2025-08-08 11:02:27 -07:00
import { useFolderTrust } from './hooks/useFolderTrust.js' ;
2025-09-03 11:44:26 -07:00
import { useIdeTrustListener } from './hooks/useIdeTrustListener.js' ;
2025-06-12 02:21:54 +01:00
import { useEditorSettings } from './hooks/useEditorSettings.js' ;
2025-05-13 23:55:49 +00:00
import { useSlashCommandProcessor } from './hooks/slashCommandProcessor.js' ;
2025-05-17 21:25:28 -07:00
import { useAutoAcceptIndicator } from './hooks/useAutoAcceptIndicator.js' ;
2025-08-19 09:25:16 -07:00
import { useMessageQueue } from './hooks/useMessageQueue.js' ;
2025-05-23 22:51:47 -07:00
import { useConsoleMessages } from './hooks/useConsoleMessages.js' ;
2025-04-18 19:09:41 -04:00
import { Header } from './components/Header.js' ;
import { LoadingIndicator } from './components/LoadingIndicator.js' ;
2025-05-17 21:25:28 -07:00
import { AutoAcceptIndicator } from './components/AutoAcceptIndicator.js' ;
2025-05-18 22:16:57 -07:00
import { ShellModeIndicator } from './components/ShellModeIndicator.js' ;
2025-05-20 16:50:32 -07:00
import { InputPrompt } from './components/InputPrompt.js' ;
2025-04-18 19:09:41 -04:00
import { Footer } from './components/Footer.js' ;
2025-04-22 18:57:47 -07:00
import { ThemeDialog } from './components/ThemeDialog.js' ;
2025-06-19 16:52:22 -07:00
import { AuthDialog } from './components/AuthDialog.js' ;
2025-06-20 10:46:41 -07:00
import { AuthInProgress } from './components/AuthInProgress.js' ;
2025-06-12 02:21:54 +01:00
import { EditorSettingsDialog } from './components/EditorSettingsDialog.js' ;
2025-08-08 11:02:27 -07:00
import { FolderTrustDialog } from './components/FolderTrustDialog.js' ;
2025-07-27 02:00:26 -04:00
import { ShellConfirmationDialog } from './components/ShellConfirmationDialog.js' ;
2025-08-09 15:59:22 +09:00
import { RadioButtonSelect } from './components/shared/RadioButtonSelect.js' ;
2025-08-15 20:18:31 -07:00
import { Colors } from './colors.js' ;
2025-07-28 11:13:46 -07:00
import { loadHierarchicalGeminiMemory } from '../config/config.js' ;
2025-08-26 00:04:53 +02:00
import type { LoadedSettings } from '../config/settings.js' ;
import { SettingScope } from '../config/settings.js' ;
2025-04-22 18:57:47 -07:00
import { Tips } from './components/Tips.js' ;
2025-07-12 15:42:47 -07:00
import { ConsolePatcher } from './utils/ConsolePatcher.js' ;
import { registerCleanup } from '../utils/cleanup.js' ;
2025-05-22 10:36:44 -07:00
import { DetailedMessagesDisplay } from './components/DetailedMessagesDisplay.js' ;
2025-04-25 17:11:08 -07:00
import { HistoryItemDisplay } from './components/HistoryItemDisplay.js' ;
2025-06-01 15:48:48 -07:00
import { ContextSummaryDisplay } from './components/ContextSummaryDisplay.js' ;
2025-05-06 16:20:28 -07:00
import { useHistory } from './hooks/useHistoryManager.js' ;
2025-08-30 03:18:22 +09:00
import { useInputHistoryStore } from './hooks/useInputHistoryStore.js' ;
2025-05-21 07:36:22 +00:00
import process from 'node:process' ;
2025-09-04 09:32:09 -07:00
import type {
EditorType ,
Config ,
IdeContext ,
DetectedIde ,
} from '@google/gemini-cli-core' ;
2025-05-31 12:49:28 -07:00
import {
2025-06-02 22:05:45 +02:00
ApprovalMode ,
2025-08-26 00:04:53 +02:00
getAllGeminiMdFilenames ,
2025-06-12 02:21:54 +01:00
isEditorAvailable ,
2025-08-26 00:04:53 +02:00
getErrorMessage ,
2025-07-15 21:13:30 -07:00
AuthType ,
2025-08-26 00:04:53 +02:00
logFlashFallback ,
FlashFallbackEvent ,
2025-07-18 18:14:46 -04:00
ideContext ,
2025-08-26 00:04:53 +02:00
isProQuotaExceededError ,
isGenericQuotaExceededError ,
UserTierId ,
2025-08-29 04:19:43 +05:30
DEFAULT_GEMINI_FLASH_MODEL ,
2025-09-04 09:32:09 -07:00
IdeClient ,
2025-06-25 05:41:11 -07:00
} from '@google/gemini-cli-core' ;
2025-08-26 00:04:53 +02:00
import type { IdeIntegrationNudgeResult } from './IdeIntegrationNudge.js' ;
import { IdeIntegrationNudge } from './IdeIntegrationNudge.js' ;
2025-06-19 16:52:22 -07:00
import { validateAuthMethod } from '../config/auth.js' ;
2025-05-21 13:31:18 -07:00
import { useLogger } from './hooks/useLogger.js' ;
2025-05-28 19:46:08 +00:00
import { StreamingContext } from './contexts/StreamingContext.js' ;
2025-06-15 11:15:53 -07:00
import {
SessionStatsProvider ,
useSessionStats ,
} from './contexts/SessionContext.js' ;
2025-05-28 23:30:05 +00:00
import { useGitBranchName } from './hooks/useGitBranchName.js' ;
2025-07-17 20:45:42 -04:00
import { useFocus } from './hooks/useFocus.js' ;
2025-06-27 10:57:32 -07:00
import { useBracketedPaste } from './hooks/useBracketedPaste.js' ;
2025-06-13 09:59:09 -07:00
import { useTextBuffer } from './components/shared/text-buffer.js' ;
2025-07-25 15:36:42 -07:00
import { useVimMode , VimModeProvider } from './contexts/VimModeContext.js' ;
import { useVim } from './hooks/vim.js' ;
2025-08-26 00:04:53 +02:00
import type { Key } from './hooks/useKeypress.js' ;
import { useKeypress } from './hooks/useKeypress.js' ;
2025-08-15 10:54:00 -07:00
import { KeypressProvider } from './contexts/KeypressContext.js' ;
2025-08-13 13:32:54 -04:00
import { useKittyKeyboardProtocol } from './hooks/useKittyKeyboardProtocol.js' ;
2025-08-09 16:03:17 +09:00
import { keyMatchers , Command } from './keyMatchers.js' ;
2025-08-25 22:11:27 +02:00
import * as fs from 'node:fs' ;
2025-06-17 08:24:07 -07:00
import { UpdateNotification } from './components/UpdateNotification.js' ;
2025-08-26 00:04:53 +02:00
import type { UpdateObject } from './utils/updateCheck.js' ;
2025-06-21 11:11:42 -07:00
import ansiEscapes from 'ansi-escapes' ;
2025-06-22 00:54:10 +00:00
import { OverflowProvider } from './contexts/OverflowContext.js' ;
import { ShowMoreLines } from './components/ShowMoreLines.js' ;
2025-06-27 12:07:38 -07:00
import { PrivacyNotice } from './privacy/PrivacyNotice.js' ;
2025-08-10 09:04:52 +09:00
import { useSettingsCommand } from './hooks/useSettingsCommand.js' ;
import { SettingsDialog } from './components/SettingsDialog.js' ;
2025-08-29 04:19:43 +05:30
import { ProQuotaDialog } from './components/ProQuotaDialog.js' ;
2025-07-28 17:56:52 -07:00
import { setUpdateHandler } from '../utils/handleAutoUpdate.js' ;
2025-07-25 17:35:26 -07:00
import { appEvents , AppEvent } from '../utils/events.js' ;
2025-08-07 15:55:53 -07:00
import { isNarrowWidth } from './utils/isNarrowWidth.js' ;
2025-08-27 00:43:02 +00:00
import { useWorkspaceMigration } from './hooks/useWorkspaceMigration.js' ;
import { WorkspaceMigrationDialog } from './components/WorkspaceMigrationDialog.js' ;
2025-08-28 19:41:33 -07:00
import { isWorkspaceTrusted } from '../config/trustedFolders.js' ;
2025-04-18 21:55:02 +01:00
2025-06-13 09:59:09 -07:00
const CTRL_EXIT_PROMPT_DURATION_MS = 1000 ;
2025-08-19 09:25:16 -07:00
// Maximum number of queued messages to display in UI to prevent performance issues
const MAX_DISPLAYED_QUEUED_MESSAGES = 3 ;
2025-05-30 19:36:52 -07:00
Initial commit of Gemini Code CLI
This commit introduces the initial codebase for the Gemini Code CLI, a command-line interface designed to facilitate interaction with the Gemini API for software engineering tasks.
The code was migrated from a previous git repository as a single squashed commit.
Core Features & Components:
* **Gemini Integration:** Leverages the `@google/genai` SDK to interact with the Gemini models, supporting chat history, streaming responses, and function calling (tools).
* **Terminal UI:** Built with Ink (React for CLIs) providing an interactive chat interface within the terminal, including input prompts, message display, loading indicators, and tool interaction elements.
* **Tooling Framework:** Implements a robust tool system allowing Gemini to interact with the local environment. Includes tools for:
* File system listing (`ls`)
* File reading (`read-file`)
* Content searching (`grep`)
* File globbing (`glob`)
* File editing (`edit`)
* File writing (`write-file`)
* Executing bash commands (`terminal`)
* **State Management:** Handles the streaming state of Gemini responses and manages the conversation history.
* **Configuration:** Parses command-line arguments (`yargs`) and loads environment variables (`dotenv`) for setup.
* **Project Structure:** Organized into `core`, `ui`, `tools`, `config`, and `utils` directories using TypeScript. Includes basic build (`tsc`) and start scripts.
This initial version establishes the foundation for a powerful CLI tool enabling developers to use Gemini for coding assistance directly in their terminal environment.
---
Created by yours truly: __Gemini Code__
2025-04-15 21:41:08 -07:00
interface AppProps {
2025-04-19 19:45:42 +01:00
config : Config ;
2025-08-20 12:49:15 -07:00
settings : LoadedSettings ;
2025-05-14 00:12:04 +00:00
startupWarnings? : string [ ] ;
2025-07-11 13:43:57 -07:00
version : string ;
Initial commit of Gemini Code CLI
This commit introduces the initial codebase for the Gemini Code CLI, a command-line interface designed to facilitate interaction with the Gemini API for software engineering tasks.
The code was migrated from a previous git repository as a single squashed commit.
Core Features & Components:
* **Gemini Integration:** Leverages the `@google/genai` SDK to interact with the Gemini models, supporting chat history, streaming responses, and function calling (tools).
* **Terminal UI:** Built with Ink (React for CLIs) providing an interactive chat interface within the terminal, including input prompts, message display, loading indicators, and tool interaction elements.
* **Tooling Framework:** Implements a robust tool system allowing Gemini to interact with the local environment. Includes tools for:
* File system listing (`ls`)
* File reading (`read-file`)
* Content searching (`grep`)
* File globbing (`glob`)
* File editing (`edit`)
* File writing (`write-file`)
* Executing bash commands (`terminal`)
* **State Management:** Handles the streaming state of Gemini responses and manages the conversation history.
* **Configuration:** Parses command-line arguments (`yargs`) and loads environment variables (`dotenv`) for setup.
* **Project Structure:** Organized into `core`, `ui`, `tools`, `config`, and `utils` directories using TypeScript. Includes basic build (`tsc`) and start scripts.
This initial version establishes the foundation for a powerful CLI tool enabling developers to use Gemini for coding assistance directly in their terminal environment.
---
Created by yours truly: __Gemini Code__
2025-04-15 21:41:08 -07:00
}
2025-08-26 12:01:31 -06:00
function isToolExecuting ( pendingHistoryItems : HistoryItemWithoutId [ ] ) {
return pendingHistoryItems . some ( ( item ) = > {
if ( item && item . type === 'tool_group' ) {
return item . tools . some (
( tool ) = > ToolCallStatus . Executing === tool . status ,
) ;
}
return false ;
} ) ;
}
2025-08-15 10:54:00 -07:00
export const AppWrapper = ( props : AppProps ) = > {
const kittyProtocolStatus = useKittyKeyboardProtocol ( ) ;
return (
< KeypressProvider
kittyProtocolEnabled = { kittyProtocolStatus . enabled }
config = { props . config }
2025-08-27 18:39:45 -07:00
debugKeystrokeLogging = {
props . settings . merged . general ? . debugKeystrokeLogging
}
2025-08-15 10:54:00 -07:00
>
< SessionStatsProvider >
2025-08-20 12:49:15 -07:00
< VimModeProvider settings = { props . settings } >
2025-08-15 10:54:00 -07:00
< App { ...props } / >
< / VimModeProvider >
< / SessionStatsProvider >
< / KeypressProvider >
) ;
} ;
2025-06-08 18:01:02 -04:00
2025-08-20 12:49:15 -07:00
const App = ( { config , settings , startupWarnings = [ ] , version } : AppProps ) = > {
2025-07-17 20:45:42 -04:00
const isFocused = useFocus ( ) ;
2025-06-27 10:57:32 -07:00
useBracketedPaste ( ) ;
2025-07-28 17:56:52 -07:00
const [ updateInfo , setUpdateInfo ] = useState < UpdateObject | null > ( null ) ;
2025-06-21 11:11:42 -07:00
const { stdout } = useStdout ( ) ;
2025-07-28 11:13:46 -07:00
const nightly = version . includes ( 'nightly' ) ;
2025-07-28 17:56:52 -07:00
const { history , addItem , clearItems , loadHistory } = useHistory ( ) ;
2025-06-17 08:24:07 -07:00
2025-08-06 15:47:58 -04:00
const [ idePromptAnswered , setIdePromptAnswered ] = useState ( false ) ;
2025-09-04 09:32:09 -07:00
const [ currentIDE , setCurrentIDE ] = useState < DetectedIde | undefined > ( ) ;
2025-08-08 15:38:30 +00:00
useEffect ( ( ) = > {
2025-09-04 09:32:09 -07:00
( async ( ) = > {
const ideClient = await IdeClient . getInstance ( ) ;
setCurrentIDE ( ideClient . getCurrentIde ( ) ) ;
} ) ( ) ;
registerCleanup ( async ( ) = > {
const ideClient = await IdeClient . getInstance ( ) ;
ideClient . disconnect ( ) ;
} ) ;
2025-08-08 15:38:30 +00:00
} , [ config ] ) ;
2025-09-04 09:32:09 -07:00
2025-08-06 15:47:58 -04:00
const shouldShowIdePrompt =
currentIDE &&
! config . getIdeMode ( ) &&
2025-08-27 18:39:45 -07:00
! settings . merged . ide ? . hasSeenNudge &&
2025-08-06 15:47:58 -04:00
! idePromptAnswered ;
2025-06-17 08:24:07 -07:00
useEffect ( ( ) = > {
2025-07-28 17:56:52 -07:00
const cleanup = setUpdateHandler ( addItem , setUpdateInfo ) ;
return cleanup ;
} , [ addItem ] ) ;
2025-06-17 08:24:07 -07:00
2025-05-23 22:51:47 -07:00
const {
consoleMessages ,
handleNewMessage ,
clearConsoleMessages : clearConsoleMessagesState ,
} = useConsoleMessages ( ) ;
2025-07-12 15:42:47 -07:00
useEffect ( ( ) = > {
const consolePatcher = new ConsolePatcher ( {
onNewMessage : handleNewMessage ,
debugMode : config.getDebugMode ( ) ,
} ) ;
consolePatcher . patch ( ) ;
registerCleanup ( consolePatcher . cleanup ) ;
} , [ handleNewMessage , config ] ) ;
2025-06-15 11:15:53 -07:00
const { stats : sessionStats } = useSessionStats ( ) ;
2025-05-15 00:19:41 -07:00
const [ staticNeedsRefresh , setStaticNeedsRefresh ] = useState ( false ) ;
2025-05-13 23:55:49 +00:00
const [ staticKey , setStaticKey ] = useState ( 0 ) ;
const refreshStatic = useCallback ( ( ) = > {
2025-06-21 11:11:42 -07:00
stdout . write ( ansiEscapes . clearTerminal ) ;
2025-05-13 23:55:49 +00:00
setStaticKey ( ( prev ) = > prev + 1 ) ;
2025-06-21 11:11:42 -07:00
} , [ setStaticKey , stdout ] ) ;
2025-05-13 23:55:49 +00:00
2025-05-21 13:31:18 -07:00
const [ geminiMdFileCount , setGeminiMdFileCount ] = useState < number > ( 0 ) ;
2025-05-13 23:55:49 +00:00
const [ debugMessage , setDebugMessage ] = useState < string > ( '' ) ;
2025-05-08 20:56:46 -07:00
const [ themeError , setThemeError ] = useState < string | null > ( null ) ;
2025-06-19 16:52:22 -07:00
const [ authError , setAuthError ] = useState < string | null > ( null ) ;
2025-06-12 02:21:54 +01:00
const [ editorError , setEditorError ] = useState < string | null > ( null ) ;
2025-05-15 22:56:03 -07:00
const [ footerHeight , setFooterHeight ] = useState < number > ( 0 ) ;
2025-05-17 21:57:27 -07:00
const [ corgiMode , setCorgiMode ] = useState ( false ) ;
2025-08-14 11:15:48 -07:00
const [ isTrustedFolderState , setIsTrustedFolder ] = useState (
2025-08-28 19:41:33 -07:00
isWorkspaceTrusted ( settings . merged ) ,
2025-08-14 11:15:48 -07:00
) ;
2025-06-24 18:48:55 -04:00
const [ currentModel , setCurrentModel ] = useState ( config . getModel ( ) ) ;
2025-05-18 01:18:32 -07:00
const [ shellModeActive , setShellModeActive ] = useState ( false ) ;
2025-05-22 10:36:44 -07:00
const [ showErrorDetails , setShowErrorDetails ] = useState < boolean > ( false ) ;
2025-06-07 18:30:56 -04:00
const [ showToolDescriptions , setShowToolDescriptions ] =
useState < boolean > ( false ) ;
2025-08-08 17:26:11 -04:00
2025-05-30 19:36:52 -07:00
const [ ctrlCPressedOnce , setCtrlCPressedOnce ] = useState ( false ) ;
2025-06-11 20:08:32 -04:00
const [ quittingMessages , setQuittingMessages ] = useState <
HistoryItem [ ] | null
> ( null ) ;
2025-05-30 19:36:52 -07:00
const ctrlCTimerRef = useRef < NodeJS.Timeout | null > ( null ) ;
2025-06-13 09:59:09 -07:00
const [ ctrlDPressedOnce , setCtrlDPressedOnce ] = useState ( false ) ;
const ctrlDTimerRef = useRef < NodeJS.Timeout | null > ( null ) ;
2025-06-19 20:17:23 +00:00
const [ constrainHeight , setConstrainHeight ] = useState < boolean > ( true ) ;
2025-06-27 12:07:38 -07:00
const [ showPrivacyNotice , setShowPrivacyNotice ] = useState < boolean > ( false ) ;
2025-07-09 13:55:56 -04:00
const [ modelSwitchedFromQuotaError , setModelSwitchedFromQuotaError ] =
useState < boolean > ( false ) ;
2025-07-11 11:25:30 -04:00
const [ userTier , setUserTier ] = useState < UserTierId | undefined > ( undefined ) ;
2025-07-28 11:03:22 -04:00
const [ ideContextState , setIdeContextState ] = useState <
IdeContext | undefined
> ( ) ;
2025-08-10 06:26:43 +08:00
const [ showEscapePrompt , setShowEscapePrompt ] = useState ( false ) ;
2025-09-03 11:44:26 -07:00
const [ showIdeRestartPrompt , setShowIdeRestartPrompt ] = useState ( false ) ;
2025-07-27 02:00:26 -04:00
const [ isProcessing , setIsProcessing ] = useState < boolean > ( false ) ;
2025-08-29 04:19:43 +05:30
2025-08-27 00:43:02 +00:00
const {
showWorkspaceMigrationDialog ,
workspaceExtensions ,
onWorkspaceMigrationDialogOpen ,
onWorkspaceMigrationDialogClose ,
} = useWorkspaceMigration ( settings ) ;
2025-07-18 18:14:46 -04:00
2025-08-29 04:19:43 +05:30
const [ isProQuotaDialogOpen , setIsProQuotaDialogOpen ] = useState ( false ) ;
const [ proQuotaDialogResolver , setProQuotaDialogResolver ] = useState <
( ( value : boolean ) = > void ) | null
> ( null ) ;
2025-07-18 18:14:46 -04:00
useEffect ( ( ) = > {
2025-07-28 11:03:22 -04:00
const unsubscribe = ideContext . subscribeToIdeContext ( setIdeContextState ) ;
2025-07-18 18:14:46 -04:00
// Set the initial value
2025-07-28 11:03:22 -04:00
setIdeContextState ( ideContext . getIdeContext ( ) ) ;
2025-07-18 18:14:46 -04:00
return unsubscribe ;
} , [ ] ) ;
2025-06-27 12:07:38 -07:00
2025-07-25 17:35:26 -07:00
useEffect ( ( ) = > {
const openDebugConsole = ( ) = > {
setShowErrorDetails ( true ) ;
setConstrainHeight ( false ) ; // Make sure the user sees the full message.
} ;
appEvents . on ( AppEvent . OpenDebugConsole , openDebugConsole ) ;
const logErrorHandler = ( errorMessage : unknown ) = > {
handleNewMessage ( {
type : 'error' ,
content : String ( errorMessage ) ,
count : 1 ,
} ) ;
} ;
appEvents . on ( AppEvent . LogError , logErrorHandler ) ;
return ( ) = > {
appEvents . off ( AppEvent . OpenDebugConsole , openDebugConsole ) ;
appEvents . off ( AppEvent . LogError , logErrorHandler ) ;
} ;
} , [ handleNewMessage ] ) ;
2025-06-27 12:07:38 -07:00
const openPrivacyNotice = useCallback ( ( ) = > {
setShowPrivacyNotice ( true ) ;
} , [ ] ) ;
2025-08-10 06:26:43 +08:00
const handleEscapePromptChange = useCallback ( ( showPrompt : boolean ) = > {
setShowEscapePrompt ( showPrompt ) ;
} , [ ] ) ;
2025-07-11 16:52:56 -07:00
const initialPromptSubmitted = useRef ( false ) ;
2025-05-22 10:36:44 -07:00
const errorCount = useMemo (
2025-07-25 17:35:26 -07:00
( ) = >
consoleMessages
. filter ( ( msg ) = > msg . type === 'error' )
. reduce ( ( total , msg ) = > total + msg . count , 0 ) ,
2025-05-22 10:36:44 -07:00
[ consoleMessages ] ,
) ;
2025-05-30 19:36:52 -07:00
2025-04-30 22:26:28 +00:00
const {
isThemeDialogOpen ,
openThemeDialog ,
handleThemeSelect ,
handleThemeHighlight ,
2025-08-20 12:49:15 -07:00
} = useThemeCommand ( settings , setThemeError , addItem ) ;
2025-05-14 12:37:17 -07:00
2025-08-10 09:04:52 +09:00
const { isSettingsDialogOpen , openSettingsDialog , closeSettingsDialog } =
useSettingsCommand ( ) ;
2025-08-21 00:38:12 -07:00
const { isFolderTrustDialogOpen , handleFolderTrustSelect , isRestarting } =
useFolderTrust ( settings , setIsTrustedFolder ) ;
2025-08-08 11:02:27 -07:00
2025-09-04 09:32:09 -07:00
const { needsRestart : ideNeedsRestart } = useIdeTrustListener ( ) ;
2025-09-03 11:44:26 -07:00
useEffect ( ( ) = > {
if ( ideNeedsRestart ) {
// IDE trust changed, force a restart.
setShowIdeRestartPrompt ( true ) ;
}
} , [ ideNeedsRestart ] ) ;
useKeypress (
( key ) = > {
if ( key . name === 'r' || key . name === 'R' ) {
process . exit ( 0 ) ;
}
} ,
{ isActive : showIdeRestartPrompt } ,
) ;
2025-06-19 16:52:22 -07:00
const {
isAuthDialogOpen ,
openAuthDialog ,
handleAuthSelect ,
2025-06-20 10:46:41 -07:00
isAuthenticating ,
cancelAuthentication ,
2025-06-19 16:52:22 -07:00
} = useAuthCommand ( settings , setAuthError , config ) ;
useEffect ( ( ) = > {
2025-08-27 18:39:45 -07:00
if (
2025-09-03 15:33:37 -07:00
settings . merged . security ? . auth ? . enforcedType &&
settings . merged . security ? . auth . selectedType &&
settings . merged . security ? . auth . enforcedType !==
settings . merged . security ? . auth . selectedType
) {
setAuthError (
` Authentication is enforced to be ${ settings . merged . security ? . auth . enforcedType } , but you are currently using ${ settings . merged . security ? . auth . selectedType } . ` ,
) ;
openAuthDialog ( ) ;
} else if (
2025-08-27 18:39:45 -07:00
settings . merged . security ? . auth ? . selectedType &&
! settings . merged . security ? . auth ? . useExternal
) {
const error = validateAuthMethod (
settings . merged . security . auth . selectedType ,
) ;
2025-06-19 16:52:22 -07:00
if ( error ) {
setAuthError ( error ) ;
openAuthDialog ( ) ;
}
}
2025-08-01 11:49:03 -07:00
} , [
2025-08-27 18:39:45 -07:00
settings . merged . security ? . auth ? . selectedType ,
2025-09-03 15:33:37 -07:00
settings . merged . security ? . auth ? . enforcedType ,
2025-08-27 18:39:45 -07:00
settings . merged . security ? . auth ? . useExternal ,
2025-08-01 11:49:03 -07:00
openAuthDialog ,
setAuthError ,
] ) ;
2025-06-19 16:52:22 -07:00
2025-07-11 11:25:30 -04:00
// Sync user tier from config when authentication changes
useEffect ( ( ) = > {
// Only sync when not currently authenticating
if ( ! isAuthenticating ) {
2025-07-21 13:44:43 -07:00
setUserTier ( config . getGeminiClient ( ) ? . getUserTier ( ) ) ;
2025-07-11 11:25:30 -04:00
}
2025-07-21 13:44:43 -07:00
} , [ config , isAuthenticating ] ) ;
2025-07-11 11:25:30 -04:00
2025-06-12 02:21:54 +01:00
const {
isEditorDialogOpen ,
openEditorDialog ,
handleEditorSelect ,
exitEditorDialog ,
2025-08-20 12:49:15 -07:00
} = useEditorSettings ( settings , setEditorError , addItem ) ;
2025-06-12 02:21:54 +01:00
2025-06-07 18:30:56 -04:00
const toggleCorgiMode = useCallback ( ( ) = > {
setCorgiMode ( ( prev ) = > ! prev ) ;
} , [ ] ) ;
2025-05-14 15:19:45 -07:00
2025-05-14 12:37:17 -07:00
const performMemoryRefresh = useCallback ( async ( ) = > {
addItem (
{
type : MessageType . INFO ,
2025-06-21 12:15:43 -07:00
text : 'Refreshing hierarchical memory (GEMINI.md or other context files)...' ,
2025-05-14 12:37:17 -07:00
} ,
Date . now ( ) ,
) ;
try {
2025-05-14 15:19:45 -07:00
const { memoryContent , fileCount } = await loadHierarchicalGeminiMemory (
2025-05-14 12:37:17 -07:00
process . cwd ( ) ,
2025-08-27 18:39:45 -07:00
settings . merged . context ? . loadMemoryFromIncludeDirectories
2025-08-06 02:01:01 +09:00
? config . getWorkspaceContext ( ) . getDirectories ( )
: [ ] ,
2025-05-14 12:37:17 -07:00
config . getDebugMode ( ) ,
2025-06-14 10:25:34 -04:00
config . getFileService ( ) ,
2025-07-23 14:48:35 -07:00
settings . merged ,
2025-06-22 16:17:05 -07:00
config . getExtensionContextFilePaths ( ) ,
2025-08-29 14:12:36 -04:00
config . getFolderTrust ( ) ,
2025-08-27 18:39:45 -07:00
settings . merged . context ? . importFormat || 'tree' , // Use setting or default to 'tree'
2025-07-20 00:55:33 -07:00
config . getFileFilteringOptions ( ) ,
2025-05-14 12:37:17 -07:00
) ;
2025-07-20 00:55:33 -07:00
2025-05-14 15:19:45 -07:00
config . setUserMemory ( memoryContent ) ;
config . setGeminiMdFileCount ( fileCount ) ;
setGeminiMdFileCount ( fileCount ) ;
2025-05-14 12:37:17 -07:00
addItem (
{
type : MessageType . INFO ,
2025-05-14 15:19:45 -07:00
text : ` Memory refreshed successfully. ${ memoryContent . length > 0 ? ` Loaded ${ memoryContent . length } characters from ${ fileCount } file(s). ` : 'No memory content found.' } ` ,
2025-05-14 12:37:17 -07:00
} ,
Date . now ( ) ,
) ;
if ( config . getDebugMode ( ) ) {
console . log (
2025-05-14 15:19:45 -07:00
` [DEBUG] Refreshed memory content in config: ${ memoryContent . substring ( 0 , 200 ) } ... ` ,
2025-05-14 12:37:17 -07:00
) ;
}
} catch ( error ) {
const errorMessage = getErrorMessage ( error ) ;
addItem (
{
type : MessageType . ERROR ,
text : ` Error refreshing memory: ${ errorMessage } ` ,
} ,
Date . now ( ) ,
) ;
console . error ( 'Error refreshing memory:' , error ) ;
}
2025-07-23 14:48:35 -07:00
} , [ config , addItem , settings . merged ] ) ;
2025-05-14 12:37:17 -07:00
2025-06-24 18:48:55 -04:00
// Watch for model changes (e.g., from Flash fallback)
useEffect ( ( ) = > {
const checkModelChange = ( ) = > {
const configModel = config . getModel ( ) ;
if ( configModel !== currentModel ) {
setCurrentModel ( configModel ) ;
}
} ;
// Check immediately and then periodically
checkModelChange ( ) ;
const interval = setInterval ( checkModelChange , 1000 ) ; // Check every second
return ( ) = > clearInterval ( interval ) ;
} , [ config , currentModel ] ) ;
// Set up Flash fallback handler
useEffect ( ( ) = > {
const flashFallbackHandler = async (
currentModel : string ,
fallbackModel : string ,
2025-07-09 10:18:15 -04:00
error? : unknown ,
2025-06-24 18:48:55 -04:00
) : Promise < boolean > = > {
2025-08-29 04:19:43 +05:30
// Check if we've already switched to the fallback model
if ( config . isInFallbackMode ( ) ) {
// If we're already in fallback mode, don't show the dialog again
return false ;
}
2025-07-09 10:18:15 -04:00
let message : string ;
2025-07-15 21:13:30 -07:00
if (
config . getContentGeneratorConfig ( ) . authType ===
AuthType . LOGIN_WITH_GOOGLE
) {
2025-07-21 17:54:44 -04:00
// Use actual user tier if available; otherwise, default to FREE tier behavior (safe default)
2025-07-15 21:13:30 -07:00
const isPaidTier =
userTier === UserTierId . LEGACY || userTier === UserTierId . STANDARD ;
2025-07-09 13:55:56 -04:00
2025-07-15 21:13:30 -07:00
// Check if this is a Pro quota exceeded error
if ( error && isProQuotaExceededError ( error ) ) {
if ( isPaidTier ) {
message = ` ⚡ You have reached your daily ${ currentModel } quota limit.
2025-08-29 04:19:43 +05:30
⚡ You can choose to authenticate with a paid API key or continue with the fallback model .
2025-07-09 13:55:56 -04:00
⚡ To continue accessing the $ { currentModel } model today , consider using / auth to switch to using a paid API key from AI Studio at https : //aistudio.google.com/apikey`;
2025-07-15 21:13:30 -07:00
} else {
message = ` ⚡ You have reached your daily ${ currentModel } quota limit.
2025-08-29 04:19:43 +05:30
⚡ You can choose to authenticate with a paid API key or continue with the fallback model .
2025-07-09 10:18:15 -04:00
⚡ To increase your limits , upgrade to a Gemini Code Assist Standard or Enterprise plan with higher limits at https : //goo.gle/set-up-gemini-code-assist
⚡ Or you can utilize a Gemini API Key . See : https : //goo.gle/gemini-cli-docs-auth#gemini-api-key
⚡ You can switch authentication methods by typing / auth ` ;
2025-07-15 21:13:30 -07:00
}
} else if ( error && isGenericQuotaExceededError ( error ) ) {
if ( isPaidTier ) {
message = ` ⚡ You have reached your daily quota limit.
2025-07-09 13:55:56 -04:00
⚡ Automatically switching from $ { currentModel } to $ { fallbackModel } for the remainder of this session .
⚡ To continue accessing the $ { currentModel } model today , consider using / auth to switch to using a paid API key from AI Studio at https : //aistudio.google.com/apikey`;
2025-07-15 21:13:30 -07:00
} else {
message = ` ⚡ You have reached your daily quota limit.
2025-07-09 10:18:15 -04:00
⚡ Automatically switching from $ { currentModel } to $ { fallbackModel } for the remainder of this session .
⚡ To increase your limits , upgrade to a Gemini Code Assist Standard or Enterprise plan with higher limits at https : //goo.gle/set-up-gemini-code-assist
⚡ Or you can utilize a Gemini API Key . See : https : //goo.gle/gemini-cli-docs-auth#gemini-api-key
⚡ You can switch authentication methods by typing / auth ` ;
2025-07-15 21:13:30 -07:00
}
} else {
if ( isPaidTier ) {
// Default fallback message for other cases (like consecutive 429s)
message = ` ⚡ Automatically switching from ${ currentModel } to ${ fallbackModel } for faster responses for the remainder of this session.
2025-07-09 13:55:56 -04:00
⚡ Possible reasons for this are that you have received multiple consecutive capacity errors or you have reached your daily $ { currentModel } quota limit
⚡ To continue accessing the $ { currentModel } model today , consider using / auth to switch to using a paid API key from AI Studio at https : //aistudio.google.com/apikey`;
2025-07-15 21:13:30 -07:00
} else {
// Default fallback message for other cases (like consecutive 429s)
message = ` ⚡ Automatically switching from ${ currentModel } to ${ fallbackModel } for faster responses for the remainder of this session.
2025-07-09 13:55:56 -04:00
⚡ Possible reasons for this are that you have received multiple consecutive capacity errors or you have reached your daily $ { currentModel } quota limit
⚡ To increase your limits , upgrade to a Gemini Code Assist Standard or Enterprise plan with higher limits at https : //goo.gle/set-up-gemini-code-assist
⚡ Or you can utilize a Gemini API Key . See : https : //goo.gle/gemini-cli-docs-auth#gemini-api-key
⚡ You can switch authentication methods by typing / auth ` ;
2025-07-15 21:13:30 -07:00
}
2025-07-09 13:55:56 -04:00
}
2025-07-09 10:18:15 -04:00
2025-07-15 21:13:30 -07:00
// Add message to UI history
addItem (
{
type : MessageType . INFO ,
text : message ,
} ,
Date . now ( ) ,
) ;
2025-08-29 04:19:43 +05:30
// For Pro quota errors, show the dialog and wait for user's choice
if ( error && isProQuotaExceededError ( error ) ) {
// Set the flag to prevent tool continuation
setModelSwitchedFromQuotaError ( true ) ;
// Set global quota error flag to prevent Flash model calls
config . setQuotaErrorOccurred ( true ) ;
// Show the ProQuotaDialog and wait for user's choice
const shouldContinueWithFallback = await new Promise < boolean > (
( resolve ) = > {
setIsProQuotaDialogOpen ( true ) ;
setProQuotaDialogResolver ( ( ) = > resolve ) ;
} ,
) ;
// If user chose to continue with fallback, we don't need to stop the current prompt
if ( shouldContinueWithFallback ) {
// Switch to fallback model for future use
config . setModel ( fallbackModel ) ;
config . setFallbackMode ( true ) ;
logFlashFallback (
config ,
new FlashFallbackEvent (
config . getContentGeneratorConfig ( ) . authType ! ,
) ,
) ;
return true ; // Continue with current prompt using fallback model
}
// If user chose to authenticate, stop current prompt
return false ;
}
// For other quota errors, automatically switch to fallback model
2025-07-15 21:13:30 -07:00
// Set the flag to prevent tool continuation
setModelSwitchedFromQuotaError ( true ) ;
// Set global quota error flag to prevent Flash model calls
config . setQuotaErrorOccurred ( true ) ;
}
2025-07-09 13:55:56 -04:00
// Switch model for future use but return false to stop current retry
config . setModel ( fallbackModel ) ;
2025-07-28 15:55:50 -04:00
config . setFallbackMode ( true ) ;
2025-07-12 02:40:25 +05:30
logFlashFallback (
config ,
new FlashFallbackEvent ( config . getContentGeneratorConfig ( ) . authType ! ) ,
) ;
2025-07-09 13:55:56 -04:00
return false ; // Don't continue with current prompt
2025-06-24 18:48:55 -04:00
} ;
config . setFlashFallbackHandler ( flashFallbackHandler ) ;
2025-07-11 11:25:30 -04:00
} , [ config , addItem , userTier ] ) ;
2025-06-24 18:48:55 -04:00
2025-07-25 15:36:42 -07:00
// Terminal and UI setup
const { rows : terminalHeight , columns : terminalWidth } = useTerminalSize ( ) ;
2025-08-07 15:55:53 -07:00
const isNarrow = isNarrowWidth ( terminalWidth ) ;
2025-07-25 15:36:42 -07:00
const { stdin , setRawMode } = useStdin ( ) ;
const isInitialMount = useRef ( true ) ;
const widthFraction = 0.9 ;
const inputWidth = Math . max (
20 ,
Math . floor ( terminalWidth * widthFraction ) - 3 ,
) ;
2025-08-07 15:55:53 -07:00
const suggestionsWidth = Math . max ( 20 , Math . floor ( terminalWidth * 0.8 ) ) ;
2025-07-25 15:36:42 -07:00
// Utility callbacks
const isValidPath = useCallback ( ( filePath : string ) : boolean = > {
try {
return fs . existsSync ( filePath ) && fs . statSync ( filePath ) . isFile ( ) ;
} catch ( _e ) {
return false ;
}
} , [ ] ) ;
const getPreferredEditor = useCallback ( ( ) = > {
2025-08-27 18:39:45 -07:00
const editorType = settings . merged . general ? . preferredEditor ;
2025-07-25 15:36:42 -07:00
const isValidEditor = isEditorAvailable ( editorType ) ;
if ( ! isValidEditor ) {
openEditorDialog ( ) ;
return ;
}
return editorType as EditorType ;
} , [ settings , openEditorDialog ] ) ;
const onAuthError = useCallback ( ( ) = > {
setAuthError ( 'reauth required' ) ;
openAuthDialog ( ) ;
} , [ openAuthDialog , setAuthError ] ) ;
// Core hooks and processors
const {
vimEnabled : vimModeEnabled ,
vimMode ,
toggleVimEnabled ,
} = useVimMode ( ) ;
2025-06-13 21:21:40 -07:00
const {
handleSlashCommand ,
slashCommands ,
pendingHistoryItems : pendingSlashCommandHistoryItems ,
2025-07-07 16:45:44 -04:00
commandContext ,
2025-07-27 02:00:26 -04:00
shellConfirmationRequest ,
2025-08-09 15:59:22 +09:00
confirmationRequest ,
2025-06-13 21:21:40 -07:00
} = useSlashCommandProcessor (
2025-05-20 16:50:32 -07:00
config ,
2025-06-21 12:15:43 -07:00
settings ,
2025-05-06 16:20:28 -07:00
addItem ,
clearItems ,
2025-06-11 15:33:09 -04:00
loadHistory ,
2025-05-05 20:48:34 +00:00
refreshStatic ,
2025-05-13 23:55:49 +00:00
setDebugMessage ,
2025-05-05 20:48:34 +00:00
openThemeDialog ,
2025-06-19 16:52:22 -07:00
openAuthDialog ,
2025-06-12 02:21:54 +01:00
openEditorDialog ,
2025-05-17 21:57:27 -07:00
toggleCorgiMode ,
2025-06-11 20:08:32 -04:00
setQuittingMessages ,
2025-06-27 12:07:38 -07:00
openPrivacyNotice ,
2025-08-10 09:04:52 +09:00
openSettingsDialog ,
2025-07-25 15:36:42 -07:00
toggleVimEnabled ,
2025-07-27 02:00:26 -04:00
setIsProcessing ,
2025-08-06 02:01:01 +09:00
setGeminiMdFileCount ,
2025-06-07 18:30:56 -04:00
) ;
2025-08-06 15:19:10 -04:00
const buffer = useTextBuffer ( {
initialText : '' ,
viewport : { height : 10 , width : inputWidth } ,
stdin ,
setRawMode ,
isValidPath ,
shellModeActive ,
} ) ;
2025-08-30 03:18:22 +09:00
// Independent input history management (unaffected by /clear)
const inputHistoryStore = useInputHistoryStore ( ) ;
2025-08-06 15:19:10 -04:00
2025-08-19 09:25:16 -07:00
// Stable reference for cancel handler to avoid circular dependency
const cancelHandlerRef = useRef < ( ) = > void > ( ( ) = > { } ) ;
2025-08-06 15:19:10 -04:00
2025-07-25 15:36:42 -07:00
const {
streamingState ,
submitQuery ,
initError ,
pendingHistoryItems : pendingGeminiHistoryItems ,
thought ,
2025-08-12 09:43:57 +05:30
cancelOngoingRequest ,
2025-07-25 15:36:42 -07:00
} = useGeminiStream (
config . getGeminiClient ( ) ,
history ,
addItem ,
config ,
2025-08-28 16:42:54 -07:00
settings ,
2025-07-25 15:36:42 -07:00
setDebugMessage ,
handleSlashCommand ,
shellModeActive ,
getPreferredEditor ,
onAuthError ,
performMemoryRefresh ,
modelSwitchedFromQuotaError ,
setModelSwitchedFromQuotaError ,
2025-08-05 14:55:54 -07:00
refreshStatic ,
2025-08-19 09:25:16 -07:00
( ) = > cancelHandlerRef . current ( ) ,
2025-07-25 15:36:42 -07:00
) ;
2025-06-13 09:59:09 -07:00
2025-08-26 12:01:31 -06:00
const pendingHistoryItems = useMemo (
( ) = > [ . . . pendingSlashCommandHistoryItems , . . . pendingGeminiHistoryItems ] ,
[ pendingSlashCommandHistoryItems , pendingGeminiHistoryItems ] ,
) ;
2025-08-19 09:25:16 -07:00
// Message queue for handling input during streaming
const { messageQueue , addMessage , clearQueue , getQueuedMessagesText } =
useMessageQueue ( {
streamingState ,
submitQuery ,
} ) ;
// Update the cancel handler with message queue support
cancelHandlerRef . current = useCallback ( ( ) = > {
2025-08-26 12:01:31 -06:00
if ( isToolExecuting ( pendingHistoryItems ) ) {
buffer . setText ( '' ) ; // Just clear the prompt
return ;
}
2025-08-30 03:18:22 +09:00
const lastUserMessage = inputHistoryStore . inputHistory . at ( - 1 ) ;
2025-08-19 09:25:16 -07:00
let textToSet = lastUserMessage || '' ;
// Append queued messages if any exist
const queuedText = getQueuedMessagesText ( ) ;
if ( queuedText ) {
textToSet = textToSet ? ` ${ textToSet } \ n \ n ${ queuedText } ` : queuedText ;
clearQueue ( ) ;
}
if ( textToSet ) {
buffer . setText ( textToSet ) ;
}
2025-08-26 12:01:31 -06:00
} , [
buffer ,
2025-08-30 03:18:22 +09:00
inputHistoryStore . inputHistory ,
2025-08-26 12:01:31 -06:00
getQueuedMessagesText ,
clearQueue ,
pendingHistoryItems ,
] ) ;
2025-08-19 09:25:16 -07:00
// Input handling - queue messages for processing
2025-07-25 15:36:42 -07:00
const handleFinalSubmit = useCallback (
( submittedValue : string ) = > {
2025-08-30 03:18:22 +09:00
const trimmedValue = submittedValue . trim ( ) ;
if ( trimmedValue . length > 0 ) {
// Add to independent input history
inputHistoryStore . addInput ( trimmedValue ) ;
}
// Always add to message queue
2025-08-19 09:25:16 -07:00
addMessage ( submittedValue ) ;
2025-07-25 15:36:42 -07:00
} ,
2025-08-30 03:18:22 +09:00
[ addMessage , inputHistoryStore ] ,
2025-06-13 09:59:09 -07:00
) ;
2025-08-06 15:47:58 -04:00
const handleIdePromptComplete = useCallback (
( result : IdeIntegrationNudgeResult ) = > {
2025-08-12 20:08:47 +00:00
if ( result . userSelection === 'yes' ) {
if ( result . isExtensionPreInstalled ) {
handleSlashCommand ( '/ide enable' ) ;
} else {
handleSlashCommand ( '/ide install' ) ;
}
2025-08-06 15:47:58 -04:00
settings . setValue (
SettingScope . User ,
'hasSeenIdeIntegrationNudge' ,
true ,
) ;
2025-08-12 20:08:47 +00:00
} else if ( result . userSelection === 'dismiss' ) {
2025-08-06 15:47:58 -04:00
settings . setValue (
SettingScope . User ,
'hasSeenIdeIntegrationNudge' ,
true ,
) ;
}
setIdePromptAnswered ( true ) ;
} ,
[ handleSlashCommand , settings ] ,
) ;
2025-07-25 15:36:42 -07:00
const { handleInput : vimHandleInput } = useVim ( buffer , handleFinalSubmit ) ;
2025-09-03 22:09:04 +05:30
const { elapsedTime , currentLoadingPhrase } = useLoadingIndicator (
streamingState ,
settings . merged . ui ? . customWittyPhrases ,
) ;
2025-08-25 17:30:04 -07:00
const showAutoAcceptIndicator = useAutoAcceptIndicator ( { config , addItem } ) ;
2025-07-25 15:36:42 -07:00
2025-06-13 09:59:09 -07:00
const handleExit = useCallback (
(
pressedOnce : boolean ,
setPressedOnce : ( value : boolean ) = > void ,
2025-08-19 09:25:16 -07:00
timerRef : ReturnType < typeof useRef < NodeJS.Timeout | null > > ,
2025-06-13 09:59:09 -07:00
) = > {
if ( pressedOnce ) {
if ( timerRef . current ) {
clearTimeout ( timerRef . current ) ;
}
2025-07-17 12:07:10 -04:00
// Directly invoke the central command handler.
handleSlashCommand ( '/quit' ) ;
2025-06-13 09:59:09 -07:00
} else {
setPressedOnce ( true ) ;
timerRef . current = setTimeout ( ( ) = > {
setPressedOnce ( false ) ;
timerRef . current = null ;
} , CTRL_EXIT_PROMPT_DURATION_MS ) ;
}
} ,
2025-07-17 12:07:10 -04:00
[ handleSlashCommand ] ,
2025-06-13 09:59:09 -07:00
) ;
2025-08-09 16:03:17 +09:00
const handleGlobalKeypress = useCallback (
( key : Key ) = > {
2025-08-22 19:31:55 -04:00
// Debug log keystrokes if enabled
2025-08-27 18:39:45 -07:00
if ( settings . merged . general ? . debugKeystrokeLogging ) {
2025-08-22 19:31:55 -04:00
console . log ( '[DEBUG] Keystroke:' , JSON . stringify ( key ) ) ;
}
2025-08-09 16:03:17 +09:00
let enteringConstrainHeightMode = false ;
if ( ! constrainHeight ) {
enteringConstrainHeightMode = true ;
setConstrainHeight ( true ) ;
}
2025-06-22 20:43:36 +00:00
2025-08-09 16:03:17 +09:00
if ( keyMatchers [ Command . SHOW_ERROR_DETAILS ] ( key ) ) {
setShowErrorDetails ( ( prev ) = > ! prev ) ;
} else if ( keyMatchers [ Command . TOGGLE_TOOL_DESCRIPTIONS ] ( key ) ) {
const newValue = ! showToolDescriptions ;
setShowToolDescriptions ( newValue ) ;
2025-06-07 18:30:56 -04:00
2025-08-09 16:03:17 +09:00
const mcpServers = config . getMcpServers ( ) ;
if ( Object . keys ( mcpServers || { } ) . length > 0 ) {
handleSlashCommand ( newValue ? '/mcp desc' : '/mcp nodesc' ) ;
}
} else if (
keyMatchers [ Command . TOGGLE_IDE_CONTEXT_DETAIL ] ( key ) &&
config . getIdeMode ( ) &&
ideContextState
) {
// Show IDE status when in IDE mode and context is available.
handleSlashCommand ( '/ide status' ) ;
} else if ( keyMatchers [ Command . QUIT ] ( key ) ) {
// When authenticating, let AuthInProgress component handle Ctrl+C.
if ( isAuthenticating ) {
return ;
}
2025-08-12 09:43:57 +05:30
if ( ! ctrlCPressedOnce ) {
cancelOngoingRequest ? . ( ) ;
}
2025-08-09 16:03:17 +09:00
handleExit ( ctrlCPressedOnce , setCtrlCPressedOnce , ctrlCTimerRef ) ;
} else if ( keyMatchers [ Command . EXIT ] ( key ) ) {
if ( buffer . text . length > 0 ) {
return ;
}
handleExit ( ctrlDPressedOnce , setCtrlDPressedOnce , ctrlDTimerRef ) ;
} else if (
keyMatchers [ Command . SHOW_MORE_LINES ] ( key ) &&
! enteringConstrainHeightMode
) {
setConstrainHeight ( false ) ;
2025-06-07 18:30:56 -04:00
}
2025-08-09 16:03:17 +09:00
} ,
[
constrainHeight ,
setConstrainHeight ,
setShowErrorDetails ,
showToolDescriptions ,
setShowToolDescriptions ,
config ,
ideContextState ,
handleExit ,
ctrlCPressedOnce ,
setCtrlCPressedOnce ,
ctrlCTimerRef ,
buffer . text . length ,
ctrlDPressedOnce ,
setCtrlDPressedOnce ,
ctrlDTimerRef ,
handleSlashCommand ,
isAuthenticating ,
2025-08-12 09:43:57 +05:30
cancelOngoingRequest ,
2025-08-27 18:39:45 -07:00
settings . merged . general ? . debugKeystrokeLogging ,
2025-08-09 16:03:17 +09:00
] ,
) ;
2025-08-13 13:32:54 -04:00
useKeypress ( handleGlobalKeypress , {
isActive : true ,
} ) ;
2025-06-07 18:30:56 -04:00
useEffect ( ( ) = > {
if ( config ) {
setGeminiMdFileCount ( config . getGeminiMdFileCount ( ) ) ;
}
2025-08-06 02:01:01 +09:00
} , [ config , config . getGeminiMdFileCount ] ) ;
2025-06-07 18:30:56 -04:00
2025-08-20 10:55:47 +09:00
const logger = useLogger ( config . storage ) ;
2025-05-21 07:36:22 +00:00
2025-08-30 03:18:22 +09:00
// Initialize independent input history from logger
2025-05-21 07:36:22 +00:00
useEffect ( ( ) = > {
2025-08-30 03:18:22 +09:00
inputHistoryStore . initializeFromLogger ( logger ) ;
} , [ logger , inputHistoryStore ] ) ;
Initial commit of Gemini Code CLI
This commit introduces the initial codebase for the Gemini Code CLI, a command-line interface designed to facilitate interaction with the Gemini API for software engineering tasks.
The code was migrated from a previous git repository as a single squashed commit.
Core Features & Components:
* **Gemini Integration:** Leverages the `@google/genai` SDK to interact with the Gemini models, supporting chat history, streaming responses, and function calling (tools).
* **Terminal UI:** Built with Ink (React for CLIs) providing an interactive chat interface within the terminal, including input prompts, message display, loading indicators, and tool interaction elements.
* **Tooling Framework:** Implements a robust tool system allowing Gemini to interact with the local environment. Includes tools for:
* File system listing (`ls`)
* File reading (`read-file`)
* Content searching (`grep`)
* File globbing (`glob`)
* File editing (`edit`)
* File writing (`write-file`)
* Executing bash commands (`terminal`)
* **State Management:** Handles the streaming state of Gemini responses and manages the conversation history.
* **Configuration:** Parses command-line arguments (`yargs`) and loads environment variables (`dotenv`) for setup.
* **Project Structure:** Organized into `core`, `ui`, `tools`, `config`, and `utils` directories using TypeScript. Includes basic build (`tsc`) and start scripts.
This initial version establishes the foundation for a powerful CLI tool enabling developers to use Gemini for coding assistance directly in their terminal environment.
---
Created by yours truly: __Gemini Code__
2025-04-15 21:41:08 -07:00
2025-07-27 02:00:26 -04:00
const isInputActive =
2025-08-19 09:25:16 -07:00
( streamingState === StreamingState . Idle ||
streamingState === StreamingState . Responding ) &&
! initError &&
2025-08-29 04:19:43 +05:30
! isProcessing &&
! isProQuotaDialogOpen ;
2025-04-19 14:31:59 +01:00
2025-05-14 17:33:37 -07:00
const handleClearScreen = useCallback ( ( ) = > {
clearItems ( ) ;
2025-05-23 22:51:47 -07:00
clearConsoleMessagesState ( ) ;
2025-05-14 22:48:50 -07:00
console . clear ( ) ;
2025-05-14 17:33:37 -07:00
refreshStatic ( ) ;
2025-05-23 22:51:47 -07:00
} , [ clearItems , clearConsoleMessagesState , refreshStatic ] ) ;
2025-05-14 17:33:37 -07:00
2025-05-15 22:56:03 -07:00
const mainControlsRef = useRef < DOMElement > ( null ) ;
2025-05-15 00:19:41 -07:00
const pendingHistoryItemRef = useRef < DOMElement > ( null ) ;
useEffect ( ( ) = > {
2025-05-15 22:56:03 -07:00
if ( mainControlsRef . current ) {
const fullFooterMeasurement = measureElement ( mainControlsRef . current ) ;
setFooterHeight ( fullFooterMeasurement . height ) ;
}
2025-05-22 10:36:44 -07:00
} , [ terminalHeight , consoleMessages , showErrorDetails ] ) ;
2025-05-15 00:19:41 -07:00
2025-06-19 20:17:23 +00:00
const staticExtraHeight = /* margins and padding */ 3 ;
const availableTerminalHeight = useMemo (
( ) = > terminalHeight - footerHeight - staticExtraHeight ,
[ terminalHeight , footerHeight ] ,
) ;
2025-05-15 00:19:41 -07:00
2025-06-21 11:11:42 -07:00
useEffect ( ( ) = > {
// skip refreshing Static during first mount
if ( isInitialMount . current ) {
isInitialMount . current = false ;
return ;
}
// debounce so it doesn't fire up too often during resize
const handler = setTimeout ( ( ) = > {
2025-06-23 14:45:15 -07:00
setStaticNeedsRefresh ( false ) ;
refreshStatic ( ) ;
2025-06-21 11:11:42 -07:00
} , 300 ) ;
return ( ) = > {
clearTimeout ( handler ) ;
} ;
2025-06-23 14:45:15 -07:00
} , [ terminalWidth , terminalHeight , refreshStatic ] ) ;
2025-06-21 11:11:42 -07:00
2025-05-15 00:19:41 -07:00
useEffect ( ( ) = > {
if ( streamingState === StreamingState . Idle && staticNeedsRefresh ) {
setStaticNeedsRefresh ( false ) ;
refreshStatic ( ) ;
}
} , [ streamingState , refreshStatic , staticNeedsRefresh ] ) ;
2025-05-22 10:36:44 -07:00
const filteredConsoleMessages = useMemo ( ( ) = > {
if ( config . getDebugMode ( ) ) {
return consoleMessages ;
}
return consoleMessages . filter ( ( msg ) = > msg . type !== 'debug' ) ;
} , [ consoleMessages , config ] ) ;
2025-05-28 23:30:05 +00:00
const branchName = useGitBranchName ( config . getTargetDir ( ) ) ;
2025-06-13 09:19:08 -07:00
const contextFileNames = useMemo ( ( ) = > {
2025-08-27 18:39:45 -07:00
const fromSettings = settings . merged . context ? . fileName ;
2025-06-13 09:19:08 -07:00
if ( fromSettings ) {
return Array . isArray ( fromSettings ) ? fromSettings : [ fromSettings ] ;
}
return getAllGeminiMdFilenames ( ) ;
2025-08-27 18:39:45 -07:00
} , [ settings . merged . context ? . fileName ] ) ;
2025-06-13 09:19:08 -07:00
2025-07-11 16:52:56 -07:00
const initialPrompt = useMemo ( ( ) = > config . getQuestion ( ) , [ config ] ) ;
const geminiClient = config . getGeminiClient ( ) ;
useEffect ( ( ) = > {
if (
initialPrompt &&
! initialPromptSubmitted . current &&
! isAuthenticating &&
! isAuthDialogOpen &&
! isThemeDialogOpen &&
! isEditorDialogOpen &&
! showPrivacyNotice &&
geminiClient ? . isInitialized ? . ( )
) {
submitQuery ( initialPrompt ) ;
initialPromptSubmitted . current = true ;
}
} , [
initialPrompt ,
submitQuery ,
isAuthenticating ,
isAuthDialogOpen ,
isThemeDialogOpen ,
isEditorDialogOpen ,
showPrivacyNotice ,
geminiClient ,
] ) ;
2025-06-11 20:08:32 -04:00
if ( quittingMessages ) {
return (
< Box flexDirection = "column" marginBottom = { 1 } >
{ quittingMessages . map ( ( item ) = > (
< HistoryItemDisplay
key = { item . id }
2025-06-19 20:17:23 +00:00
availableTerminalHeight = {
constrainHeight ? availableTerminalHeight : undefined
}
terminalWidth = { terminalWidth }
2025-06-11 20:08:32 -04:00
item = { item }
isPending = { false }
config = { config }
/ >
) ) }
< / Box >
) ;
}
2025-08-06 15:47:58 -04:00
2025-06-19 20:17:23 +00:00
const mainAreaWidth = Math . floor ( terminalWidth * 0.9 ) ;
2025-06-22 20:43:36 +00:00
const debugConsoleMaxHeight = Math . floor ( Math . max ( terminalHeight * 0.2 , 5 ) ) ;
2025-06-19 20:17:23 +00:00
// Arbitrary threshold to ensure that items in the static area are large
// enough but not too large to make the terminal hard to use.
const staticAreaMaxItemHeight = Math . max ( terminalHeight * 4 , 100 ) ;
2025-07-25 15:36:42 -07:00
const placeholder = vimModeEnabled
? " Press 'i' for INSERT mode and 'Esc' for NORMAL mode."
: ' Type your message or @path/to/file' ;
2025-09-02 10:35:43 -07:00
const hideContextSummary = settings . merged . ui ? . hideContextSummary ? ? false ;
2025-04-17 18:06:21 -04:00
return (
2025-05-28 19:46:08 +00:00
< StreamingContext.Provider value = { streamingState } >
2025-07-25 17:36:19 -07:00
< Box flexDirection = "column" width = "90%" >
2025-05-24 00:44:17 -07:00
{ / *
* The Static component is an Ink intrinsic in which there can only be 1 per application .
* Because of this restriction we 're hacking it slightly by having a ' header ' item here to
* ensure that it ' s statically rendered .
*
* Background on the Static Item : Anything in the Static component is written a single time
* to the console . Think of it like doing a console . log and then never using ANSI codes to
* clear that content ever again . Effectively it has a moving frame that every time new static
* content is set it 'll flush content to the terminal and move the area which it' s "clearing"
* down a notch . Without Static the area which gets erased and redrawn continuously grows .
* / }
< Static
key = { staticKey }
items = { [
< Box flexDirection = "column" key = "header" >
2025-08-27 18:39:45 -07:00
{ ! (
settings . merged . ui ? . hideBanner || config . getScreenReader ( )
) && < Header version = { version } nightly = { nightly } / > }
{ ! ( settings . merged . ui ? . hideTips || config . getScreenReader ( ) ) && (
2025-08-21 22:29:15 +00:00
< Tips config = { config } / >
) }
2025-05-24 00:44:17 -07:00
< / Box > ,
. . . history . map ( ( h ) = > (
< HistoryItemDisplay
2025-06-19 20:17:23 +00:00
terminalWidth = { mainAreaWidth }
availableTerminalHeight = { staticAreaMaxItemHeight }
2025-05-24 00:44:17 -07:00
key = { h . id }
item = { h }
isPending = { false }
2025-06-08 18:56:58 +01:00
config = { config }
2025-08-04 09:53:50 -07:00
commands = { slashCommands }
2025-05-24 00:44:17 -07:00
/ >
) ) ,
] }
>
{ ( item ) = > item }
< / Static >
2025-06-22 00:54:10 +00:00
< OverflowProvider >
< Box ref = { pendingHistoryItemRef } flexDirection = "column" >
{ pendingHistoryItems . map ( ( item , i ) = > (
< HistoryItemDisplay
key = { i }
availableTerminalHeight = {
constrainHeight ? availableTerminalHeight : undefined
}
terminalWidth = { mainAreaWidth }
// TODO(taehykim): It seems like references to ids aren't necessary in
// HistoryItemDisplay. Refactor later. Use a fake id for now.
item = { { . . . item , id : 0 } }
isPending = { true }
config = { config }
isFocused = { ! isEditorDialogOpen }
/ >
) ) }
< ShowMoreLines constrainHeight = { constrainHeight } / >
< / Box >
< / OverflowProvider >
2025-05-24 00:44:17 -07:00
< Box flexDirection = "column" ref = { mainControlsRef } >
2025-07-28 17:56:52 -07:00
{ /* Move UpdateNotification to render update notification above input area */ }
{ updateInfo && < UpdateNotification message = { updateInfo . message } / > }
2025-07-28 11:13:46 -07:00
{ startupWarnings . length > 0 && (
2025-05-17 22:36:33 -07:00
< Box
2025-05-24 00:44:17 -07:00
borderStyle = "round"
2025-08-15 20:18:31 -07:00
borderColor = { Colors . AccentYellow }
2025-05-24 00:44:17 -07:00
paddingX = { 1 }
marginY = { 1 }
flexDirection = "column"
2025-05-17 22:36:33 -07:00
>
2025-07-28 11:13:46 -07:00
{ startupWarnings . map ( ( warning , index ) = > (
2025-08-15 20:18:31 -07:00
< Text key = { index } color = { Colors . AccentYellow } >
2025-05-24 00:44:17 -07:00
{ warning }
< / Text >
) ) }
2025-05-17 22:36:33 -07:00
< / Box >
2025-05-24 00:44:17 -07:00
) }
2025-08-27 00:43:02 +00:00
{ showWorkspaceMigrationDialog ? (
< WorkspaceMigrationDialog
workspaceExtensions = { workspaceExtensions }
onOpen = { onWorkspaceMigrationDialogOpen }
onClose = { onWorkspaceMigrationDialogClose }
/ >
) : shouldShowIdePrompt && currentIDE ? (
2025-08-06 15:47:58 -04:00
< IdeIntegrationNudge
2025-08-12 20:08:47 +00:00
ide = { currentIDE }
2025-08-06 15:47:58 -04:00
onComplete = { handleIdePromptComplete }
/ >
2025-08-29 04:19:43 +05:30
) : isProQuotaDialogOpen ? (
< ProQuotaDialog
currentModel = { config . getModel ( ) }
fallbackModel = { DEFAULT_GEMINI_FLASH_MODEL }
onChoice = { ( choice ) = > {
setIsProQuotaDialogOpen ( false ) ;
if ( ! proQuotaDialogResolver ) return ;
const resolveValue = choice !== 'auth' ;
proQuotaDialogResolver ( resolveValue ) ;
setProQuotaDialogResolver ( null ) ;
if ( choice === 'auth' ) {
2025-09-05 22:00:15 +05:30
cancelOngoingRequest ? . ( ) ;
2025-08-29 04:19:43 +05:30
openAuthDialog ( ) ;
} else {
addItem (
{
type : MessageType . INFO ,
text : 'Switched to fallback model. Tip: Press Ctrl+P to recall your previous prompt and submit it again if you wish.' ,
} ,
Date . now ( ) ,
) ;
}
} }
/ >
2025-09-03 11:44:26 -07:00
) : showIdeRestartPrompt ? (
< Box
borderStyle = "round"
borderColor = { Colors . AccentYellow }
paddingX = { 1 }
>
< Text color = { Colors . AccentYellow } >
Workspace trust has changed . Press & apos ; r & apos ; to restart
Gemini to apply the changes .
< / Text >
< / Box >
2025-08-08 11:02:27 -07:00
) : isFolderTrustDialogOpen ? (
2025-08-21 00:38:12 -07:00
< FolderTrustDialog
onSelect = { handleFolderTrustSelect }
isRestarting = { isRestarting }
/ >
2025-08-06 15:47:58 -04:00
) : shellConfirmationRequest ? (
2025-07-27 02:00:26 -04:00
< ShellConfirmationDialog request = { shellConfirmationRequest } / >
2025-08-09 15:59:22 +09:00
) : confirmationRequest ? (
< Box flexDirection = "column" >
{ confirmationRequest . prompt }
< Box paddingY = { 1 } >
< RadioButtonSelect
2025-08-16 00:46:45 +09:00
isFocused = { ! ! confirmationRequest }
2025-08-09 15:59:22 +09:00
items = { [
{ label : 'Yes' , value : true } ,
{ label : 'No' , value : false } ,
] }
onSelect = { ( value : boolean ) = > {
confirmationRequest . onConfirm ( value ) ;
} }
/ >
< / Box >
< / Box >
2025-07-27 02:00:26 -04:00
) : isThemeDialogOpen ? (
2025-05-24 00:44:17 -07:00
< Box flexDirection = "column" >
{ themeError && (
< Box marginBottom = { 1 } >
2025-08-15 20:18:31 -07:00
< Text color = { Colors . AccentRed } > { themeError } < / Text >
2025-05-24 00:44:17 -07:00
< / Box >
) }
< ThemeDialog
onSelect = { handleThemeSelect }
onHighlight = { handleThemeHighlight }
settings = { settings }
2025-06-19 20:17:23 +00:00
availableTerminalHeight = {
constrainHeight
? terminalHeight - staticExtraHeight
: undefined
}
terminalWidth = { mainAreaWidth }
2025-05-20 16:50:32 -07:00
/ >
2025-05-24 00:44:17 -07:00
< / Box >
2025-08-10 09:04:52 +09:00
) : isSettingsDialogOpen ? (
< Box flexDirection = "column" >
< SettingsDialog
settings = { settings }
onSelect = { ( ) = > closeSettingsDialog ( ) }
onRestartRequest = { ( ) = > process . exit ( 0 ) }
/ >
< / Box >
2025-06-20 10:46:41 -07:00
) : isAuthenticating ? (
2025-07-10 18:59:02 -07:00
< >
< AuthInProgress
onTimeout = { ( ) = > {
setAuthError ( 'Authentication timed out. Please try again.' ) ;
cancelAuthentication ( ) ;
openAuthDialog ( ) ;
} }
/ >
{ showErrorDetails && (
< OverflowProvider >
< Box flexDirection = "column" >
< DetailedMessagesDisplay
messages = { filteredConsoleMessages }
maxHeight = {
constrainHeight ? debugConsoleMaxHeight : undefined
}
width = { inputWidth }
/ >
< ShowMoreLines constrainHeight = { constrainHeight } / >
< / Box >
< / OverflowProvider >
) }
< / >
2025-06-19 16:52:22 -07:00
) : isAuthDialogOpen ? (
< Box flexDirection = "column" >
< AuthDialog
onSelect = { handleAuthSelect }
settings = { settings }
initialErrorMessage = { authError }
/ >
< / Box >
2025-06-12 02:21:54 +01:00
) : isEditorDialogOpen ? (
< Box flexDirection = "column" >
{ editorError && (
< Box marginBottom = { 1 } >
2025-08-15 20:18:31 -07:00
< Text color = { Colors . AccentRed } > { editorError } < / Text >
2025-06-12 02:21:54 +01:00
< / Box >
) }
< EditorSettingsDialog
onSelect = { handleEditorSelect }
settings = { settings }
onExit = { exitEditorDialog }
/ >
< / Box >
2025-06-27 12:07:38 -07:00
) : showPrivacyNotice ? (
< PrivacyNotice
onExit = { ( ) = > setShowPrivacyNotice ( false ) }
config = { config }
/ >
2025-05-24 00:44:17 -07:00
) : (
< >
< LoadingIndicator
2025-06-15 11:19:05 -07:00
thought = {
streamingState === StreamingState . WaitingForConfirmation ||
2025-08-21 22:29:15 +00:00
config . getAccessibility ( ) ? . disableLoadingPhrases ||
config . getScreenReader ( )
2025-06-15 11:19:05 -07:00
? undefined
: thought
}
2025-06-04 00:46:57 -07:00
currentLoadingPhrase = {
2025-08-21 22:29:15 +00:00
config . getAccessibility ( ) ? . disableLoadingPhrases ||
config . getScreenReader ( )
2025-06-04 00:46:57 -07:00
? undefined
: currentLoadingPhrase
}
2025-05-24 00:44:17 -07:00
elapsedTime = { elapsedTime }
/ >
2025-08-19 09:25:16 -07:00
{ /* Display queued messages below loading indicator */ }
{ messageQueue . length > 0 && (
< Box flexDirection = "column" marginTop = { 1 } >
{ messageQueue
. slice ( 0 , MAX_DISPLAYED_QUEUED_MESSAGES )
. map ( ( message , index ) = > {
// Ensure multi-line messages are collapsed for the preview.
// Replace all whitespace (including newlines) with a single space.
const preview = message . replace ( /\s+/g , ' ' ) ;
return (
// Ensure the Box takes full width so truncation calculates correctly
< Box key = { index } paddingLeft = { 2 } width = "100%" >
{ /* Use wrap="truncate" to ensure it fits the terminal width and doesn't wrap */ }
< Text dimColor wrap = "truncate" >
{ preview }
< / Text >
< / Box >
) ;
} ) }
{ messageQueue . length > MAX_DISPLAYED_QUEUED_MESSAGES && (
< Box paddingLeft = { 2 } >
< Text dimColor >
. . . ( +
2025-08-27 00:43:02 +00:00
{ messageQueue . length - MAX_DISPLAYED_QUEUED_MESSAGES }
2025-08-19 09:25:16 -07:00
more )
< / Text >
< / Box >
) }
< / Box >
) }
2025-05-24 00:44:17 -07:00
< Box
marginTop = { 1 }
2025-09-02 10:35:43 -07:00
justifyContent = {
hideContextSummary ? 'flex-start' : 'space-between'
}
2025-05-24 00:44:17 -07:00
width = "100%"
2025-08-07 15:55:53 -07:00
flexDirection = { isNarrow ? 'column' : 'row' }
alignItems = { isNarrow ? 'flex-start' : 'center' }
2025-05-24 00:44:17 -07:00
>
< Box >
2025-08-17 12:43:21 -04:00
{ process . env [ 'GEMINI_SYSTEM_MD' ] && (
2025-08-15 20:18:31 -07:00
< Text color = { Colors . AccentRed } > | ⌐ ■ _ ■ | < / Text >
2025-05-24 00:44:17 -07:00
) }
2025-05-30 19:36:52 -07:00
{ ctrlCPressedOnce ? (
2025-08-15 20:18:31 -07:00
< Text color = { Colors . AccentYellow } >
2025-05-30 19:36:52 -07:00
Press Ctrl + C again to exit .
< / Text >
2025-06-13 09:59:09 -07:00
) : ctrlDPressedOnce ? (
2025-08-15 20:18:31 -07:00
< Text color = { Colors . AccentYellow } >
2025-06-13 09:59:09 -07:00
Press Ctrl + D again to exit .
< / Text >
2025-08-10 06:26:43 +08:00
) : showEscapePrompt ? (
2025-08-15 20:18:31 -07:00
< Text color = { Colors . Gray } > Press Esc again to clear . < / Text >
2025-09-02 10:35:43 -07:00
) : ! hideContextSummary ? (
2025-06-01 15:48:48 -07:00
< ContextSummaryDisplay
2025-07-28 11:03:22 -04:00
ideContext = { ideContextState }
2025-06-01 15:48:48 -07:00
geminiMdFileCount = { geminiMdFileCount }
2025-06-13 09:19:08 -07:00
contextFileNames = { contextFileNames }
2025-06-01 15:48:48 -07:00
mcpServers = { config . getMcpServers ( ) }
2025-07-18 20:45:00 +02:00
blockedMcpServers = { config . getBlockedMcpServers ( ) }
2025-06-07 18:30:56 -04:00
showToolDescriptions = { showToolDescriptions }
2025-06-01 15:48:48 -07:00
/ >
2025-09-02 10:35:43 -07:00
) : null }
2025-05-24 00:44:17 -07:00
< / Box >
2025-09-02 10:35:43 -07:00
< Box
paddingTop = { isNarrow ? 1 : 0 }
marginLeft = { hideContextSummary ? 1 : 2 }
>
2025-06-02 22:05:45 +02:00
{ showAutoAcceptIndicator !== ApprovalMode . DEFAULT &&
! shellModeActive && (
< AutoAcceptIndicator
approvalMode = { showAutoAcceptIndicator }
/ >
) }
2025-05-24 00:44:17 -07:00
{ shellModeActive && < ShellModeIndicator / > }
< / Box >
< / Box >
{ showErrorDetails && (
2025-06-22 00:54:10 +00:00
< OverflowProvider >
2025-07-03 21:42:02 +02:00
< Box flexDirection = "column" >
< DetailedMessagesDisplay
messages = { filteredConsoleMessages }
maxHeight = {
constrainHeight ? debugConsoleMaxHeight : undefined
}
width = { inputWidth }
/ >
< ShowMoreLines constrainHeight = { constrainHeight } / >
< / Box >
2025-06-22 00:54:10 +00:00
< / OverflowProvider >
2025-05-24 00:44:17 -07:00
) }
{ isInputActive && (
< InputPrompt
2025-06-13 09:59:09 -07:00
buffer = { buffer }
inputWidth = { inputWidth }
suggestionsWidth = { suggestionsWidth }
2025-05-24 00:44:17 -07:00
onSubmit = { handleFinalSubmit }
2025-08-30 03:18:22 +09:00
userMessages = { inputHistoryStore . inputHistory }
2025-05-24 00:44:17 -07:00
onClearScreen = { handleClearScreen }
config = { config }
slashCommands = { slashCommands }
2025-07-07 16:45:44 -04:00
commandContext = { commandContext }
2025-05-24 00:44:17 -07:00
shellModeActive = { shellModeActive }
setShellModeActive = { setShellModeActive }
2025-08-10 06:26:43 +08:00
onEscapePromptChange = { handleEscapePromptChange }
2025-07-17 20:45:42 -04:00
focus = { isFocused }
2025-07-25 15:36:42 -07:00
vimHandleInput = { vimHandleInput }
placeholder = { placeholder }
2025-05-24 00:44:17 -07:00
/ >
) }
< / >
) }
{ initError && streamingState !== StreamingState . Responding && (
< Box
borderStyle = "round"
2025-08-15 20:18:31 -07:00
borderColor = { Colors . AccentRed }
2025-05-24 00:44:17 -07:00
paddingX = { 1 }
marginBottom = { 1 }
>
{ history . find (
( item ) = >
item . type === 'error' && item . text ? . includes ( initError ) ,
) ? . text ? (
2025-08-15 20:18:31 -07:00
< Text color = { Colors . AccentRed } >
2025-05-24 00:44:17 -07:00
{
history . find (
( item ) = >
item . type === 'error' && item . text ? . includes ( initError ) ,
) ? . text
}
2025-05-15 00:19:41 -07:00
< / Text >
2025-05-24 00:44:17 -07:00
) : (
< >
2025-08-15 20:18:31 -07:00
< Text color = { Colors . AccentRed } >
2025-05-24 00:44:17 -07:00
Initialization Error : { initError }
< / Text >
2025-08-15 20:18:31 -07:00
< Text color = { Colors . AccentRed } >
2025-05-24 00:44:17 -07:00
{ ' ' }
Please check API key and configuration .
< / Text >
< / >
) }
< / Box >
) }
2025-08-27 18:39:45 -07:00
{ ! settings . merged . ui ? . hideFooter && (
2025-08-19 17:06:01 -04:00
< Footer
model = { currentModel }
targetDir = { config . getTargetDir ( ) }
debugMode = { config . getDebugMode ( ) }
branchName = { branchName }
debugMessage = { debugMessage }
corgiMode = { corgiMode }
errorCount = { errorCount }
showErrorDetails = { showErrorDetails }
showMemoryUsage = {
config . getDebugMode ( ) ||
2025-08-27 18:39:45 -07:00
settings . merged . ui ? . showMemoryUsage ||
2025-08-19 17:06:01 -04:00
false
}
promptTokenCount = { sessionStats . lastPromptTokenCount }
nightly = { nightly }
vimMode = { vimModeEnabled ? vimMode : undefined }
isTrustedFolder = { isTrustedFolderState }
2025-09-02 10:35:43 -07:00
hideCWD = { settings . merged . ui ? . footer ? . hideCWD }
hideSandboxStatus = { settings . merged . ui ? . footer ? . hideSandboxStatus }
hideModelInfo = { settings . merged . ui ? . footer ? . hideModelInfo }
2025-08-19 17:06:01 -04:00
/ >
) }
2025-05-24 00:44:17 -07:00
< / Box >
2025-05-15 00:19:41 -07:00
< / Box >
2025-05-24 00:44:17 -07:00
< / StreamingContext.Provider >
2025-04-17 18:06:21 -04:00
) ;
Initial commit of Gemini Code CLI
This commit introduces the initial codebase for the Gemini Code CLI, a command-line interface designed to facilitate interaction with the Gemini API for software engineering tasks.
The code was migrated from a previous git repository as a single squashed commit.
Core Features & Components:
* **Gemini Integration:** Leverages the `@google/genai` SDK to interact with the Gemini models, supporting chat history, streaming responses, and function calling (tools).
* **Terminal UI:** Built with Ink (React for CLIs) providing an interactive chat interface within the terminal, including input prompts, message display, loading indicators, and tool interaction elements.
* **Tooling Framework:** Implements a robust tool system allowing Gemini to interact with the local environment. Includes tools for:
* File system listing (`ls`)
* File reading (`read-file`)
* Content searching (`grep`)
* File globbing (`glob`)
* File editing (`edit`)
* File writing (`write-file`)
* Executing bash commands (`terminal`)
* **State Management:** Handles the streaming state of Gemini responses and manages the conversation history.
* **Configuration:** Parses command-line arguments (`yargs`) and loads environment variables (`dotenv`) for setup.
* **Project Structure:** Organized into `core`, `ui`, `tools`, `config`, and `utils` directories using TypeScript. Includes basic build (`tsc`) and start scripts.
This initial version establishes the foundation for a powerful CLI tool enabling developers to use Gemini for coding assistance directly in their terminal environment.
---
Created by yours truly: __Gemini Code__
2025-04-15 21:41:08 -07:00
} ;