|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import React from 'react'; |
|
|
import { render } from 'ink'; |
|
|
import { AppWrapper } from './ui/App.js'; |
|
|
import { loadCliConfig } from './config/config.js'; |
|
|
import { readStdin } from './utils/readStdin.js'; |
|
|
import { basename } from 'node:path'; |
|
|
import v8 from 'node:v8'; |
|
|
import os from 'node:os'; |
|
|
import { spawn } from 'node:child_process'; |
|
|
import { start_sandbox } from './utils/sandbox.js'; |
|
|
import { |
|
|
LoadedSettings, |
|
|
loadSettings, |
|
|
SettingScope, |
|
|
} from './config/settings.js'; |
|
|
import { themeManager } from './ui/themes/theme-manager.js'; |
|
|
import { getStartupWarnings } from './utils/startupWarnings.js'; |
|
|
import { runNonInteractive } from './nonInteractiveCli.js'; |
|
|
import { loadExtensions, Extension } from './config/extension.js'; |
|
|
import { cleanupCheckpoints } from './utils/cleanup.js'; |
|
|
import { |
|
|
ApprovalMode, |
|
|
Config, |
|
|
EditTool, |
|
|
ShellTool, |
|
|
WriteFileTool, |
|
|
sessionId, |
|
|
logUserPrompt, |
|
|
AuthType, |
|
|
} from '@google/gemini-cli-core'; |
|
|
import { validateAuthMethod } from './config/auth.js'; |
|
|
import { setMaxSizedBoxDebugging } from './ui/components/shared/MaxSizedBox.js'; |
|
|
|
|
|
function getNodeMemoryArgs(config: Config): string[] { |
|
|
const totalMemoryMB = os.totalmem() / (1024 * 1024); |
|
|
const heapStats = v8.getHeapStatistics(); |
|
|
const currentMaxOldSpaceSizeMb = Math.floor( |
|
|
heapStats.heap_size_limit / 1024 / 1024, |
|
|
); |
|
|
|
|
|
|
|
|
const targetMaxOldSpaceSizeInMB = Math.floor(totalMemoryMB * 0.5); |
|
|
if (config.getDebugMode()) { |
|
|
console.debug( |
|
|
`Current heap size ${currentMaxOldSpaceSizeMb.toFixed(2)} MB`, |
|
|
); |
|
|
} |
|
|
|
|
|
if (process.env.GEMINI_CLI_NO_RELAUNCH) { |
|
|
return []; |
|
|
} |
|
|
|
|
|
if (targetMaxOldSpaceSizeInMB > currentMaxOldSpaceSizeMb) { |
|
|
if (config.getDebugMode()) { |
|
|
console.debug( |
|
|
`Need to relaunch with more memory: ${targetMaxOldSpaceSizeInMB.toFixed(2)} MB`, |
|
|
); |
|
|
} |
|
|
return [`--max-old-space-size=${targetMaxOldSpaceSizeInMB}`]; |
|
|
} |
|
|
|
|
|
return []; |
|
|
} |
|
|
|
|
|
async function relaunchWithAdditionalArgs(additionalArgs: string[]) { |
|
|
const nodeArgs = [...additionalArgs, ...process.argv.slice(1)]; |
|
|
const newEnv = { ...process.env, GEMINI_CLI_NO_RELAUNCH: 'true' }; |
|
|
|
|
|
const child = spawn(process.execPath, nodeArgs, { |
|
|
stdio: 'inherit', |
|
|
env: newEnv, |
|
|
}); |
|
|
|
|
|
await new Promise((resolve) => child.on('close', resolve)); |
|
|
process.exit(0); |
|
|
} |
|
|
|
|
|
export async function main() { |
|
|
|
|
|
const workspaceRoot = process.env.OPENCLI_USER_CWD || process.cwd(); |
|
|
const settings = loadSettings(workspaceRoot); |
|
|
|
|
|
await cleanupCheckpoints(); |
|
|
if (settings.errors.length > 0) { |
|
|
for (const error of settings.errors) { |
|
|
let errorMessage = `Error in ${error.path}: ${error.message}`; |
|
|
if (!process.env.NO_COLOR) { |
|
|
errorMessage = `\x1b[31m${errorMessage}\x1b[0m`; |
|
|
} |
|
|
console.error(errorMessage); |
|
|
console.error(`Please fix ${error.path} and try again.`); |
|
|
} |
|
|
process.exit(1); |
|
|
} |
|
|
|
|
|
const extensions = loadExtensions(workspaceRoot); |
|
|
const config = await loadCliConfig(settings.merged, extensions, sessionId); |
|
|
|
|
|
|
|
|
|
|
|
if (!settings.merged.selectedAuthType && process.env.GEMINI_API_KEY) { |
|
|
settings.setValue( |
|
|
SettingScope.User, |
|
|
'selectedAuthType', |
|
|
AuthType.USE_GEMINI, |
|
|
); |
|
|
} |
|
|
|
|
|
setMaxSizedBoxDebugging(config.getDebugMode()); |
|
|
|
|
|
|
|
|
config.getFileService(); |
|
|
if (config.getCheckpointingEnabled()) { |
|
|
try { |
|
|
await config.getGitService(); |
|
|
} catch { |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
if (settings.merged.theme) { |
|
|
if (!themeManager.setActiveTheme(settings.merged.theme)) { |
|
|
|
|
|
|
|
|
console.warn(`Warning: Theme "${settings.merged.theme}" not found.`); |
|
|
} |
|
|
} |
|
|
|
|
|
const memoryArgs = settings.merged.autoConfigureMaxOldSpaceSize |
|
|
? getNodeMemoryArgs(config) |
|
|
: []; |
|
|
|
|
|
|
|
|
if (!process.env.SANDBOX) { |
|
|
const sandboxConfig = config.getSandbox(); |
|
|
if (sandboxConfig) { |
|
|
if (settings.merged.selectedAuthType) { |
|
|
|
|
|
try { |
|
|
const err = validateAuthMethod(settings.merged.selectedAuthType); |
|
|
if (err) { |
|
|
throw new Error(err); |
|
|
} |
|
|
await config.refreshAuth(settings.merged.selectedAuthType); |
|
|
} catch (err) { |
|
|
console.error('Error authenticating:', err); |
|
|
process.exit(1); |
|
|
} |
|
|
} |
|
|
await start_sandbox(sandboxConfig, memoryArgs); |
|
|
process.exit(0); |
|
|
} else { |
|
|
|
|
|
|
|
|
if (memoryArgs.length > 0) { |
|
|
await relaunchWithAdditionalArgs(memoryArgs); |
|
|
process.exit(0); |
|
|
} |
|
|
} |
|
|
} |
|
|
let input = config.getQuestion(); |
|
|
const startupWarnings = await getStartupWarnings(); |
|
|
|
|
|
|
|
|
if (process.stdin.isTTY && input?.length === 0) { |
|
|
setWindowTitle(basename(workspaceRoot), settings); |
|
|
render( |
|
|
<React.StrictMode> |
|
|
<AppWrapper |
|
|
config={config} |
|
|
settings={settings} |
|
|
startupWarnings={startupWarnings} |
|
|
/> |
|
|
</React.StrictMode>, |
|
|
{ exitOnCtrlC: false }, |
|
|
); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
if (!process.stdin.isTTY) { |
|
|
input += await readStdin(); |
|
|
} |
|
|
if (!input) { |
|
|
console.error('No input provided via stdin.'); |
|
|
process.exit(1); |
|
|
} |
|
|
|
|
|
logUserPrompt(config, { |
|
|
'event.name': 'user_prompt', |
|
|
'event.timestamp': new Date().toISOString(), |
|
|
prompt: input, |
|
|
prompt_length: input.length, |
|
|
}); |
|
|
|
|
|
|
|
|
const nonInteractiveConfig = await loadNonInteractiveConfig( |
|
|
config, |
|
|
extensions, |
|
|
settings, |
|
|
); |
|
|
|
|
|
await runNonInteractive(nonInteractiveConfig, input); |
|
|
process.exit(0); |
|
|
} |
|
|
|
|
|
function setWindowTitle(title: string, settings: LoadedSettings) { |
|
|
if (!settings.merged.hideWindowTitle) { |
|
|
process.stdout.write(`\x1b]2; Gemini - ${title} \x07`); |
|
|
|
|
|
process.on('exit', () => { |
|
|
process.stdout.write(`\x1b]2;\x07`); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
process.on('unhandledRejection', (reason, _promise) => { |
|
|
|
|
|
console.error('========================================='); |
|
|
console.error('CRITICAL: Unhandled Promise Rejection!'); |
|
|
console.error('========================================='); |
|
|
console.error('Reason:', reason); |
|
|
console.error('Stack trace may follow:'); |
|
|
if (!(reason instanceof Error)) { |
|
|
console.error(reason); |
|
|
} |
|
|
|
|
|
process.exit(1); |
|
|
}); |
|
|
|
|
|
async function loadNonInteractiveConfig( |
|
|
config: Config, |
|
|
extensions: Extension[], |
|
|
settings: LoadedSettings, |
|
|
) { |
|
|
let finalConfig = config; |
|
|
if (config.getApprovalMode() !== ApprovalMode.YOLO) { |
|
|
|
|
|
const existingExcludeTools = settings.merged.excludeTools || []; |
|
|
const interactiveTools = [ |
|
|
ShellTool.Name, |
|
|
EditTool.Name, |
|
|
|
|
|
]; |
|
|
|
|
|
const newExcludeTools = [ |
|
|
...new Set([...existingExcludeTools, ...interactiveTools]), |
|
|
]; |
|
|
|
|
|
const nonInteractiveSettings = { |
|
|
...settings.merged, |
|
|
excludeTools: newExcludeTools, |
|
|
}; |
|
|
finalConfig = await loadCliConfig( |
|
|
nonInteractiveSettings, |
|
|
extensions, |
|
|
config.getSessionId(), |
|
|
); |
|
|
} |
|
|
|
|
|
return await validateNonInterActiveAuth( |
|
|
settings.merged.selectedAuthType, |
|
|
finalConfig, |
|
|
); |
|
|
} |
|
|
|
|
|
async function validateNonInterActiveAuth( |
|
|
selectedAuthType: AuthType | undefined, |
|
|
nonInteractiveConfig: Config, |
|
|
) { |
|
|
|
|
|
|
|
|
|
|
|
if (!selectedAuthType && !process.env.GEMINI_API_KEY) { |
|
|
console.error( |
|
|
'Please set an Auth method in your .gemini/settings.json OR specify GEMINI_API_KEY env variable file before running', |
|
|
); |
|
|
process.exit(1); |
|
|
} |
|
|
|
|
|
selectedAuthType = selectedAuthType || AuthType.USE_GEMINI; |
|
|
const err = validateAuthMethod(selectedAuthType); |
|
|
if (err != null) { |
|
|
console.error(err); |
|
|
process.exit(1); |
|
|
} |
|
|
|
|
|
await nonInteractiveConfig.refreshAuth(selectedAuthType); |
|
|
return nonInteractiveConfig; |
|
|
} |
|
|
|