| |
| |
| |
| |
| |
|
|
| import * as fs from 'fs'; |
| import * as path from 'path'; |
| import { homedir } from 'os'; |
| import { |
| MCPServerConfig, |
| getErrorMessage, |
| BugCommandSettings, |
| TelemetrySettings, |
| AuthType, |
| } from '@google/gemini-cli-core'; |
| import stripJsonComments from 'strip-json-comments'; |
| import { DefaultLight } from '../ui/themes/default-light.js'; |
| import { DefaultDark } from '../ui/themes/default.js'; |
|
|
| 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'); |
|
|
| export enum SettingScope { |
| User = 'User', |
| Workspace = 'Workspace', |
| } |
|
|
| export interface CheckpointingSettings { |
| enabled?: boolean; |
| } |
|
|
| export interface AccessibilitySettings { |
| disableLoadingPhrases?: boolean; |
| } |
|
|
| export interface Settings { |
| theme?: string; |
| selectedAuthType?: AuthType; |
| sandbox?: boolean | string; |
| coreTools?: string[]; |
| excludeTools?: string[]; |
| toolDiscoveryCommand?: string; |
| toolCallCommand?: string; |
| mcpServerCommand?: string; |
| mcpServers?: Record<string, MCPServerConfig>; |
| showMemoryUsage?: boolean; |
| contextFileName?: string | string[]; |
| accessibility?: AccessibilitySettings; |
| telemetry?: TelemetrySettings; |
| usageStatisticsEnabled?: boolean; |
| preferredEditor?: string; |
| bugCommand?: BugCommandSettings; |
| checkpointing?: CheckpointingSettings; |
| autoConfigureMaxOldSpaceSize?: boolean; |
|
|
| |
| fileFiltering?: { |
| respectGitIgnore?: boolean; |
| enableRecursiveFileSearch?: boolean; |
| }; |
|
|
| |
| hideWindowTitle?: boolean; |
|
|
| |
| } |
|
|
| export interface SettingsError { |
| message: string; |
| path: string; |
| } |
|
|
| export interface SettingsFile { |
| settings: Settings; |
| path: string; |
| } |
| export class LoadedSettings { |
| constructor( |
| user: SettingsFile, |
| workspace: SettingsFile, |
| errors: SettingsError[], |
| ) { |
| this.user = user; |
| this.workspace = workspace; |
| this.errors = errors; |
| this._merged = this.computeMergedSettings(); |
| } |
|
|
| readonly user: SettingsFile; |
| readonly workspace: SettingsFile; |
| readonly errors: SettingsError[]; |
|
|
| private _merged: Settings; |
|
|
| get merged(): Settings { |
| return this._merged; |
| } |
|
|
| private computeMergedSettings(): Settings { |
| return { |
| ...this.user.settings, |
| ...this.workspace.settings, |
| }; |
| } |
|
|
| forScope(scope: SettingScope): SettingsFile { |
| switch (scope) { |
| case SettingScope.User: |
| return this.user; |
| case SettingScope.Workspace: |
| return this.workspace; |
| default: |
| throw new Error(`Invalid scope: ${scope}`); |
| } |
| } |
|
|
| setValue( |
| scope: SettingScope, |
| key: keyof Settings, |
| value: string | Record<string, MCPServerConfig> | undefined, |
| ): void { |
| const settingsFile = this.forScope(scope); |
| |
| settingsFile.settings[key] = value; |
| this._merged = this.computeMergedSettings(); |
| saveSettings(settingsFile); |
| } |
| } |
|
|
| function resolveEnvVarsInString(value: string): string { |
| const envVarRegex = /\$(?:(\w+)|{([^}]+)})/g; |
| 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; |
| } |
|
|
| |
| |
| |
| |
| export function loadSettings(workspaceDir: string): LoadedSettings { |
| let userSettings: Settings = {}; |
| let workspaceSettings: Settings = {}; |
| const settingsErrors: SettingsError[] = []; |
|
|
| |
| 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); |
| |
| if (userSettings.theme && userSettings.theme === 'VS') { |
| userSettings.theme = DefaultLight.name; |
| } else if (userSettings.theme && userSettings.theme === 'VS2015') { |
| userSettings.theme = DefaultDark.name; |
| } |
| } |
| } catch (error: unknown) { |
| settingsErrors.push({ |
| message: getErrorMessage(error), |
| path: USER_SETTINGS_PATH, |
| }); |
| } |
|
|
| const workspaceSettingsPath = path.join( |
| workspaceDir, |
| SETTINGS_DIRECTORY_NAME, |
| 'settings.json', |
| ); |
|
|
| |
| try { |
| if (fs.existsSync(workspaceSettingsPath)) { |
| const projectContent = fs.readFileSync(workspaceSettingsPath, 'utf-8'); |
| const parsedWorkspaceSettings = JSON.parse( |
| stripJsonComments(projectContent), |
| ) as Settings; |
| workspaceSettings = resolveEnvVarsInObject(parsedWorkspaceSettings); |
| if (workspaceSettings.theme && workspaceSettings.theme === 'VS') { |
| workspaceSettings.theme = DefaultLight.name; |
| } else if ( |
| workspaceSettings.theme && |
| workspaceSettings.theme === 'VS2015' |
| ) { |
| workspaceSettings.theme = DefaultDark.name; |
| } |
| } |
| } catch (error: unknown) { |
| settingsErrors.push({ |
| message: getErrorMessage(error), |
| path: workspaceSettingsPath, |
| }); |
| } |
|
|
| return new LoadedSettings( |
| { |
| path: USER_SETTINGS_PATH, |
| settings: userSettings, |
| }, |
| { |
| path: workspaceSettingsPath, |
| settings: workspaceSettings, |
| }, |
| settingsErrors, |
| ); |
| } |
|
|
| export function saveSettings(settingsFile: SettingsFile): void { |
| try { |
| |
| const dirPath = path.dirname(settingsFile.path); |
| if (!fs.existsSync(dirPath)) { |
| fs.mkdirSync(dirPath, { recursive: true }); |
| } |
|
|
| fs.writeFileSync( |
| settingsFile.path, |
| JSON.stringify(settingsFile.settings, null, 2), |
| 'utf-8', |
| ); |
| } catch (error) { |
| console.error('Error saving user settings file:', error); |
| } |
| } |
|
|