mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-11 22:51:00 -07:00
155 lines
4.2 KiB
TypeScript
155 lines
4.2 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import { homedir } from 'node:os';
|
|
|
|
import type { MCPServerConfig } from '@google/gemini-cli-core';
|
|
import {
|
|
getErrorMessage,
|
|
type TelemetrySettings,
|
|
} from '@google/gemini-cli-core';
|
|
import stripJsonComments from 'strip-json-comments';
|
|
|
|
export const SETTINGS_DIRECTORY_NAME = '.gemini';
|
|
export const USER_SETTINGS_DIR = path.join(homedir(), SETTINGS_DIRECTORY_NAME);
|
|
export const USER_SETTINGS_PATH = path.join(USER_SETTINGS_DIR, 'settings.json');
|
|
|
|
// Reconcile with https://github.com/google-gemini/gemini-cli/blob/b09bc6656080d4d12e1d06734aae2ec33af5c1ed/packages/cli/src/config/settings.ts#L53
|
|
export interface Settings {
|
|
mcpServers?: Record<string, MCPServerConfig>;
|
|
coreTools?: string[];
|
|
excludeTools?: string[];
|
|
telemetry?: TelemetrySettings;
|
|
showMemoryUsage?: boolean;
|
|
checkpointing?: CheckpointingSettings;
|
|
|
|
// Git-aware file filtering settings
|
|
fileFiltering?: {
|
|
respectGitIgnore?: boolean;
|
|
enableRecursiveFileSearch?: boolean;
|
|
};
|
|
}
|
|
|
|
export interface SettingsError {
|
|
message: string;
|
|
path: string;
|
|
}
|
|
|
|
export interface CheckpointingSettings {
|
|
enabled?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Loads settings from user and workspace directories.
|
|
* Project settings override user settings.
|
|
*
|
|
* How is it different to gemini-cli/cli: Returns already merged settings rather
|
|
* than `LoadedSettings` (unnecessary since we are not modifying users
|
|
* settings.json).
|
|
*/
|
|
export function loadSettings(workspaceDir: string): Settings {
|
|
let userSettings: Settings = {};
|
|
let workspaceSettings: Settings = {};
|
|
const settingsErrors: SettingsError[] = [];
|
|
|
|
// Load user settings
|
|
try {
|
|
if (fs.existsSync(USER_SETTINGS_PATH)) {
|
|
const userContent = fs.readFileSync(USER_SETTINGS_PATH, 'utf-8');
|
|
const parsedUserSettings = JSON.parse(
|
|
stripJsonComments(userContent),
|
|
) as Settings;
|
|
userSettings = resolveEnvVarsInObject(parsedUserSettings);
|
|
}
|
|
} catch (error: unknown) {
|
|
settingsErrors.push({
|
|
message: getErrorMessage(error),
|
|
path: USER_SETTINGS_PATH,
|
|
});
|
|
}
|
|
|
|
const workspaceSettingsPath = path.join(
|
|
workspaceDir,
|
|
SETTINGS_DIRECTORY_NAME,
|
|
'settings.json',
|
|
);
|
|
|
|
// Load workspace settings
|
|
try {
|
|
if (fs.existsSync(workspaceSettingsPath)) {
|
|
const projectContent = fs.readFileSync(workspaceSettingsPath, 'utf-8');
|
|
const parsedWorkspaceSettings = JSON.parse(
|
|
stripJsonComments(projectContent),
|
|
) as Settings;
|
|
workspaceSettings = resolveEnvVarsInObject(parsedWorkspaceSettings);
|
|
}
|
|
} catch (error: unknown) {
|
|
settingsErrors.push({
|
|
message: getErrorMessage(error),
|
|
path: workspaceSettingsPath,
|
|
});
|
|
}
|
|
|
|
if (settingsErrors.length > 0) {
|
|
console.error('Errors loading settings:');
|
|
for (const error of settingsErrors) {
|
|
console.error(` Path: ${error.path}`);
|
|
console.error(` Message: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// If there are overlapping keys, the values of workspaceSettings will
|
|
// override values from userSettings
|
|
return {
|
|
...userSettings,
|
|
...workspaceSettings,
|
|
};
|
|
}
|
|
|
|
function resolveEnvVarsInString(value: string): string {
|
|
const envVarRegex = /\$(?:(\w+)|{([^}]+)})/g; // Find $VAR_NAME or ${VAR_NAME}
|
|
return value.replace(envVarRegex, (match, varName1, varName2) => {
|
|
const varName = varName1 || varName2;
|
|
if (process && process.env && typeof process.env[varName] === 'string') {
|
|
return process.env[varName]!;
|
|
}
|
|
return match;
|
|
});
|
|
}
|
|
|
|
function resolveEnvVarsInObject<T>(obj: T): T {
|
|
if (
|
|
obj === null ||
|
|
obj === undefined ||
|
|
typeof obj === 'boolean' ||
|
|
typeof obj === 'number'
|
|
) {
|
|
return obj;
|
|
}
|
|
|
|
if (typeof obj === 'string') {
|
|
return resolveEnvVarsInString(obj) as unknown as T;
|
|
}
|
|
|
|
if (Array.isArray(obj)) {
|
|
return obj.map((item) => resolveEnvVarsInObject(item)) as unknown as T;
|
|
}
|
|
|
|
if (typeof obj === 'object') {
|
|
const newObj = { ...obj } as T;
|
|
for (const key in newObj) {
|
|
if (Object.prototype.hasOwnProperty.call(newObj, key)) {
|
|
newObj[key] = resolveEnvVarsInObject(newObj[key]);
|
|
}
|
|
}
|
|
return newObj;
|
|
}
|
|
|
|
return obj;
|
|
}
|