2025-04-18 17:44:24 -07:00
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
2025-04-18 11:12:18 -07:00
import yargs from 'yargs/yargs' ;
import { hideBin } from 'yargs/helpers' ;
import process from 'node:process' ;
2025-04-19 19:45:42 +01:00
import {
Config ,
2025-05-23 08:53:22 -07:00
loadServerHierarchicalMemory ,
2025-05-31 12:49:28 -07:00
setGeminiMdFilename as setServerGeminiMdFilename ,
getCurrentGeminiMdFilename ,
2025-06-02 22:05:45 +02:00
ApprovalMode ,
2025-06-13 01:25:42 -07:00
DEFAULT_GEMINI_MODEL ,
DEFAULT_GEMINI_EMBEDDING_MODEL ,
2025-06-13 20:25:59 -04:00
FileDiscoveryService ,
2025-06-15 00:47:32 -04:00
TelemetryTarget ,
2025-06-25 05:41:11 -07:00
} from '@google/gemini-cli-core' ;
2025-05-02 08:15:46 -07:00
import { Settings } from './settings.js' ;
2025-06-19 16:52:22 -07:00
2025-06-13 13:57:00 -07:00
import { Extension } from './extension.js' ;
2025-06-16 01:13:39 -05:00
import { getCliVersion } from '../utils/version.js' ;
2025-06-18 10:01:00 -07:00
import { loadSandboxConfig } from './sandboxConfig.js' ;
2025-04-18 11:12:18 -07:00
2025-05-14 12:37:17 -07:00
// Simple console logger for now - replace with actual logger if available
const logger = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
debug : ( . . . args : any [ ] ) = > console . debug ( '[DEBUG]' , . . . args ) ,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
warn : ( . . . args : any [ ] ) = > console . warn ( '[WARN]' , . . . args ) ,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error : ( . . . args : any [ ] ) = > console . error ( '[ERROR]' , . . . args ) ,
} ;
2025-04-18 11:12:18 -07:00
interface CliArgs {
model : string | undefined ;
2025-05-02 08:15:46 -07:00
sandbox : boolean | string | undefined ;
2025-06-18 10:01:00 -07:00
'sandbox-image' : string | undefined ;
2025-05-15 11:38:33 -07:00
debug : boolean | undefined ;
prompt : string | undefined ;
2025-05-15 11:44:56 -07:00
all_files : boolean | undefined ;
2025-05-30 22:18:01 +00:00
show_memory_usage : boolean | undefined ;
2025-06-02 22:05:45 +02:00
yolo : boolean | undefined ;
2025-06-05 16:04:25 -04:00
telemetry : boolean | undefined ;
2025-06-20 00:39:15 -04:00
checkpointing : boolean | undefined ;
2025-06-15 00:47:32 -04:00
telemetryTarget : string | undefined ;
telemetryOtlpEndpoint : string | undefined ;
telemetryLogPrompts : boolean | undefined ;
2025-04-18 11:12:18 -07:00
}
2025-05-01 01:00:53 +00:00
async function parseArguments ( ) : Promise < CliArgs > {
const argv = await yargs ( hideBin ( process . argv ) )
2025-04-18 11:12:18 -07:00
. option ( 'model' , {
alias : 'm' ,
type : 'string' ,
2025-05-15 11:38:33 -07:00
description : ` Model ` ,
2025-05-17 17:28:44 -07:00
default : process . env . GEMINI_MODEL || DEFAULT_GEMINI_MODEL ,
2025-04-18 11:12:18 -07:00
} )
2025-05-15 11:38:33 -07:00
. option ( 'prompt' , {
alias : 'p' ,
type : 'string' ,
description : 'Prompt. Appended to input on stdin (if any).' ,
} )
2025-05-02 08:15:46 -07:00
. option ( 'sandbox' , {
alias : 's' ,
type : 'boolean' ,
2025-05-15 11:38:33 -07:00
description : 'Run in sandbox?' ,
2025-05-02 08:15:46 -07:00
} )
2025-06-18 10:01:00 -07:00
. option ( 'sandbox-image' , {
type : 'string' ,
description : 'Sandbox image URI.' ,
} )
2025-05-15 11:38:33 -07:00
. option ( 'debug' , {
alias : 'd' ,
2025-04-20 20:20:40 +01:00
type : 'boolean' ,
2025-05-15 11:38:33 -07:00
description : 'Run in debug mode?' ,
2025-04-20 20:20:40 +01:00
default : false ,
} )
2025-05-15 11:44:56 -07:00
. option ( 'all_files' , {
2025-05-15 11:38:33 -07:00
alias : 'a' ,
2025-04-24 16:08:29 -07:00
type : 'boolean' ,
2025-05-15 11:38:33 -07:00
description : 'Include ALL files in context?' ,
2025-04-24 16:08:29 -07:00
default : false ,
} )
2025-05-30 22:18:01 +00:00
. option ( 'show_memory_usage' , {
type : 'boolean' ,
description : 'Show memory usage in status bar' ,
default : false ,
} )
2025-06-02 22:05:45 +02:00
. option ( 'yolo' , {
alias : 'y' ,
type : 'boolean' ,
description :
'Automatically accept all actions (aka YOLO mode, see https://www.youtube.com/watch?v=xvFZjo5PgG0 for more details)?' ,
default : false ,
} )
2025-06-05 16:04:25 -04:00
. option ( 'telemetry' , {
type : 'boolean' ,
2025-06-15 00:47:32 -04:00
description :
'Enable telemetry? This flag specifically controls if telemetry is sent. Other --telemetry-* flags set specific values but do not enable telemetry on their own.' ,
} )
. option ( 'telemetry-target' , {
type : 'string' ,
choices : [ 'local' , 'gcp' ] ,
description :
'Set the telemetry target (local or gcp). Overrides settings files.' ,
} )
. option ( 'telemetry-otlp-endpoint' , {
type : 'string' ,
description :
'Set the OTLP endpoint for telemetry. Overrides environment variables and settings files.' ,
} )
. option ( 'telemetry-log-prompts' , {
type : 'boolean' ,
description :
'Enable or disable logging of user prompts for telemetry. Overrides settings files.' ,
2025-06-05 16:04:25 -04:00
} )
2025-06-20 00:39:15 -04:00
. option ( 'checkpointing' , {
2025-06-11 15:33:09 -04:00
alias : 'c' ,
type : 'boolean' ,
description : 'Enables checkpointing of file edits' ,
default : false ,
} )
2025-06-18 09:57:17 -07:00
. version ( await getCliVersion ( ) ) // This will enable the --version flag based on package.json
2025-06-16 01:13:39 -05:00
. alias ( 'v' , 'version' )
2025-04-18 11:12:18 -07:00
. help ( )
. alias ( 'h' , 'help' )
2025-04-19 19:45:42 +01:00
. strict ( ) . argv ;
2025-05-14 12:37:17 -07:00
2025-06-03 13:47:53 -07:00
return argv ;
2025-05-14 12:37:17 -07:00
}
2025-05-23 08:53:22 -07:00
// This function is now a thin wrapper around the server's implementation.
// It's kept in the CLI for now as App.tsx directly calls it for memory refresh.
// TODO: Consider if App.tsx should get memory via a server call or if Config should refresh itself.
2025-05-14 12:37:17 -07:00
export async function loadHierarchicalGeminiMemory (
currentWorkingDirectory : string ,
debugMode : boolean ,
2025-06-13 20:25:59 -04:00
fileService : FileDiscoveryService ,
2025-06-11 13:34:35 -07:00
extensionContextFilePaths : string [ ] = [ ] ,
2025-05-14 15:19:45 -07:00
) : Promise < { memoryContent : string ; fileCount : number } > {
2025-05-23 08:53:22 -07:00
if ( debugMode ) {
2025-05-14 12:37:17 -07:00
logger . debug (
2025-05-23 08:53:22 -07:00
` CLI: Delegating hierarchical memory load to server for CWD: ${ currentWorkingDirectory } ` ,
2025-05-14 12:37:17 -07:00
) ;
}
2025-05-23 08:53:22 -07:00
// Directly call the server function.
// The server function will use its own homedir() for the global path.
2025-06-11 13:34:35 -07:00
return loadServerHierarchicalMemory (
currentWorkingDirectory ,
debugMode ,
2025-06-13 20:25:59 -04:00
fileService ,
2025-06-11 13:34:35 -07:00
extensionContextFilePaths ,
) ;
2025-04-18 11:12:18 -07:00
}
2025-06-02 13:55:54 -07:00
export async function loadCliConfig (
settings : Settings ,
2025-06-13 13:57:00 -07:00
extensions : Extension [ ] ,
2025-06-11 04:46:39 +00:00
sessionId : string ,
2025-06-07 11:12:30 -07:00
) : Promise < Config > {
2025-05-01 01:00:53 +00:00
const argv = await parseArguments ( ) ;
2025-05-15 11:38:33 -07:00
const debugMode = argv . debug || false ;
2025-05-14 12:37:17 -07:00
2025-05-31 12:49:28 -07:00
// Set the context filename in the server's memoryTool module BEFORE loading memory
// TODO(b/343434939): This is a bit of a hack. The contextFileName should ideally be passed
// directly to the Config constructor in core, and have core handle setGeminiMdFilename.
// However, loadHierarchicalGeminiMemory is called *before* createServerConfig.
if ( settings . contextFileName ) {
setServerGeminiMdFilename ( settings . contextFileName ) ;
} else {
// Reset to default if not provided in settings.
setServerGeminiMdFilename ( getCurrentGeminiMdFilename ( ) ) ;
}
2025-06-13 13:57:00 -07:00
const extensionContextFilePaths = extensions . flatMap ( ( e ) = > e . contextFiles ) ;
2025-06-11 13:34:35 -07:00
2025-06-13 20:25:59 -04:00
const fileService = new FileDiscoveryService ( process . cwd ( ) ) ;
2025-05-23 08:53:22 -07:00
// Call the (now wrapper) loadHierarchicalGeminiMemory which calls the server's version
2025-05-14 15:19:45 -07:00
const { memoryContent , fileCount } = await loadHierarchicalGeminiMemory (
2025-05-14 12:37:17 -07:00
process . cwd ( ) ,
debugMode ,
2025-06-13 20:25:59 -04:00
fileService ,
2025-06-11 13:34:35 -07:00
extensionContextFilePaths ,
2025-05-14 12:37:17 -07:00
) ;
2025-04-19 19:45:42 +01:00
2025-06-10 15:48:39 -07:00
const mcpServers = mergeMcpServers ( settings , extensions ) ;
2025-07-01 16:13:46 -07:00
const excludeTools = mergeExcludeTools ( settings , extensions ) ;
2025-06-10 15:48:39 -07:00
2025-06-18 10:01:00 -07:00
const sandboxConfig = await loadSandboxConfig ( settings , argv ) ;
2025-06-07 13:49:00 -07:00
return new Config ( {
2025-06-11 04:46:39 +00:00
sessionId ,
2025-06-07 13:38:05 -07:00
embeddingModel : DEFAULT_GEMINI_EMBEDDING_MODEL ,
2025-06-18 10:01:00 -07:00
sandbox : sandboxConfig ,
2025-05-29 20:51:17 +00:00
targetDir : process.cwd ( ) ,
2025-05-14 12:37:17 -07:00
debugMode ,
2025-05-29 20:51:17 +00:00
question : argv.prompt || '' ,
fullContext : argv.all_files || false ,
coreTools : settings.coreTools || undefined ,
2025-07-01 16:13:46 -07:00
excludeTools ,
2025-05-29 20:51:17 +00:00
toolDiscoveryCommand : settings.toolDiscoveryCommand ,
toolCallCommand : settings.toolCallCommand ,
mcpServerCommand : settings.mcpServerCommand ,
2025-06-10 15:48:39 -07:00
mcpServers ,
2025-05-29 20:51:17 +00:00
userMemory : memoryContent ,
geminiMdFileCount : fileCount ,
2025-06-02 22:05:45 +02:00
approvalMode : argv.yolo || false ? ApprovalMode.YOLO : ApprovalMode.DEFAULT ,
2025-05-31 12:49:28 -07:00
showMemoryUsage :
argv.show_memory_usage || settings . showMemoryUsage || false ,
2025-06-04 00:46:57 -07:00
accessibility : settings.accessibility ,
2025-06-15 00:47:32 -04:00
telemetry : {
enabled : argv.telemetry ? ? settings . telemetry ? . enabled ,
target : ( argv . telemetryTarget ? ?
settings . telemetry ? . target ) as TelemetryTarget ,
otlpEndpoint :
argv.telemetryOtlpEndpoint ? ?
process . env . OTEL_EXPORTER_OTLP_ENDPOINT ? ?
settings . telemetry ? . otlpEndpoint ,
logPrompts : argv.telemetryLogPrompts ? ? settings . telemetry ? . logPrompts ,
} ,
2025-06-23 20:29:31 -04:00
usageStatisticsEnabled : settings.usageStatisticsEnabled ? ? true ,
2025-06-03 21:40:46 -07:00
// Git-aware file filtering settings
2025-06-21 18:23:35 -07:00
fileFiltering : {
respectGitIgnore : settings.fileFiltering?.respectGitIgnore ,
enableRecursiveFileSearch :
settings.fileFiltering?.enableRecursiveFileSearch ,
} ,
2025-06-20 00:39:15 -04:00
checkpointing : argv.checkpointing || settings . checkpointing ? . enabled ,
2025-06-12 17:17:29 -07:00
proxy :
process.env.HTTPS_PROXY ||
process . env . https_proxy ||
process . env . HTTP_PROXY ||
process . env . http_proxy ,
cwd : process.cwd ( ) ,
2025-06-13 20:25:59 -04:00
fileDiscoveryService : fileService ,
2025-06-14 00:00:24 -07:00
bugCommand : settings.bugCommand ,
2025-06-19 16:52:22 -07:00
model : argv.model ! ,
2025-06-22 16:17:05 -07:00
extensionContextFilePaths ,
2025-06-07 13:49:00 -07:00
} ) ;
2025-04-18 11:12:18 -07:00
}
2025-06-07 16:17:27 -07:00
2025-06-13 13:57:00 -07:00
function mergeMcpServers ( settings : Settings , extensions : Extension [ ] ) {
2025-06-13 14:51:29 -07:00
const mcpServers = { . . . ( settings . mcpServers || { } ) } ;
2025-06-10 15:48:39 -07:00
for ( const extension of extensions ) {
2025-06-13 13:57:00 -07:00
Object . entries ( extension . config . mcpServers || { } ) . forEach (
( [ key , server ] ) = > {
if ( mcpServers [ key ] ) {
logger . warn (
` Skipping extension MCP config for server with key " ${ key } " as it already exists. ` ,
) ;
return ;
}
mcpServers [ key ] = server ;
} ,
) ;
2025-06-10 15:48:39 -07:00
}
return mcpServers ;
}
2025-07-01 16:13:46 -07:00
function mergeExcludeTools (
settings : Settings ,
extensions : Extension [ ] ,
) : string [ ] {
const allExcludeTools = new Set ( settings . excludeTools || [ ] ) ;
for ( const extension of extensions ) {
for ( const tool of extension . config . excludeTools || [ ] ) {
allExcludeTools . add ( tool ) ;
}
}
return [ . . . allExcludeTools ] ;
}