mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-13 14:50:39 -07:00
190 lines
5.7 KiB
JavaScript
190 lines
5.7 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { spawn } from 'node:child_process';
|
|
import os from 'node:os';
|
|
import v8 from 'node:v8';
|
|
|
|
// --- Global Entry Point ---
|
|
|
|
// Suppress known race condition error in node-pty on Windows
|
|
// Tracking bug: https://github.com/microsoft/node-pty/issues/827
|
|
process.on('uncaughtException', (error) => {
|
|
if (
|
|
process.platform === 'win32' &&
|
|
error instanceof Error &&
|
|
error.message === 'Cannot resize a pty that has already exited'
|
|
) {
|
|
// This error happens on Windows with node-pty when resizing a pty that has just exited.
|
|
// It is a race condition in node-pty that we cannot prevent, so we silence it.
|
|
return;
|
|
}
|
|
|
|
// For other errors, we rely on the default behavior, but since we attached a listener,
|
|
// we must manually replicate it.
|
|
if (error instanceof Error) {
|
|
process.stderr.write(error.stack + '\n');
|
|
} else {
|
|
process.stderr.write(String(error) + '\n');
|
|
}
|
|
process.exit(1);
|
|
});
|
|
|
|
async function getMemoryNodeArgs(): Promise<string[]> {
|
|
let autoConfigureMemory = true;
|
|
try {
|
|
const { readFileSync } = await import('node:fs');
|
|
const { join } = await import('node:path');
|
|
// Respect GEMINI_CLI_HOME environment variable, falling back to os.homedir()
|
|
const baseDir =
|
|
process.env['GEMINI_CLI_HOME'] || join(os.homedir(), '.gemini');
|
|
const settingsPath = join(baseDir, 'settings.json');
|
|
const rawSettings = readFileSync(settingsPath, 'utf8');
|
|
const settings = JSON.parse(rawSettings);
|
|
if (settings?.advanced?.autoConfigureMemory === false) {
|
|
autoConfigureMemory = false;
|
|
}
|
|
} catch {
|
|
// ignore
|
|
}
|
|
|
|
if (autoConfigureMemory) {
|
|
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 (targetMaxOldSpaceSizeInMB > currentMaxOldSpaceSizeMb) {
|
|
return [`--max-old-space-size=${targetMaxOldSpaceSizeInMB}`];
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
async function run() {
|
|
if (!process.env['GEMINI_CLI_NO_RELAUNCH'] && !process.env['SANDBOX']) {
|
|
// --- Lightweight Parent Process / Daemon ---
|
|
// We avoid importing heavy dependencies here to save ~1.5s of startup time.
|
|
|
|
const nodeArgs: string[] = [...process.execArgv];
|
|
const scriptArgs = process.argv.slice(2);
|
|
|
|
const memoryArgs = await getMemoryNodeArgs();
|
|
nodeArgs.push(...memoryArgs);
|
|
|
|
const script = process.argv[1];
|
|
nodeArgs.push(script);
|
|
nodeArgs.push(...scriptArgs);
|
|
|
|
const newEnv = { ...process.env, GEMINI_CLI_NO_RELAUNCH: 'true' };
|
|
const RELAUNCH_EXIT_CODE = 199;
|
|
let latestAdminSettings: unknown = undefined;
|
|
|
|
// Prevent the parent process from exiting prematurely on signals.
|
|
// The child process will receive the same signals and handle its own cleanup.
|
|
for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
process.on(sig as NodeJS.Signals, () => {});
|
|
}
|
|
|
|
const runner = () => {
|
|
process.stdin.pause();
|
|
|
|
const child = spawn(process.execPath, nodeArgs, {
|
|
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
|
env: newEnv,
|
|
});
|
|
|
|
if (latestAdminSettings) {
|
|
child.send({ type: 'admin-settings', settings: latestAdminSettings });
|
|
}
|
|
|
|
child.on('message', (msg: { type?: string; settings?: unknown }) => {
|
|
if (msg.type === 'admin-settings-update' && msg.settings) {
|
|
latestAdminSettings = msg.settings;
|
|
}
|
|
});
|
|
|
|
return new Promise<number>((resolve) => {
|
|
child.on('error', (err) => {
|
|
process.stderr.write(
|
|
'Error: Failed to start child process: ' + err.message + '\n',
|
|
);
|
|
resolve(1);
|
|
});
|
|
child.on('close', (code) => {
|
|
process.stdin.resume();
|
|
resolve(code ?? 1);
|
|
});
|
|
});
|
|
};
|
|
|
|
while (true) {
|
|
try {
|
|
const exitCode = await runner();
|
|
if (exitCode !== RELAUNCH_EXIT_CODE) {
|
|
process.exit(exitCode);
|
|
}
|
|
} catch (error: unknown) {
|
|
process.stdin.resume();
|
|
process.stderr.write(
|
|
`Fatal error: Failed to relaunch the CLI process.\n${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
} else {
|
|
// --- Heavy Child Process ---
|
|
// Now we can safely import everything.
|
|
const { main } = await import('./src/gemini.js');
|
|
const { FatalError, writeToStderr } = await import(
|
|
'@google/gemini-cli-core'
|
|
);
|
|
const { runExitCleanup } = await import('./src/utils/cleanup.js');
|
|
|
|
main().catch(async (error: unknown) => {
|
|
// Set a timeout to force exit if cleanup hangs
|
|
const cleanupTimeout = setTimeout(() => {
|
|
writeToStderr('Cleanup timed out, forcing exit...\n');
|
|
process.exit(1);
|
|
}, 5000);
|
|
|
|
try {
|
|
await runExitCleanup();
|
|
} catch (cleanupError: unknown) {
|
|
writeToStderr(
|
|
`Error during final cleanup: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}\n`,
|
|
);
|
|
} finally {
|
|
clearTimeout(cleanupTimeout);
|
|
}
|
|
|
|
if (error instanceof FatalError) {
|
|
let errorMessage = error.message;
|
|
if (!process.env['NO_COLOR']) {
|
|
errorMessage = `\x1b[31m${errorMessage}\x1b[0m`;
|
|
}
|
|
writeToStderr(errorMessage + '\n');
|
|
process.exit(error.exitCode);
|
|
}
|
|
|
|
writeToStderr('An unexpected critical error occurred:');
|
|
if (error instanceof Error) {
|
|
writeToStderr(error.stack + '\n');
|
|
} else {
|
|
writeToStderr(String(error) + '\n');
|
|
}
|
|
process.exit(1);
|
|
});
|
|
}
|
|
}
|
|
|
|
run();
|