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-07 16:17:27 -07:00
ContentGeneratorConfig ,
2025-06-12 17:17:29 -07:00
GEMINI_CONFIG_DIR as GEMINI_DIR ,
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-07 14:27:22 -07:00
} from '@gemini-cli/core' ;
2025-05-02 08:15:46 -07:00
import { Settings } from './settings.js' ;
2025-06-07 11:12:30 -07:00
import { getEffectiveModel } from '../utils/modelCheck.js' ;
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-12 17:17:29 -07:00
import * as dotenv from 'dotenv' ;
import * as fs from 'node:fs' ;
import * as path from 'node:path' ;
import * as os from 'node:os' ;
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-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-11 15:33:09 -04:00
checkpoint : 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-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-11 15:33:09 -04:00
. option ( 'checkpoint' , {
alias : 'c' ,
type : 'boolean' ,
description : 'Enables checkpointing of file edits' ,
default : false ,
} )
2025-06-16 01:13:39 -05:00
. version ( getCliVersion ( ) ) // This will enable the --version flag based on package.json
. 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-04-19 19:45:42 +01:00
loadEnvironment ( ) ;
2025-04-18 11:12:18 -07:00
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-07 16:17:27 -07:00
const contentGeneratorConfig = await createContentGeneratorConfig ( argv ) ;
2025-06-02 13:55:54 -07:00
2025-06-10 15:48:39 -07:00
const mcpServers = mergeMcpServers ( settings , extensions ) ;
2025-06-07 13:49:00 -07:00
return new Config ( {
2025-06-11 04:46:39 +00:00
sessionId ,
2025-06-07 16:17:27 -07:00
contentGeneratorConfig ,
2025-06-07 13:38:05 -07:00
embeddingModel : DEFAULT_GEMINI_EMBEDDING_MODEL ,
2025-06-10 17:41:59 -07:00
sandbox : argv.sandbox ? ? settings . sandbox ,
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-06-11 14:32:23 -07:00
excludeTools : settings.excludeTools || undefined ,
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-03 21:40:46 -07:00
// Git-aware file filtering settings
fileFilteringRespectGitIgnore : settings.fileFiltering?.respectGitIgnore ,
2025-06-11 15:33:09 -04:00
checkpoint : argv.checkpoint ,
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-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-06-07 16:17:27 -07:00
async function createContentGeneratorConfig (
argv : CliArgs ,
) : Promise < ContentGeneratorConfig > {
const geminiApiKey = process . env . GEMINI_API_KEY ;
const googleApiKey = process . env . GOOGLE_API_KEY ;
const googleCloudProject = process . env . GOOGLE_CLOUD_PROJECT ;
const googleCloudLocation = process . env . GOOGLE_CLOUD_LOCATION ;
2025-06-12 18:00:17 -07:00
const hasCodeAssist = process . env . GEMINI_CODE_ASSIST === 'true' ;
2025-06-07 16:17:27 -07:00
const hasGeminiApiKey = ! ! geminiApiKey ;
const hasGoogleApiKey = ! ! googleApiKey ;
const hasVertexProjectLocationConfig =
! ! googleCloudProject && ! ! googleCloudLocation ;
2025-06-09 13:54:11 -07:00
if ( hasGeminiApiKey && hasGoogleApiKey ) {
logger . warn (
'Both GEMINI_API_KEY and GOOGLE_API_KEY are set. Using GOOGLE_API_KEY.' ,
) ;
}
2025-06-12 18:00:17 -07:00
if (
! hasCodeAssist &&
! hasGeminiApiKey &&
! hasGoogleApiKey &&
! hasVertexProjectLocationConfig
) {
2025-06-07 16:17:27 -07:00
logger . error (
'No valid API authentication configuration found. Please set ONE of the following combinations in your environment variables or .env file:\n' +
2025-06-12 18:00:17 -07:00
'1. GEMINI_CODE_ASSIST=true (for Code Assist access).\n' +
'2. GEMINI_API_KEY (for Gemini API access).\n' +
'3. GOOGLE_API_KEY (for Gemini API or Vertex AI Express Mode access).\n' +
'4. GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION (for Vertex AI access).\n\n' +
2025-06-07 16:17:27 -07:00
'For Gemini API keys, visit: https://ai.google.dev/gemini-api/docs/api-key\n' +
2025-06-14 01:25:08 -07:00
'For Vertex AI authentication, visit: https://cloud.google.com/vertex-ai/docs/authentication\n' +
2025-06-07 16:17:27 -07:00
'The GOOGLE_GENAI_USE_VERTEXAI environment variable can also be set to true/false to influence service selection when ambiguity exists.' ,
) ;
process . exit ( 1 ) ;
}
const config : ContentGeneratorConfig = {
model : argv.model || DEFAULT_GEMINI_MODEL ,
2025-06-09 13:54:11 -07:00
apiKey : googleApiKey || geminiApiKey || '' ,
2025-06-07 16:17:27 -07:00
vertexai : hasGeminiApiKey ? false : undefined ,
2025-06-12 18:00:17 -07:00
codeAssist : hasCodeAssist ,
2025-06-07 16:17:27 -07:00
} ;
if ( config . apiKey ) {
config . model = await getEffectiveModel ( config . apiKey , config . model ) ;
}
return config ;
}
2025-06-12 17:17:29 -07:00
function findEnvFile ( startDir : string ) : string | null {
let currentDir = path . resolve ( startDir ) ;
while ( true ) {
// prefer gemini-specific .env under GEMINI_DIR
const geminiEnvPath = path . join ( currentDir , GEMINI_DIR , '.env' ) ;
if ( fs . existsSync ( geminiEnvPath ) ) {
return geminiEnvPath ;
}
const envPath = path . join ( currentDir , '.env' ) ;
if ( fs . existsSync ( envPath ) ) {
return envPath ;
}
const parentDir = path . dirname ( currentDir ) ;
if ( parentDir === currentDir || ! parentDir ) {
// check .env under home as fallback, again preferring gemini-specific .env
const homeGeminiEnvPath = path . join ( os . homedir ( ) , GEMINI_DIR , '.env' ) ;
if ( fs . existsSync ( homeGeminiEnvPath ) ) {
return homeGeminiEnvPath ;
}
const homeEnvPath = path . join ( os . homedir ( ) , '.env' ) ;
if ( fs . existsSync ( homeEnvPath ) ) {
return homeEnvPath ;
}
return null ;
}
currentDir = parentDir ;
}
}
export function loadEnvironment ( ) : void {
const envFilePath = findEnvFile ( process . cwd ( ) ) ;
if ( envFilePath ) {
dotenv . config ( { path : envFilePath } ) ;
}
}