2025-04-18 17:44:24 -07:00
/ * *
* @license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
2025-05-15 00:19:41 -07:00
import { useCallback , useEffect , useMemo , useState , useRef } from 'react' ;
2025-05-22 10:36:44 -07:00
import {
Box ,
DOMElement ,
measureElement ,
Static ,
Text ,
2025-06-13 09:59:09 -07:00
useStdin ,
2025-06-21 11:11:42 -07:00
useStdout ,
2025-05-22 10:36:44 -07:00
useInput ,
type Key as InkKeyType ,
} from 'ink' ;
2025-05-23 22:51:47 -07:00
import { StreamingState , type HistoryItem , MessageType } 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-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-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-04-19 12:38:09 -04:00
import { Colors } from './colors.js' ;
2025-05-05 20:48:34 +00:00
import { Help } from './components/Help.js' ;
2025-05-14 15:19:45 -07:00
import { loadHierarchicalGeminiMemory } from '../config/config.js' ;
2025-05-01 10:34:07 -07:00
import { LoadedSettings } from '../config/settings.js' ;
2025-04-22 18:57:47 -07:00
import { Tips } from './components/Tips.js' ;
2025-05-22 10:36:44 -07:00
import { useConsolePatcher } from './components/ConsolePatcher.js' ;
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-05-21 07:36:22 +00:00
import process from 'node:process' ;
2025-05-31 12:49:28 -07:00
import {
getErrorMessage ,
type Config ,
2025-06-13 09:19:08 -07:00
getAllGeminiMdFilenames ,
2025-06-02 22:05:45 +02:00
ApprovalMode ,
2025-06-12 02:21:54 +01:00
isEditorAvailable ,
EditorType ,
2025-06-25 05:41:11 -07:00
} from '@google/gemini-cli-core' ;
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-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' ;
import * as fs from 'fs' ;
2025-06-17 08:24:07 -07:00
import { UpdateNotification } from './components/UpdateNotification.js' ;
import { checkForUpdates } 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-04-18 21:55:02 +01:00
2025-06-13 09:59:09 -07:00
const CTRL_EXIT_PROMPT_DURATION_MS = 1000 ;
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-05-01 10:34:07 -07:00
settings : LoadedSettings ;
2025-05-14 00:12:04 +00:00
startupWarnings? : 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-06-08 18:01:02 -04:00
export const AppWrapper = ( props : AppProps ) = > (
2025-06-09 20:25:37 -04:00
< SessionStatsProvider >
2025-06-08 18:01:02 -04:00
< App { ...props } / >
2025-06-09 20:25:37 -04:00
< / SessionStatsProvider >
2025-06-08 18:01:02 -04:00
) ;
const App = ( { config , settings , startupWarnings = [ ] } : AppProps ) = > {
2025-06-27 10:57:32 -07:00
useBracketedPaste ( ) ;
2025-06-17 08:24:07 -07:00
const [ updateMessage , setUpdateMessage ] = useState < string | null > ( null ) ;
2025-06-21 11:11:42 -07:00
const { stdout } = useStdout ( ) ;
2025-06-17 08:24:07 -07:00
useEffect ( ( ) = > {
checkForUpdates ( ) . then ( setUpdateMessage ) ;
} , [ ] ) ;
2025-06-11 15:33:09 -04:00
const { history , addItem , clearItems , loadHistory } = useHistory ( ) ;
2025-05-23 22:51:47 -07:00
const {
consoleMessages ,
handleNewMessage ,
clearConsoleMessages : clearConsoleMessagesState ,
} = useConsoleMessages ( ) ;
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-05 20:48:34 +00:00
const [ showHelp , setShowHelp ] = useState < boolean > ( false ) ;
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-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-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 ) ;
const openPrivacyNotice = useCallback ( ( ) = > {
setShowPrivacyNotice ( true ) ;
} , [ ] ) ;
2025-05-22 10:36:44 -07:00
const errorCount = useMemo (
( ) = > consoleMessages . filter ( ( msg ) = > msg . type === 'error' ) . length ,
[ consoleMessages ] ,
) ;
2025-05-30 19:36:52 -07:00
2025-04-30 22:26:28 +00:00
const {
isThemeDialogOpen ,
openThemeDialog ,
handleThemeSelect ,
handleThemeHighlight ,
2025-06-06 07:55:28 -07:00
} = useThemeCommand ( settings , setThemeError , addItem ) ;
2025-05-14 12:37:17 -07:00
2025-06-19 16:52:22 -07:00
const {
isAuthDialogOpen ,
openAuthDialog ,
handleAuthSelect ,
handleAuthHighlight ,
2025-06-20 10:46:41 -07:00
isAuthenticating ,
cancelAuthentication ,
2025-06-19 16:52:22 -07:00
} = useAuthCommand ( settings , setAuthError , config ) ;
useEffect ( ( ) = > {
if ( settings . merged . selectedAuthType ) {
const error = validateAuthMethod ( settings . merged . selectedAuthType ) ;
if ( error ) {
setAuthError ( error ) ;
openAuthDialog ( ) ;
}
}
} , [ settings . merged . selectedAuthType , openAuthDialog , setAuthError ] ) ;
2025-06-12 02:21:54 +01:00
const {
isEditorDialogOpen ,
openEditorDialog ,
handleEditorSelect ,
exitEditorDialog ,
} = useEditorSettings ( settings , setEditorError , addItem ) ;
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 ( ) ,
config . getDebugMode ( ) ,
2025-06-14 10:25:34 -04:00
config . getFileService ( ) ,
2025-06-22 16:17:05 -07:00
config . getExtensionContextFilePaths ( ) ,
2025-05-14 12:37:17 -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 ) ;
}
} , [ config , addItem ] ) ;
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 ,
) : Promise < boolean > = > {
// Add message to UI history
addItem (
{
type : MessageType . INFO ,
2025-06-25 19:33:32 +02:00
text : ` ⚡ Slow response times detected. Automatically switching from ${ currentModel } to ${ fallbackModel } for faster responses for the remainder of this session.
2025-06-27 08:21:46 -07:00
⚡ To avoid this you can either upgrade to Standard tier . See : 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
2025-06-25 19:33:32 +02:00
⚡ You can switch authentication methods by typing / auth ` ,
2025-06-24 18:48:55 -04:00
} ,
Date . now ( ) ,
) ;
return true ; // Always accept the fallback
} ;
config . setFlashFallbackHandler ( flashFallbackHandler ) ;
} , [ config , addItem ] ) ;
2025-06-13 21:21:40 -07:00
const {
handleSlashCommand ,
slashCommands ,
pendingHistoryItems : pendingSlashCommandHistoryItems ,
} = useSlashCommandProcessor (
2025-05-20 16:50:32 -07:00
config ,
2025-06-21 12:15:43 -07:00
settings ,
2025-06-11 15:33:09 -04:00
history ,
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 ,
setShowHelp ,
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-14 15:19:45 -07:00
performMemoryRefresh ,
2025-05-17 21:57:27 -07:00
toggleCorgiMode ,
2025-06-07 18:30:56 -04:00
showToolDescriptions ,
2025-06-11 20:08:32 -04:00
setQuittingMessages ,
2025-06-27 12:07:38 -07:00
openPrivacyNotice ,
2025-06-07 18:30:56 -04:00
) ;
2025-06-13 21:21:40 -07:00
const pendingHistoryItems = [ . . . pendingSlashCommandHistoryItems ] ;
2025-06-07 18:30:56 -04:00
2025-06-13 09:59:09 -07:00
const { rows : terminalHeight , columns : terminalWidth } = useTerminalSize ( ) ;
2025-06-21 11:11:42 -07:00
const isInitialMount = useRef ( true ) ;
2025-06-13 09:59:09 -07:00
const { stdin , setRawMode } = useStdin ( ) ;
const isValidPath = useCallback ( ( filePath : string ) : boolean = > {
try {
return fs . existsSync ( filePath ) && fs . statSync ( filePath ) . isFile ( ) ;
} catch ( _e ) {
return false ;
}
} , [ ] ) ;
const widthFraction = 0.9 ;
const inputWidth = Math . max (
20 ,
2025-06-19 20:17:23 +00:00
Math . floor ( terminalWidth * widthFraction ) - 3 ,
2025-06-13 09:59:09 -07:00
) ;
const suggestionsWidth = Math . max ( 60 , Math . floor ( terminalWidth * 0.8 ) ) ;
const buffer = useTextBuffer ( {
initialText : '' ,
viewport : { height : 10 , width : inputWidth } ,
stdin ,
setRawMode ,
isValidPath ,
} ) ;
const handleExit = useCallback (
(
pressedOnce : boolean ,
setPressedOnce : ( value : boolean ) = > void ,
timerRef : React.MutableRefObject < NodeJS.Timeout | null > ,
) = > {
if ( pressedOnce ) {
if ( timerRef . current ) {
clearTimeout ( timerRef . current ) ;
}
const quitCommand = slashCommands . find (
( cmd ) = > cmd . name === 'quit' || cmd . altName === 'exit' ,
) ;
if ( quitCommand ) {
quitCommand . action ( 'quit' , '' , '' ) ;
} else {
process . exit ( 0 ) ;
}
} else {
setPressedOnce ( true ) ;
timerRef . current = setTimeout ( ( ) = > {
setPressedOnce ( false ) ;
timerRef . current = null ;
} , CTRL_EXIT_PROMPT_DURATION_MS ) ;
}
} ,
[ slashCommands ] ,
) ;
2025-06-07 18:30:56 -04:00
useInput ( ( input : string , key : InkKeyType ) = > {
2025-06-23 05:42:20 +00:00
let enteringConstrainHeightMode = false ;
2025-06-22 20:43:36 +00:00
if ( ! constrainHeight ) {
// Automatically re-enter constrain height mode if the user types
// anything. When constrainHeight==false, the user will experience
// significant flickering so it is best to disable it immediately when
// the user starts interacting with the app.
2025-06-23 05:42:20 +00:00
enteringConstrainHeightMode = true ;
2025-06-22 20:43:36 +00:00
setConstrainHeight ( true ) ;
}
2025-06-07 18:30:56 -04:00
if ( key . ctrl && input === 'o' ) {
setShowErrorDetails ( ( prev ) = > ! prev ) ;
} else if ( key . ctrl && input === 't' ) {
const newValue = ! showToolDescriptions ;
setShowToolDescriptions ( newValue ) ;
const mcpServers = config . getMcpServers ( ) ;
if ( Object . keys ( mcpServers || { } ) . length > 0 ) {
handleSlashCommand ( newValue ? '/mcp desc' : '/mcp nodesc' ) ;
}
} else if ( key . ctrl && ( input === 'c' || input === 'C' ) ) {
2025-06-13 09:59:09 -07:00
handleExit ( ctrlCPressedOnce , setCtrlCPressedOnce , ctrlCTimerRef ) ;
} else if ( key . ctrl && ( input === 'd' || input === 'D' ) ) {
if ( buffer . text . length > 0 ) {
// Do nothing if there is text in the input.
return ;
2025-06-07 18:30:56 -04:00
}
2025-06-13 09:59:09 -07:00
handleExit ( ctrlDPressedOnce , setCtrlDPressedOnce , ctrlDTimerRef ) ;
2025-06-23 05:42:20 +00:00
} else if ( key . ctrl && input === 's' && ! enteringConstrainHeightMode ) {
2025-06-22 20:43:36 +00:00
setConstrainHeight ( false ) ;
2025-06-07 18:30:56 -04:00
}
} ) ;
useConsolePatcher ( {
onNewMessage : handleNewMessage ,
debugMode : config.getDebugMode ( ) ,
} ) ;
useEffect ( ( ) = > {
if ( config ) {
setGeminiMdFileCount ( config . getGeminiMdFileCount ( ) ) ;
}
} , [ config ] ) ;
2025-06-12 02:21:54 +01:00
const getPreferredEditor = useCallback ( ( ) = > {
const editorType = settings . merged . preferredEditor ;
const isValidEditor = isEditorAvailable ( editorType ) ;
if ( ! isValidEditor ) {
openEditorDialog ( ) ;
return ;
}
return editorType as EditorType ;
} , [ settings , openEditorDialog ] ) ;
2025-06-19 16:52:22 -07:00
const onAuthError = useCallback ( ( ) = > {
setAuthError ( 'reauth required' ) ;
openAuthDialog ( ) ;
} , [ openAuthDialog , setAuthError ] ) ;
2025-06-13 21:21:40 -07:00
const {
streamingState ,
submitQuery ,
initError ,
pendingHistoryItems : pendingGeminiHistoryItems ,
2025-06-15 11:19:05 -07:00
thought ,
2025-06-13 21:21:40 -07:00
} = useGeminiStream (
config . getGeminiClient ( ) ,
history ,
addItem ,
setShowHelp ,
config ,
setDebugMessage ,
handleSlashCommand ,
shellModeActive ,
getPreferredEditor ,
2025-06-19 16:52:22 -07:00
onAuthError ,
2025-06-22 01:35:36 -04:00
performMemoryRefresh ,
2025-06-13 21:21:40 -07:00
) ;
pendingHistoryItems . push ( . . . pendingGeminiHistoryItems ) ;
2025-05-24 00:44:17 -07:00
const { elapsedTime , currentLoadingPhrase } =
useLoadingIndicator ( streamingState ) ;
2025-05-17 21:25:28 -07:00
const showAutoAcceptIndicator = useAutoAcceptIndicator ( { config } ) ;
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-04-22 18:57:47 -07:00
const handleFinalSubmit = useCallback (
( submittedValue : string ) = > {
const trimmedValue = submittedValue . trim ( ) ;
2025-04-30 22:26:28 +00:00
if ( trimmedValue . length > 0 ) {
2025-05-19 16:11:45 -07:00
submitQuery ( trimmedValue ) ;
2025-04-22 18:57:47 -07:00
}
} ,
2025-05-19 16:11:45 -07:00
[ submitQuery ] ,
2025-04-22 18:57:47 -07:00
) ;
2025-05-21 07:36:22 +00:00
const logger = useLogger ( ) ;
const [ userMessages , setUserMessages ] = useState < string [ ] > ( [ ] ) ;
useEffect ( ( ) = > {
const fetchUserMessages = async ( ) = > {
2025-06-03 23:01:26 -07:00
const pastMessagesRaw = ( await logger ? . getPreviousUserMessages ( ) ) || [ ] ; // Newest first
const currentSessionUserMessages = history
. filter (
( item ) : item is HistoryItem & { type : 'user' ; text : string } = >
item . type === 'user' &&
typeof item . text === 'string' &&
item . text . trim ( ) !== '' ,
)
. map ( ( item ) = > item . text )
. reverse ( ) ; // Newest first, to match pastMessagesRaw sorting
// Combine, with current session messages being more recent
const combinedMessages = [
. . . currentSessionUserMessages ,
. . . pastMessagesRaw ,
] ;
// Deduplicate consecutive identical messages from the combined list (still newest first)
const deduplicatedMessages : string [ ] = [ ] ;
if ( combinedMessages . length > 0 ) {
deduplicatedMessages . push ( combinedMessages [ 0 ] ) ; // Add the newest one unconditionally
for ( let i = 1 ; i < combinedMessages . length ; i ++ ) {
if ( combinedMessages [ i ] !== combinedMessages [ i - 1 ] ) {
deduplicatedMessages . push ( combinedMessages [ i ] ) ;
}
}
2025-05-21 07:36:22 +00:00
}
2025-06-03 23:01:26 -07:00
// Reverse to oldest first for useInputHistory
setUserMessages ( deduplicatedMessages . reverse ( ) ) ;
2025-05-21 07:36:22 +00:00
} ;
fetchUserMessages ( ) ;
} , [ history , logger ] ) ;
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-04-21 14:32:18 -04:00
const isInputActive = streamingState === StreamingState . Idle && ! initError ;
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 ( ( ) = > {
const fromSettings = settings . merged . contextFileName ;
if ( fromSettings ) {
return Array . isArray ( fromSettings ) ? fromSettings : [ fromSettings ] ;
}
return getAllGeminiMdFilenames ( ) ;
} , [ settings . merged . contextFileName ] ) ;
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-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-04-17 18:06:21 -04:00
return (
2025-05-28 19:46:08 +00:00
< StreamingContext.Provider value = { streamingState } >
2025-05-24 00:44:17 -07:00
< Box flexDirection = "column" marginBottom = { 1 } width = "90%" >
{ / *
* 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-06-13 00:59:45 -07:00
< Header terminalWidth = { terminalWidth } / >
2025-06-30 01:56:37 +02:00
{ ! settings . merged . hideTips && < Tips config = { config } / > }
2025-06-17 08:24:07 -07:00
{ updateMessage && < UpdateNotification message = { updateMessage } / > }
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-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
{ showHelp && < Help commands = { slashCommands } / > }
< Box flexDirection = "column" ref = { mainControlsRef } >
{ startupWarnings . length > 0 && (
2025-05-17 22:36:33 -07:00
< Box
2025-05-24 00:44:17 -07:00
borderStyle = "round"
borderColor = { Colors . AccentYellow }
paddingX = { 1 }
marginY = { 1 }
flexDirection = "column"
2025-05-17 22:36:33 -07:00
>
2025-05-24 00:44:17 -07:00
{ startupWarnings . map ( ( warning , index ) = > (
< Text key = { index } color = { Colors . AccentYellow } >
{ warning }
< / Text >
) ) }
2025-05-17 22:36:33 -07:00
< / Box >
2025-05-24 00:44:17 -07:00
) }
{ isThemeDialogOpen ? (
< Box flexDirection = "column" >
{ themeError && (
< Box marginBottom = { 1 } >
< Text color = { Colors . AccentRed } > { themeError } < / Text >
< / 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-06-20 10:46:41 -07:00
) : isAuthenticating ? (
< AuthInProgress
onTimeout = { ( ) = > {
setAuthError ( 'Authentication timed out. Please try again.' ) ;
cancelAuthentication ( ) ;
openAuthDialog ( ) ;
} }
/ >
2025-06-19 16:52:22 -07:00
) : isAuthDialogOpen ? (
< Box flexDirection = "column" >
< AuthDialog
onSelect = { handleAuthSelect }
onHighlight = { handleAuthHighlight }
settings = { settings }
initialErrorMessage = { authError }
/ >
< / Box >
2025-06-12 02:21:54 +01:00
) : isEditorDialogOpen ? (
< Box flexDirection = "column" >
{ editorError && (
< Box marginBottom = { 1 } >
< Text color = { Colors . AccentRed } > { editorError } < / Text >
< / 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 ||
config . getAccessibility ( ) ? . disableLoadingPhrases
? undefined
: thought
}
2025-06-04 00:46:57 -07:00
currentLoadingPhrase = {
config . getAccessibility ( ) ? . disableLoadingPhrases
? undefined
: currentLoadingPhrase
}
2025-05-24 00:44:17 -07:00
elapsedTime = { elapsedTime }
/ >
< Box
marginTop = { 1 }
display = "flex"
justifyContent = "space-between"
width = "100%"
>
< Box >
{ process . env . GEMINI_SYSTEM_MD && (
< Text color = { Colors . AccentRed } > | ⌐ ■ _ ■ | < / Text >
) }
2025-05-30 19:36:52 -07:00
{ ctrlCPressedOnce ? (
< Text color = { Colors . AccentYellow } >
Press Ctrl + C again to exit .
< / Text >
2025-06-13 09:59:09 -07:00
) : ctrlDPressedOnce ? (
< Text color = { Colors . AccentYellow } >
Press Ctrl + D again to exit .
< / Text >
2025-05-30 19:36:52 -07:00
) : (
2025-06-01 15:48:48 -07:00
< ContextSummaryDisplay
geminiMdFileCount = { geminiMdFileCount }
2025-06-13 09:19:08 -07:00
contextFileNames = { contextFileNames }
2025-06-01 15:48:48 -07:00
mcpServers = { config . getMcpServers ( ) }
2025-06-07 18:30:56 -04:00
showToolDescriptions = { showToolDescriptions }
2025-06-01 15:48:48 -07:00
/ >
2025-05-24 00:44:17 -07:00
) }
< / Box >
< Box >
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 >
< DetailedMessagesDisplay
messages = { filteredConsoleMessages }
maxHeight = {
constrainHeight ? debugConsoleMaxHeight : undefined
}
width = { inputWidth }
/ >
< ShowMoreLines constrainHeight = { constrainHeight } / >
< / 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 }
userMessages = { userMessages }
onClearScreen = { handleClearScreen }
config = { config }
slashCommands = { slashCommands }
shellModeActive = { shellModeActive }
setShellModeActive = { setShellModeActive }
/ >
) }
< / >
) }
{ initError && streamingState !== StreamingState . Responding && (
< Box
borderStyle = "round"
borderColor = { Colors . AccentRed }
paddingX = { 1 }
marginBottom = { 1 }
>
{ history . find (
( item ) = >
item . type === 'error' && item . text ? . includes ( initError ) ,
) ? . text ? (
2025-05-15 00:19:41 -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
) : (
< >
< Text color = { Colors . AccentRed } >
Initialization Error : { initError }
< / Text >
< Text color = { Colors . AccentRed } >
{ ' ' }
Please check API key and configuration .
< / Text >
< / >
) }
< / Box >
) }
< Footer
2025-06-24 18:48:55 -04:00
model = { currentModel }
2025-05-28 23:30:05 +00:00
targetDir = { config . getTargetDir ( ) }
2025-05-24 00:44:17 -07:00
debugMode = { config . getDebugMode ( ) }
2025-05-28 23:30:05 +00:00
branchName = { branchName }
2025-05-24 00:44:17 -07:00
debugMessage = { debugMessage }
corgiMode = { corgiMode }
errorCount = { errorCount }
showErrorDetails = { showErrorDetails }
2025-05-30 22:18:01 +00:00
showMemoryUsage = {
config . getDebugMode ( ) || config . getShowMemoryUsage ( )
}
2025-06-15 11:15:53 -07:00
promptTokenCount = { sessionStats . currentResponse . promptTokenCount }
candidatesTokenCount = {
sessionStats . currentResponse . candidatesTokenCount
}
totalTokenCount = { sessionStats . currentResponse . totalTokenCount }
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
} ;