diff --git a/integration-tests/globalSetup.ts b/integration-tests/globalSetup.ts index ccd26bfe622..180728e09ed 100644 --- a/integration-tests/globalSetup.ts +++ b/integration-tests/globalSetup.ts @@ -19,23 +19,14 @@ import { } from 'node:fs/promises'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; -import * as os from 'node:os'; - -import { - GEMINI_CONFIG_DIR, - DEFAULT_CONTEXT_FILENAME, -} from '../packages/core/src/tools/memoryTool.js'; +import { getGlobalMemoryFilePath } from '../packages/core/src/tools/memoryTool.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); const rootDir = join(__dirname, '..'); const integrationTestsDir = join(rootDir, '.integration-tests'); let runDir = ''; // Make runDir accessible in teardown -const memoryFilePath = join( - os.homedir(), - GEMINI_CONFIG_DIR, - DEFAULT_CONTEXT_FILENAME, -); +const memoryFilePath = getGlobalMemoryFilePath(); let originalMemoryContent: string | null = null; export async function setup() { diff --git a/integration-tests/test-helper.ts b/integration-tests/test-helper.ts index aa50e0731e9..60d6e218add 100644 --- a/integration-tests/test-helper.ts +++ b/integration-tests/test-helper.ts @@ -15,6 +15,7 @@ import fs from 'node:fs'; import * as pty from '@lydell/node-pty'; import stripAnsi from 'strip-ansi'; import * as os from 'node:os'; +import { GEMINI_DIR } from '../packages/core/src/utils/paths.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -244,7 +245,7 @@ export class TestRig { mkdirSync(this.testDir, { recursive: true }); // Create a settings file to point the CLI to the local collector - const geminiDir = join(this.testDir, '.gemini'); + const geminiDir = join(this.testDir, GEMINI_DIR); mkdirSync(geminiDir, { recursive: true }); // In sandbox mode, use an absolute path for telemetry inside the container // The container mounts the test directory at the same path as the host diff --git a/packages/a2a-server/src/config/config.ts b/packages/a2a-server/src/config/config.ts index 4d53f7c1b38..cc3af7f58be 100644 --- a/packages/a2a-server/src/config/config.ts +++ b/packages/a2a-server/src/config/config.ts @@ -17,7 +17,7 @@ import { FileDiscoveryService, ApprovalMode, loadServerHierarchicalMemory, - GEMINI_CONFIG_DIR, + GEMINI_DIR, DEFAULT_GEMINI_EMBEDDING_MODEL, DEFAULT_GEMINI_MODEL, type GeminiCLIExtension, @@ -176,7 +176,7 @@ 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_CONFIG_DIR, '.env'); + const geminiEnvPath = path.join(currentDir, GEMINI_DIR, '.env'); if (fs.existsSync(geminiEnvPath)) { return geminiEnvPath; } @@ -187,11 +187,7 @@ function findEnvFile(startDir: string): string | null { const parentDir = path.dirname(currentDir); if (parentDir === currentDir || !parentDir) { // check .env under home as fallback, again preferring gemini-specific .env - const homeGeminiEnvPath = path.join( - process.cwd(), - GEMINI_CONFIG_DIR, - '.env', - ); + const homeGeminiEnvPath = path.join(process.cwd(), GEMINI_DIR, '.env'); if (fs.existsSync(homeGeminiEnvPath)) { return homeGeminiEnvPath; } diff --git a/packages/a2a-server/src/config/extension.ts b/packages/a2a-server/src/config/extension.ts index 4932e241224..f56eadfb0c0 100644 --- a/packages/a2a-server/src/config/extension.ts +++ b/packages/a2a-server/src/config/extension.ts @@ -6,17 +6,18 @@ // Copied exactly from packages/cli/src/config/extension.ts, last PR #1026 -import type { - MCPServerConfig, - ExtensionInstallMetadata, - GeminiCLIExtension, +import { + GEMINI_DIR, + type MCPServerConfig, + type ExtensionInstallMetadata, + type GeminiCLIExtension, } from '@google/gemini-cli-core'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; import { logger } from '../utils/logger.js'; -export const EXTENSIONS_DIRECTORY_NAME = path.join('.gemini', 'extensions'); +export const EXTENSIONS_DIRECTORY_NAME = path.join(GEMINI_DIR, 'extensions'); export const EXTENSIONS_CONFIG_FILENAME = 'gemini-extension.json'; export const INSTALL_METADATA_FILENAME = '.gemini-extension-install.json'; diff --git a/packages/a2a-server/src/config/settings.ts b/packages/a2a-server/src/config/settings.ts index c5bb8cd7b6f..ad7ee213e69 100644 --- a/packages/a2a-server/src/config/settings.ts +++ b/packages/a2a-server/src/config/settings.ts @@ -10,13 +10,13 @@ import { homedir } from 'node:os'; import type { MCPServerConfig } from '@google/gemini-cli-core'; import { + GEMINI_DIR, 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_DIR = path.join(homedir(), GEMINI_DIR); 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 @@ -76,7 +76,7 @@ export function loadSettings(workspaceDir: string): Settings { const workspaceSettingsPath = path.join( workspaceDir, - SETTINGS_DIRECTORY_NAME, + GEMINI_DIR, 'settings.json', ); diff --git a/packages/cli/src/commands/mcp/list.test.ts b/packages/cli/src/commands/mcp/list.test.ts index 3a191fc418f..d2fe3afbe32 100644 --- a/packages/cli/src/commands/mcp/list.test.ts +++ b/packages/cli/src/commands/mcp/list.test.ts @@ -32,7 +32,7 @@ vi.mock('@google/gemini-cli-core', () => ({ getWorkspaceSettingsPath: () => '/tmp/gemini/workspace-settings.json', getProjectTempDir: () => '/test/home/.gemini/tmp/mocked_hash', })), - GEMINI_CONFIG_DIR: '.gemini', + GEMINI_DIR: '.gemini', getErrorMessage: (e: unknown) => (e instanceof Error ? e.message : String(e)), })); vi.mock('@modelcontextprotocol/sdk/client/index.js'); diff --git a/packages/cli/src/commands/mcp/remove.test.ts b/packages/cli/src/commands/mcp/remove.test.ts index 3803a0b77c1..3886832f134 100644 --- a/packages/cli/src/commands/mcp/remove.test.ts +++ b/packages/cli/src/commands/mcp/remove.test.ts @@ -11,6 +11,7 @@ import { removeCommand } from './remove.js'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; +import { GEMINI_DIR } from '@google/gemini-cli-core'; vi.mock('fs/promises', () => ({ readFile: vi.fn(), @@ -80,7 +81,7 @@ describe('mcp remove command', () => { vi.restoreAllMocks(); tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mcp-remove-test-')); - settingsDir = path.join(tempDir, '.gemini'); + settingsDir = path.join(tempDir, GEMINI_DIR); settingsPath = path.join(settingsDir, 'settings.json'); fs.mkdirSync(settingsDir, { recursive: true }); diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index fb63a6a14dc..19756f939a2 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -1171,7 +1171,10 @@ describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => { // 3. Spies on console functions (for logger output) are correctly set up if needed. // Example of a previously failing test structure: it.skip('should correctly use mocked homedir for global path', async () => { - const MOCK_GEMINI_DIR_LOCAL = path.join('/mock/home/user', '.gemini'); + const MOCK_GEMINI_DIR_LOCAL = path.join( + '/mock/home/user', + ServerConfig.GEMINI_DIR, + ); const MOCK_GLOBAL_PATH_LOCAL = path.join( MOCK_GEMINI_DIR_LOCAL, 'GEMINI.md', diff --git a/packages/cli/src/config/extensions/extensionEnablement.test.ts b/packages/cli/src/config/extensions/extensionEnablement.test.ts index 8b2adb4ae6d..6887c73dc6d 100644 --- a/packages/cli/src/config/extensions/extensionEnablement.test.ts +++ b/packages/cli/src/config/extensions/extensionEnablement.test.ts @@ -9,7 +9,8 @@ import fs from 'node:fs'; import os from 'node:os'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { ExtensionEnablementManager, Override } from './extensionEnablement.js'; -import type { GeminiCLIExtension } from '@google/gemini-cli-core'; + +import { GEMINI_DIR, type GeminiCLIExtension } from '@google/gemini-cli-core'; // Helper to create a temporary directory for testing function createTestDir() { @@ -27,7 +28,7 @@ let manager: ExtensionEnablementManager; describe('ExtensionEnablementManager', () => { beforeEach(() => { testDir = createTestDir(); - configDir = path.join(testDir.path, '.gemini'); + configDir = path.join(testDir.path, GEMINI_DIR); manager = new ExtensionEnablementManager(configDir); }); diff --git a/packages/cli/src/config/settings.test.ts b/packages/cli/src/config/settings.test.ts index 2baf5c8b65e..306c342132f 100644 --- a/packages/cli/src/config/settings.test.ts +++ b/packages/cli/src/config/settings.test.ts @@ -59,7 +59,6 @@ import { USER_SETTINGS_PATH, // This IS the mocked path. getSystemSettingsPath, getSystemDefaultsPath, - SETTINGS_DIRECTORY_NAME, // This is from the original module, but used by the mock. migrateSettingsToV1, needsMigration, type Settings, @@ -70,10 +69,10 @@ import { import { FatalConfigError, GEMINI_DIR } from '@google/gemini-cli-core'; const MOCK_WORKSPACE_DIR = '/mock/workspace'; -// Use the (mocked) SETTINGS_DIRECTORY_NAME for consistency +// Use the (mocked) GEMINI_DIR for consistency const MOCK_WORKSPACE_SETTINGS_PATH = pathActual.join( MOCK_WORKSPACE_DIR, - SETTINGS_DIRECTORY_NAME, + GEMINI_DIR, 'settings.json', ); diff --git a/packages/cli/src/config/settings.ts b/packages/cli/src/config/settings.ts index e9047c699d4..b5bc4be045f 100644 --- a/packages/cli/src/config/settings.ts +++ b/packages/cli/src/config/settings.ts @@ -11,7 +11,7 @@ import * as dotenv from 'dotenv'; import process from 'node:process'; import { FatalConfigError, - GEMINI_CONFIG_DIR as GEMINI_DIR, + GEMINI_DIR, getErrorMessage, Storage, } from '@google/gemini-cli-core'; @@ -49,8 +49,6 @@ function getMergeStrategyForPath(path: string[]): MergeStrategy | undefined { export type { Settings, MemoryImportFormat }; -export const SETTINGS_DIRECTORY_NAME = '.gemini'; - export const USER_SETTINGS_PATH = Storage.getGlobalSettingsPath(); export const USER_SETTINGS_DIR = path.dirname(USER_SETTINGS_PATH); export const DEFAULT_EXCLUDED_ENV_VARS = ['DEBUG', 'DEBUG_MODE']; diff --git a/packages/cli/src/config/trustedFolders.ts b/packages/cli/src/config/trustedFolders.ts index c2a74b3a01f..2b592b5cb84 100644 --- a/packages/cli/src/config/trustedFolders.ts +++ b/packages/cli/src/config/trustedFolders.ts @@ -12,13 +12,13 @@ import { getErrorMessage, isWithinRoot, ideContextStore, + GEMINI_DIR, } from '@google/gemini-cli-core'; import type { Settings } from './settings.js'; import stripJsonComments from 'strip-json-comments'; export const TRUSTED_FOLDERS_FILENAME = 'trustedFolders.json'; -export const SETTINGS_DIRECTORY_NAME = '.gemini'; -export const USER_SETTINGS_DIR = path.join(homedir(), SETTINGS_DIRECTORY_NAME); +export const USER_SETTINGS_DIR = path.join(homedir(), GEMINI_DIR); export function getTrustedFoldersPath(): string { if (process.env['GEMINI_CLI_TRUSTED_FOLDERS_PATH']) { diff --git a/packages/cli/src/services/FileCommandLoader.test.ts b/packages/cli/src/services/FileCommandLoader.test.ts index 0f2b4d5683c..7d853b19ef7 100644 --- a/packages/cli/src/services/FileCommandLoader.test.ts +++ b/packages/cli/src/services/FileCommandLoader.test.ts @@ -6,7 +6,7 @@ import * as path from 'node:path'; import type { Config } from '@google/gemini-cli-core'; -import { Storage } from '@google/gemini-cli-core'; +import { GEMINI_DIR, Storage } from '@google/gemini-cli-core'; import mock from 'mock-fs'; import { FileCommandLoader } from './FileCommandLoader.js'; import { assert, vi } from 'vitest'; @@ -529,7 +529,9 @@ describe('FileCommandLoader', () => { ).getProjectCommandsDir(); const extensionDir = path.join( process.cwd(), - '.gemini/extensions/test-ext', + GEMINI_DIR, + 'extensions', + 'test-ext', ); mock({ @@ -582,7 +584,9 @@ describe('FileCommandLoader', () => { ).getProjectCommandsDir(); const extensionDir = path.join( process.cwd(), - '.gemini/extensions/test-ext', + GEMINI_DIR, + 'extensions', + 'test-ext', ); mock({ @@ -678,11 +682,15 @@ describe('FileCommandLoader', () => { it('only loads commands from active extensions', async () => { const extensionDir1 = path.join( process.cwd(), - '.gemini/extensions/active-ext', + GEMINI_DIR, + 'extensions', + 'active-ext', ); const extensionDir2 = path.join( process.cwd(), - '.gemini/extensions/inactive-ext', + GEMINI_DIR, + 'extensions', + 'inactive-ext', ); mock({ @@ -737,7 +745,9 @@ describe('FileCommandLoader', () => { it('handles missing extension commands directory gracefully', async () => { const extensionDir = path.join( process.cwd(), - '.gemini/extensions/no-commands', + GEMINI_DIR, + 'extensions', + 'no-commands', ); mock({ @@ -769,7 +779,12 @@ describe('FileCommandLoader', () => { }); it('handles nested command structure in extensions', async () => { - const extensionDir = path.join(process.cwd(), '.gemini/extensions/a'); + const extensionDir = path.join( + process.cwd(), + GEMINI_DIR, + 'extensions', + 'a', + ); mock({ [extensionDir]: { diff --git a/packages/cli/src/ui/commands/restoreCommand.test.ts b/packages/cli/src/ui/commands/restoreCommand.test.ts index 3e6199f1134..f1c73e3c96d 100644 --- a/packages/cli/src/ui/commands/restoreCommand.test.ts +++ b/packages/cli/src/ui/commands/restoreCommand.test.ts @@ -11,7 +11,11 @@ import * as path from 'node:path'; import { restoreCommand } from './restoreCommand.js'; import { type CommandContext } from './types.js'; import { createMockCommandContext } from '../../test-utils/mockCommandContext.js'; -import type { Config, GitService } from '@google/gemini-cli-core'; +import { + GEMINI_DIR, + type Config, + type GitService, +} from '@google/gemini-cli-core'; describe('restoreCommand', () => { let mockContext: CommandContext; @@ -26,7 +30,7 @@ describe('restoreCommand', () => { testRootDir = await fs.mkdtemp( path.join(os.tmpdir(), 'restore-command-test-'), ); - geminiTempDir = path.join(testRootDir, '.gemini'); + geminiTempDir = path.join(testRootDir, GEMINI_DIR); checkpointsDir = path.join(geminiTempDir, 'checkpoints'); // The command itself creates this, but for tests it's easier to have it ready. // Some tests might remove it to test error paths. diff --git a/packages/cli/src/ui/components/Notifications.tsx b/packages/cli/src/ui/components/Notifications.tsx index 4d225cf6c13..a322dab86bc 100644 --- a/packages/cli/src/ui/components/Notifications.tsx +++ b/packages/cli/src/ui/components/Notifications.tsx @@ -11,10 +11,11 @@ import { theme } from '../semantic-colors.js'; import { StreamingState } from '../types.js'; import { UpdateNotification } from './UpdateNotification.js'; +import { GEMINI_DIR } from '@google/gemini-cli-core'; import { homedir } from 'node:os'; import path from 'node:path'; -const settingsPath = path.join(homedir(), '.gemini', 'settings.json'); +const settingsPath = path.join(homedir(), GEMINI_DIR, 'settings.json'); export const Notifications = () => { const { startupWarnings } = useAppContext(); diff --git a/packages/cli/src/ui/hooks/useFlickerDetector.test.ts b/packages/cli/src/ui/hooks/useFlickerDetector.test.ts index 5ccccc111c7..ffa1923a0dd 100644 --- a/packages/cli/src/ui/hooks/useFlickerDetector.test.ts +++ b/packages/cli/src/ui/hooks/useFlickerDetector.test.ts @@ -19,6 +19,7 @@ vi.mock('../contexts/ConfigContext.js'); vi.mock('../contexts/UIStateContext.js'); vi.mock('@google/gemini-cli-core', () => ({ recordFlickerFrame: vi.fn(), + GEMINI_DIR: '.gemini', })); vi.mock('ink', async (importOriginal) => { const original = await importOriginal(); diff --git a/packages/cli/src/ui/hooks/useShellHistory.test.ts b/packages/cli/src/ui/hooks/useShellHistory.test.ts index adc8eaa6d95..ccb4bb7b6d7 100644 --- a/packages/cli/src/ui/hooks/useShellHistory.test.ts +++ b/packages/cli/src/ui/hooks/useShellHistory.test.ts @@ -10,30 +10,34 @@ import * as fs from 'node:fs/promises'; import * as path from 'node:path'; import * as os from 'node:os'; import * as crypto from 'node:crypto'; +import { GEMINI_DIR } from '@google/gemini-cli-core'; -vi.mock('fs/promises', () => ({ +vi.mock('node:fs/promises', () => ({ readFile: vi.fn(), writeFile: vi.fn(), mkdir: vi.fn(), })); -vi.mock('os'); -vi.mock('crypto'); -vi.mock('fs', async (importOriginal) => { - const actualFs = await importOriginal(); +vi.mock('node:os'); +vi.mock('node:crypto'); +vi.mock('node:fs', async (importOriginal) => { + const actualFs = await importOriginal(); return { ...actualFs, mkdirSync: vi.fn(), }; }); -vi.mock('@google/gemini-cli-core', () => { +vi.mock('@google/gemini-cli-core', async (importOriginal) => { + const actual = + await importOriginal(); + const path = await import('node:path'); class Storage { getProjectTempDir(): string { - return path.join('/test/home/', '.gemini', 'tmp', 'mocked_hash'); + return path.join('/test/home/', actual.GEMINI_DIR, 'tmp', 'mocked_hash'); } getHistoryFilePath(): string { return path.join( '/test/home/', - '.gemini', + actual.GEMINI_DIR, 'tmp', 'mocked_hash', 'shell_history', @@ -41,6 +45,7 @@ vi.mock('@google/gemini-cli-core', () => { } } return { + ...actual, isNodeError: (err: unknown): err is NodeJS.ErrnoException => typeof err === 'object' && err !== null && 'code' in err, Storage, @@ -53,7 +58,7 @@ const MOCKED_PROJECT_HASH = 'mocked_hash'; const MOCKED_HISTORY_DIR = path.join( MOCKED_HOME_DIR, - '.gemini', + GEMINI_DIR, 'tmp', MOCKED_PROJECT_HASH, ); diff --git a/packages/cli/src/utils/sandbox.ts b/packages/cli/src/utils/sandbox.ts index 93a3ee4cc19..df70ca12183 100644 --- a/packages/cli/src/utils/sandbox.ts +++ b/packages/cli/src/utils/sandbox.ts @@ -11,13 +11,10 @@ import fs from 'node:fs'; import { readFile } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import { quote, parse } from 'shell-quote'; -import { - USER_SETTINGS_DIR, - SETTINGS_DIRECTORY_NAME, -} from '../config/settings.js'; +import { USER_SETTINGS_DIR } from '../config/settings.js'; import { promisify } from 'node:util'; import type { Config, SandboxConfig } from '@google/gemini-cli-core'; -import { FatalSandboxError } from '@google/gemini-cli-core'; +import { FatalSandboxError, GEMINI_DIR } from '@google/gemini-cli-core'; import { ConsolePatcher } from '../ui/utils/ConsolePatcher.js'; import { randomBytes } from 'node:crypto'; @@ -156,10 +153,7 @@ function entrypoint(workdir: string, cliArgs: string[]): string[] { shellCmds.push(`export PYTHONPATH="$PYTHONPATH${pythonPathSuffix}";`); } - const projectSandboxBashrc = path.join( - SETTINGS_DIRECTORY_NAME, - 'sandbox.bashrc', - ); + const projectSandboxBashrc = path.join(GEMINI_DIR, 'sandbox.bashrc'); if (fs.existsSync(projectSandboxBashrc)) { shellCmds.push(`source ${getContainerPath(projectSandboxBashrc)};`); } @@ -211,10 +205,7 @@ export async function start_sandbox( ); // if profile name is not recognized, then look for file under project settings directory if (!BUILTIN_SEATBELT_PROFILES.includes(profile)) { - profileFile = path.join( - SETTINGS_DIRECTORY_NAME, - `sandbox-macos-${profile}.sb`, - ); + profileFile = path.join(GEMINI_DIR, `sandbox-macos-${profile}.sb`); } if (!fs.existsSync(profileFile)) { throw new FatalSandboxError( @@ -359,7 +350,7 @@ export async function start_sandbox( const gcPath = fs.realpathSync(process.argv[1]); const projectSandboxDockerfile = path.join( - SETTINGS_DIRECTORY_NAME, + GEMINI_DIR, 'sandbox.Dockerfile', ); const isCustomProjectSandbox = fs.existsSync(projectSandboxDockerfile); @@ -383,7 +374,7 @@ export async function start_sandbox( // if project folder has sandbox.Dockerfile under project settings folder, use that let buildArgs = ''; const projectSandboxDockerfile = path.join( - SETTINGS_DIRECTORY_NAME, + GEMINI_DIR, 'sandbox.Dockerfile', ); if (isCustomProjectSandbox) { @@ -441,7 +432,7 @@ export async function start_sandbox( // note user/home changes inside sandbox and we mount at BOTH paths for consistency const userSettingsDirOnHost = USER_SETTINGS_DIR; const userSettingsDirInSandbox = getContainerPath( - `/home/node/${SETTINGS_DIRECTORY_NAME}`, + `/home/node/${GEMINI_DIR}`, ); if (!fs.existsSync(userSettingsDirOnHost)) { fs.mkdirSync(userSettingsDirOnHost); @@ -665,10 +656,7 @@ export async function start_sandbox( ?.toLowerCase() .startsWith(workdir.toLowerCase()) ) { - const sandboxVenvPath = path.resolve( - SETTINGS_DIRECTORY_NAME, - 'sandbox.venv', - ); + const sandboxVenvPath = path.resolve(GEMINI_DIR, 'sandbox.venv'); if (!fs.existsSync(sandboxVenvPath)) { fs.mkdirSync(sandboxVenvPath, { recursive: true }); } diff --git a/packages/core/src/code_assist/oauth-credential-storage.ts b/packages/core/src/code_assist/oauth-credential-storage.ts index 30c940628b5..b1ed9b0e82d 100644 --- a/packages/core/src/code_assist/oauth-credential-storage.ts +++ b/packages/core/src/code_assist/oauth-credential-storage.ts @@ -11,8 +11,8 @@ import type { OAuthCredentials } from '../mcp/token-storage/types.js'; import * as path from 'node:path'; import * as os from 'node:os'; import { promises as fs } from 'node:fs'; +import { GEMINI_DIR } from '../utils/paths.js'; -const GEMINI_DIR = '.gemini'; const KEYCHAIN_SERVICE_NAME = 'gemini-cli-oauth'; const MAIN_ACCOUNT_KEY = 'main-account'; diff --git a/packages/core/src/code_assist/oauth2.test.ts b/packages/core/src/code_assist/oauth2.test.ts index 50c0f5e3514..d089440e16b 100644 --- a/packages/core/src/code_assist/oauth2.test.ts +++ b/packages/core/src/code_assist/oauth2.test.ts @@ -25,6 +25,7 @@ import { AuthType } from '../core/contentGenerator.js'; import type { Config } from '../config/config.js'; import readline from 'node:readline'; import { FORCE_ENCRYPTED_FILE_ENV_VAR } from '../mcp/token-storage/index.js'; +import { GEMINI_DIR } from '../utils/paths.js'; vi.mock('os', async (importOriginal) => { const os = await importOriginal(); @@ -182,7 +183,7 @@ describe('oauth2', () => { // Verify Google Account was cached const googleAccountPath = path.join( tempHomeDir, - '.gemini', + GEMINI_DIR, 'google_accounts.json', ); expect(fs.existsSync(googleAccountPath)).toBe(true); @@ -290,7 +291,11 @@ describe('oauth2', () => { it('should attempt to load cached credentials first', async () => { const cachedCreds = { refresh_token: 'cached-token' }; - const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json'); + const credsPath = path.join( + tempHomeDir, + GEMINI_DIR, + 'oauth_creds.json', + ); await fs.promises.mkdir(path.dirname(credsPath), { recursive: true }); await fs.promises.writeFile(credsPath, JSON.stringify(cachedCreds)); @@ -328,7 +333,11 @@ describe('oauth2', () => { await getOauthClient(AuthType.CLOUD_SHELL, mockConfig); - const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json'); + const credsPath = path.join( + tempHomeDir, + GEMINI_DIR, + 'oauth_creds.json', + ); expect(fs.existsSync(credsPath)).toBe(false); }); @@ -355,7 +364,7 @@ describe('oauth2', () => { const defaultCreds = { refresh_token: 'default-cached-token' }; const defaultCredsPath = path.join( tempHomeDir, - '.gemini', + GEMINI_DIR, 'oauth_creds.json', ); await fs.promises.mkdir(path.dirname(defaultCredsPath), { @@ -463,7 +472,7 @@ describe('oauth2', () => { // Verify Google Account was cached const googleAccountPath = path.join( tempHomeDir, - '.gemini', + GEMINI_DIR, 'google_accounts.json', ); const cachedContent = fs.readFileSync(googleAccountPath, 'utf-8'); @@ -493,7 +502,11 @@ describe('oauth2', () => { // Make it fall through to cached credentials path const cachedCreds = { refresh_token: 'cached-token' }; - const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json'); + const credsPath = path.join( + tempHomeDir, + GEMINI_DIR, + 'oauth_creds.json', + ); await fs.promises.mkdir(path.dirname(credsPath), { recursive: true }); await fs.promises.writeFile(credsPath, JSON.stringify(cachedCreds)); @@ -524,7 +537,11 @@ describe('oauth2', () => { // Make it fall through to cached credentials path const cachedCreds = { refresh_token: 'cached-token' }; - const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json'); + const credsPath = path.join( + tempHomeDir, + GEMINI_DIR, + 'oauth_creds.json', + ); await fs.promises.mkdir(path.dirname(credsPath), { recursive: true }); await fs.promises.writeFile(credsPath, JSON.stringify(cachedCreds)); @@ -916,13 +933,17 @@ describe('oauth2', () => { describe('clearCachedCredentialFile', () => { it('should clear cached credentials and Google account', async () => { const cachedCreds = { refresh_token: 'test-token' }; - const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json'); + const credsPath = path.join( + tempHomeDir, + GEMINI_DIR, + 'oauth_creds.json', + ); await fs.promises.mkdir(path.dirname(credsPath), { recursive: true }); await fs.promises.writeFile(credsPath, JSON.stringify(cachedCreds)); const googleAccountPath = path.join( tempHomeDir, - '.gemini', + GEMINI_DIR, 'google_accounts.json', ); const accountData = { active: 'test@example.com', old: [] }; @@ -965,7 +986,11 @@ describe('oauth2', () => { ); // Pre-populate credentials to make getOauthClient resolve quickly - const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json'); + const credsPath = path.join( + tempHomeDir, + GEMINI_DIR, + 'oauth_creds.json', + ); await fs.promises.mkdir(path.dirname(credsPath), { recursive: true }); await fs.promises.writeFile( credsPath, @@ -1104,7 +1129,7 @@ describe('oauth2', () => { expect( OAuthCredentialStorage.saveCredentials as Mock, ).toHaveBeenCalledWith(mockTokens); - const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json'); + const credsPath = path.join(tempHomeDir, GEMINI_DIR, 'oauth_creds.json'); expect(fs.existsSync(credsPath)).toBe(false); }); @@ -1120,7 +1145,7 @@ describe('oauth2', () => { // Create a dummy unencrypted credential file. // If the logic is correct, this file should be ignored. const unencryptedCreds = { refresh_token: 'unencrypted-token' }; - const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json'); + const credsPath = path.join(tempHomeDir, GEMINI_DIR, 'oauth_creds.json'); await fs.promises.mkdir(path.dirname(credsPath), { recursive: true }); await fs.promises.writeFile(credsPath, JSON.stringify(unencryptedCreds)); @@ -1150,7 +1175,7 @@ describe('oauth2', () => { ); // Create a dummy unencrypted credential file. It should not be deleted. - const credsPath = path.join(tempHomeDir, '.gemini', 'oauth_creds.json'); + const credsPath = path.join(tempHomeDir, GEMINI_DIR, 'oauth_creds.json'); await fs.promises.mkdir(path.dirname(credsPath), { recursive: true }); await fs.promises.writeFile(credsPath, '{}'); diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index ee5a5df6755..e7ddd738ef8 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -81,7 +81,7 @@ vi.mock('../tools/memoryTool', () => ({ setGeminiMdFilename: vi.fn(), getCurrentGeminiMdFilename: vi.fn(() => 'GEMINI.md'), // Mock the original filename DEFAULT_CONTEXT_FILENAME: 'GEMINI.md', - GEMINI_CONFIG_DIR: '.gemini', + GEMINI_DIR: '.gemini', })); vi.mock('../core/contentGenerator.js'); diff --git a/packages/core/src/config/storage.test.ts b/packages/core/src/config/storage.test.ts index e93bb375c83..075eb9fb162 100644 --- a/packages/core/src/config/storage.test.ts +++ b/packages/core/src/config/storage.test.ts @@ -17,10 +17,11 @@ vi.mock('fs', async (importOriginal) => { }); import { Storage } from './storage.js'; +import { GEMINI_DIR } from '../utils/paths.js'; describe('Storage – getGlobalSettingsPath', () => { it('returns path to ~/.gemini/settings.json', () => { - const expected = path.join(os.homedir(), '.gemini', 'settings.json'); + const expected = path.join(os.homedir(), GEMINI_DIR, 'settings.json'); expect(Storage.getGlobalSettingsPath()).toBe(expected); }); }); @@ -30,31 +31,31 @@ describe('Storage – additional helpers', () => { const storage = new Storage(projectRoot); it('getWorkspaceSettingsPath returns project/.gemini/settings.json', () => { - const expected = path.join(projectRoot, '.gemini', 'settings.json'); + const expected = path.join(projectRoot, GEMINI_DIR, 'settings.json'); expect(storage.getWorkspaceSettingsPath()).toBe(expected); }); it('getUserCommandsDir returns ~/.gemini/commands', () => { - const expected = path.join(os.homedir(), '.gemini', 'commands'); + const expected = path.join(os.homedir(), GEMINI_DIR, 'commands'); expect(Storage.getUserCommandsDir()).toBe(expected); }); it('getProjectCommandsDir returns project/.gemini/commands', () => { - const expected = path.join(projectRoot, '.gemini', 'commands'); + const expected = path.join(projectRoot, GEMINI_DIR, 'commands'); expect(storage.getProjectCommandsDir()).toBe(expected); }); it('getMcpOAuthTokensPath returns ~/.gemini/mcp-oauth-tokens.json', () => { const expected = path.join( os.homedir(), - '.gemini', + GEMINI_DIR, 'mcp-oauth-tokens.json', ); expect(Storage.getMcpOAuthTokensPath()).toBe(expected); }); it('getGlobalBinDir returns ~/.gemini/tmp/bin', () => { - const expected = path.join(os.homedir(), '.gemini', 'tmp', 'bin'); + const expected = path.join(os.homedir(), GEMINI_DIR, 'tmp', 'bin'); expect(Storage.getGlobalBinDir()).toBe(expected); }); }); diff --git a/packages/core/src/config/storage.ts b/packages/core/src/config/storage.ts index 354d51f1c11..18c0a4faee4 100644 --- a/packages/core/src/config/storage.ts +++ b/packages/core/src/config/storage.ts @@ -8,8 +8,8 @@ import * as path from 'node:path'; import * as os from 'node:os'; import * as crypto from 'node:crypto'; import * as fs from 'node:fs'; +import { GEMINI_DIR } from '../utils/paths.js'; -export const GEMINI_DIR = '.gemini'; export const GOOGLE_ACCOUNTS_FILENAME = 'google_accounts.json'; export const OAUTH_FILE = 'oauth_creds.json'; const TMP_DIR_NAME = 'tmp'; @@ -25,7 +25,7 @@ export class Storage { static getGlobalGeminiDir(): string { const homeDir = os.homedir(); if (!homeDir) { - return path.join(os.tmpdir(), '.gemini'); + return path.join(os.tmpdir(), GEMINI_DIR); } return path.join(homeDir, GEMINI_DIR); } diff --git a/packages/core/src/core/logger.test.ts b/packages/core/src/core/logger.test.ts index a7acf87850a..70697b6f982 100644 --- a/packages/core/src/core/logger.test.ts +++ b/packages/core/src/core/logger.test.ts @@ -27,20 +27,15 @@ import type { Content } from '@google/genai'; import crypto from 'node:crypto'; import os from 'node:os'; +import { GEMINI_DIR } from '../utils/paths.js'; -const GEMINI_DIR_NAME = '.gemini'; const TMP_DIR_NAME = 'tmp'; const LOG_FILE_NAME = 'logs.json'; const CHECKPOINT_FILE_NAME = 'checkpoint.json'; const projectDir = process.cwd(); const hash = crypto.createHash('sha256').update(projectDir).digest('hex'); -const TEST_GEMINI_DIR = path.join( - os.homedir(), - GEMINI_DIR_NAME, - TMP_DIR_NAME, - hash, -); +const TEST_GEMINI_DIR = path.join(os.homedir(), GEMINI_DIR, TMP_DIR_NAME, hash); const TEST_LOG_FILE_PATH = path.join(TEST_GEMINI_DIR, LOG_FILE_NAME); const TEST_CHECKPOINT_FILE_PATH = path.join( diff --git a/packages/core/src/core/prompts.test.ts b/packages/core/src/core/prompts.test.ts index bb8506c4cb3..837b621ed17 100644 --- a/packages/core/src/core/prompts.test.ts +++ b/packages/core/src/core/prompts.test.ts @@ -10,9 +10,9 @@ import { isGitRepository } from '../utils/gitUtils.js'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; -import { GEMINI_CONFIG_DIR } from '../tools/memoryTool.js'; import type { Config } from '../config/config.js'; import { CodebaseInvestigatorAgent } from '../agents/codebase-investigator.js'; +import { GEMINI_DIR } from '../utils/paths.js'; // Mock tool names if they are dynamically generated or complex vi.mock('../tools/ls', () => ({ LSTool: { Name: 'list_directory' } })); @@ -223,9 +223,7 @@ describe('Core System Prompt (prompts.ts)', () => { }); it('should read from default path when GEMINI_SYSTEM_MD is "true"', () => { - const defaultPath = path.resolve( - path.join(GEMINI_CONFIG_DIR, 'system.md'), - ); + const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md')); vi.stubEnv('GEMINI_SYSTEM_MD', 'true'); vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue('custom system prompt'); @@ -236,9 +234,7 @@ describe('Core System Prompt (prompts.ts)', () => { }); it('should read from default path when GEMINI_SYSTEM_MD is "1"', () => { - const defaultPath = path.resolve( - path.join(GEMINI_CONFIG_DIR, 'system.md'), - ); + const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md')); vi.stubEnv('GEMINI_SYSTEM_MD', '1'); vi.mocked(fs.existsSync).mockReturnValue(true); vi.mocked(fs.readFileSync).mockReturnValue('custom system prompt'); @@ -291,9 +287,7 @@ describe('Core System Prompt (prompts.ts)', () => { }); it('should write to default path when GEMINI_WRITE_SYSTEM_MD is "true"', () => { - const defaultPath = path.resolve( - path.join(GEMINI_CONFIG_DIR, 'system.md'), - ); + const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md')); vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', 'true'); getCoreSystemPrompt(mockConfig); expect(fs.writeFileSync).toHaveBeenCalledWith( @@ -303,9 +297,7 @@ describe('Core System Prompt (prompts.ts)', () => { }); it('should write to default path when GEMINI_WRITE_SYSTEM_MD is "1"', () => { - const defaultPath = path.resolve( - path.join(GEMINI_CONFIG_DIR, 'system.md'), - ); + const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md')); vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', '1'); getCoreSystemPrompt(mockConfig); expect(fs.writeFileSync).toHaveBeenCalledWith( diff --git a/packages/core/src/core/prompts.ts b/packages/core/src/core/prompts.ts index 4865b0902c9..d371ca43fc5 100644 --- a/packages/core/src/core/prompts.ts +++ b/packages/core/src/core/prompts.ts @@ -17,9 +17,10 @@ import { ShellTool } from '../tools/shell.js'; import { WRITE_FILE_TOOL_NAME } from '../tools/tool-names.js'; import process from 'node:process'; import { isGitRepository } from '../utils/gitUtils.js'; -import { MemoryTool, GEMINI_CONFIG_DIR } from '../tools/memoryTool.js'; +import { MemoryTool } from '../tools/memoryTool.js'; import { CodebaseInvestigatorAgent } from '../agents/codebase-investigator.js'; import type { Config } from '../config/config.js'; +import { GEMINI_DIR } from '../utils/paths.js'; export function resolvePathFromEnv(envVar?: string): { isSwitch: boolean; @@ -78,7 +79,7 @@ export function getCoreSystemPrompt( // A flag to indicate whether the system prompt override is active. let systemMdEnabled = false; // The default path for the system prompt file. This can be overridden. - let systemMdPath = path.resolve(path.join(GEMINI_CONFIG_DIR, 'system.md')); + let systemMdPath = path.resolve(path.join(GEMINI_DIR, 'system.md')); // Resolve the environment variable to get either a path or a switch value. const systemMdResolution = resolvePathFromEnv( process.env['GEMINI_SYSTEM_MD'], diff --git a/packages/core/src/mcp/token-storage/file-token-storage.test.ts b/packages/core/src/mcp/token-storage/file-token-storage.test.ts index 282702cdc63..fcaebaf5083 100644 --- a/packages/core/src/mcp/token-storage/file-token-storage.test.ts +++ b/packages/core/src/mcp/token-storage/file-token-storage.test.ts @@ -9,6 +9,7 @@ import { promises as fs } from 'node:fs'; import * as path from 'node:path'; import { FileTokenStorage } from './file-token-storage.js'; import type { OAuthCredentials } from './types.js'; +import { GEMINI_DIR } from '../../utils/paths.js'; vi.mock('node:fs', () => ({ promises: { @@ -135,7 +136,7 @@ describe('FileTokenStorage', () => { await storage.setCredentials(credentials); expect(mockFs.mkdir).toHaveBeenCalledWith( - path.join('/home/test', '.gemini'), + path.join('/home/test', GEMINI_DIR), { recursive: true, mode: 0o700 }, ); expect(mockFs.writeFile).toHaveBeenCalled(); @@ -201,7 +202,7 @@ describe('FileTokenStorage', () => { await storage.deleteCredentials('test-server'); expect(mockFs.unlink).toHaveBeenCalledWith( - path.join('/home/test', '.gemini', 'mcp-oauth-tokens-v2.json'), + path.join('/home/test', GEMINI_DIR, 'mcp-oauth-tokens-v2.json'), ); }); @@ -282,7 +283,7 @@ describe('FileTokenStorage', () => { await storage.clearAll(); expect(mockFs.unlink).toHaveBeenCalledWith( - path.join('/home/test', '.gemini', 'mcp-oauth-tokens-v2.json'), + path.join('/home/test', GEMINI_DIR, 'mcp-oauth-tokens-v2.json'), ); }); diff --git a/packages/core/src/mcp/token-storage/file-token-storage.ts b/packages/core/src/mcp/token-storage/file-token-storage.ts index 44090894a44..4da1d5935df 100644 --- a/packages/core/src/mcp/token-storage/file-token-storage.ts +++ b/packages/core/src/mcp/token-storage/file-token-storage.ts @@ -10,6 +10,7 @@ import * as os from 'node:os'; import * as crypto from 'node:crypto'; import { BaseTokenStorage } from './base-token-storage.js'; import type { OAuthCredentials } from './types.js'; +import { GEMINI_DIR } from '../../utils/paths.js'; export class FileTokenStorage extends BaseTokenStorage { private readonly tokenFilePath: string; @@ -17,7 +18,7 @@ export class FileTokenStorage extends BaseTokenStorage { constructor(serviceName: string) { super(serviceName); - const configDir = path.join(os.homedir(), '.gemini'); + const configDir = path.join(os.homedir(), GEMINI_DIR); this.tokenFilePath = path.join(configDir, 'mcp-oauth-tokens-v2.json'); this.encryptionKey = this.deriveEncryptionKey(); } diff --git a/packages/core/src/tools/memoryTool.test.ts b/packages/core/src/tools/memoryTool.test.ts index 9751ad64af2..c7cec3d7a0b 100644 --- a/packages/core/src/tools/memoryTool.test.ts +++ b/packages/core/src/tools/memoryTool.test.ts @@ -18,6 +18,7 @@ import * as path from 'node:path'; import * as os from 'node:os'; import { ToolConfirmationOutcome } from './tools.js'; import { ToolErrorType } from './tool-error.js'; +import { GEMINI_DIR } from '../utils/paths.js'; // Mock dependencies vi.mock(import('node:fs/promises'), async (importOriginal) => { @@ -105,7 +106,7 @@ describe('MemoryTool', () => { beforeEach(() => { testFilePath = path.join( os.homedir(), - '.gemini', + GEMINI_DIR, DEFAULT_CONTEXT_FILENAME, ); }); @@ -237,7 +238,7 @@ describe('MemoryTool', () => { // Use getCurrentGeminiMdFilename for the default expectation before any setGeminiMdFilename calls in a test const expectedFilePath = path.join( os.homedir(), - '.gemini', + GEMINI_DIR, getCurrentGeminiMdFilename(), // This will be DEFAULT_CONTEXT_FILENAME unless changed by a test ); @@ -317,9 +318,11 @@ describe('MemoryTool', () => { expect(result).not.toBe(false); if (result && result.type === 'edit') { - const expectedPath = path.join('~', '.gemini', 'GEMINI.md'); + const expectedPath = path.join('~', GEMINI_DIR, 'GEMINI.md'); expect(result.title).toBe(`Confirm Memory Save: ${expectedPath}`); - expect(result.fileName).toContain(path.join('mock', 'home', '.gemini')); + expect(result.fileName).toContain( + path.join('mock', 'home', GEMINI_DIR), + ); expect(result.fileName).toContain('GEMINI.md'); expect(result.fileDiff).toContain('Index: GEMINI.md'); expect(result.fileDiff).toContain('+## Gemini Added Memories'); @@ -334,7 +337,7 @@ describe('MemoryTool', () => { const params = { fact: 'Test fact' }; const memoryFilePath = path.join( os.homedir(), - '.gemini', + GEMINI_DIR, getCurrentGeminiMdFilename(), ); @@ -352,7 +355,7 @@ describe('MemoryTool', () => { const params = { fact: 'Test fact' }; const memoryFilePath = path.join( os.homedir(), - '.gemini', + GEMINI_DIR, getCurrentGeminiMdFilename(), ); @@ -378,7 +381,7 @@ describe('MemoryTool', () => { const params = { fact: 'Test fact' }; const memoryFilePath = path.join( os.homedir(), - '.gemini', + GEMINI_DIR, getCurrentGeminiMdFilename(), ); @@ -415,7 +418,7 @@ describe('MemoryTool', () => { expect(result).not.toBe(false); if (result && result.type === 'edit') { - const expectedPath = path.join('~', '.gemini', 'GEMINI.md'); + const expectedPath = path.join('~', GEMINI_DIR, 'GEMINI.md'); expect(result.title).toBe(`Confirm Memory Save: ${expectedPath}`); expect(result.fileDiff).toContain('Index: GEMINI.md'); expect(result.fileDiff).toContain('+- New fact'); diff --git a/packages/core/src/tools/memoryTool.ts b/packages/core/src/tools/memoryTool.ts index 85e42c07936..eb53630d649 100644 --- a/packages/core/src/tools/memoryTool.ts +++ b/packages/core/src/tools/memoryTool.ts @@ -60,7 +60,6 @@ Do NOT use this tool: - \`fact\` (string, required): The specific fact or piece of information to remember. This should be a clear, self-contained statement. For example, if the user says "My favorite color is blue", the fact would be "My favorite color is blue". `; -export const GEMINI_CONFIG_DIR = '.gemini'; export const DEFAULT_CONTEXT_FILENAME = 'GEMINI.md'; export const MEMORY_SECTION_HEADER = '## Gemini Added Memories'; @@ -98,7 +97,7 @@ interface SaveMemoryParams { modified_content?: string; } -function getGlobalMemoryFilePath(): string { +export function getGlobalMemoryFilePath(): string { return path.join(Storage.getGlobalGeminiDir(), getCurrentGeminiMdFilename()); } diff --git a/packages/core/src/utils/getFolderStructure.test.ts b/packages/core/src/utils/getFolderStructure.test.ts index e40586ff13e..5b8e18c0891 100644 --- a/packages/core/src/utils/getFolderStructure.test.ts +++ b/packages/core/src/utils/getFolderStructure.test.ts @@ -11,6 +11,7 @@ import * as os from 'node:os'; import { getFolderStructure } from './getFolderStructure.js'; import { FileDiscoveryService } from '../services/fileDiscoveryService.js'; import * as path from 'node:path'; +import { GEMINI_DIR } from './paths.js'; describe('getFolderStructure', () => { let testRootDir: string; @@ -255,8 +256,8 @@ ${testRootDir}${path.sep} await createTestFile('file1.txt'); await createTestFile('node_modules', 'some-package', 'index.js'); await createTestFile('ignored.txt'); - await createTestFile('.gemini', 'config.yaml'); - await createTestFile('.gemini', 'logs.json'); + await createTestFile(GEMINI_DIR, 'config.yaml'); + await createTestFile(GEMINI_DIR, 'logs.json'); const fileService = new FileDiscoveryService(testRootDir); const structure = await getFolderStructure(testRootDir, { @@ -301,8 +302,8 @@ ${testRootDir}${path.sep} await createTestFile('file1.txt'); await createTestFile('node_modules', 'some-package', 'index.js'); await createTestFile('ignored.txt'); - await createTestFile('.gemini', 'config.yaml'); - await createTestFile('.gemini', 'logs.json'); + await createTestFile(GEMINI_DIR, 'config.yaml'); + await createTestFile(GEMINI_DIR, 'logs.json'); const fileService = new FileDiscoveryService(testRootDir); const structure = await getFolderStructure(testRootDir, { @@ -321,8 +322,8 @@ ${testRootDir}${path.sep} await createTestFile('file1.txt'); await createTestFile('node_modules', 'some-package', 'index.js'); await createTestFile('ignored.txt'); - await createTestFile('.gemini', 'config.yaml'); - await createTestFile('.gemini', 'logs.json'); + await createTestFile(GEMINI_DIR, 'config.yaml'); + await createTestFile(GEMINI_DIR, 'logs.json'); const fileService = new FileDiscoveryService(testRootDir); const structure = await getFolderStructure(testRootDir, { diff --git a/packages/core/src/utils/installationManager.test.ts b/packages/core/src/utils/installationManager.test.ts index acdf339e0e3..f3a9c4c4e36 100644 --- a/packages/core/src/utils/installationManager.test.ts +++ b/packages/core/src/utils/installationManager.test.ts @@ -11,6 +11,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import path from 'node:path'; import { randomUUID } from 'node:crypto'; +import { GEMINI_DIR } from './paths.js'; vi.mock('node:fs', async (importOriginal) => { const actual = await importOriginal(); @@ -41,7 +42,7 @@ describe('InstallationManager', () => { let tempHomeDir: string; let installationManager: InstallationManager; const installationIdFile = () => - path.join(tempHomeDir, '.gemini', 'installation_id'); + path.join(tempHomeDir, GEMINI_DIR, 'installation_id'); beforeEach(() => { tempHomeDir = fs.mkdtempSync( diff --git a/packages/core/src/utils/userAccountManager.test.ts b/packages/core/src/utils/userAccountManager.test.ts index 434a323986f..d2657cf20f3 100644 --- a/packages/core/src/utils/userAccountManager.test.ts +++ b/packages/core/src/utils/userAccountManager.test.ts @@ -10,6 +10,7 @@ import { UserAccountManager } from './userAccountManager.js'; import * as fs from 'node:fs'; import * as os from 'node:os'; import path from 'node:path'; +import { GEMINI_DIR } from './paths.js'; vi.mock('os', async (importOriginal) => { const os = await importOriginal(); @@ -30,7 +31,7 @@ describe('UserAccountManager', () => { ); (os.homedir as Mock).mockReturnValue(tempHomeDir); accountsFile = () => - path.join(tempHomeDir, '.gemini', 'google_accounts.json'); + path.join(tempHomeDir, GEMINI_DIR, 'google_accounts.json'); userAccountManager = new UserAccountManager(); }); diff --git a/scripts/sandbox_command.js b/scripts/sandbox_command.js index 1e1d80de37c..29fb8a3b17b 100644 --- a/scripts/sandbox_command.js +++ b/scripts/sandbox_command.js @@ -25,6 +25,7 @@ import os from 'node:os'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import dotenv from 'dotenv'; +import { GEMINI_DIR } from '@google/gemini-cli-core'; const argv = yargs(hideBin(process.argv)).option('q', { alias: 'quiet', @@ -35,7 +36,7 @@ const argv = yargs(hideBin(process.argv)).option('q', { let geminiSandbox = process.env.GEMINI_SANDBOX; if (!geminiSandbox) { - const userSettingsFile = join(os.homedir(), '.gemini', 'settings.json'); + const userSettingsFile = join(os.homedir(), GEMINI_DIR, 'settings.json'); if (existsSync(userSettingsFile)) { const settings = JSON.parse( stripJsonComments(readFileSync(userSettingsFile, 'utf-8')), @@ -49,7 +50,7 @@ if (!geminiSandbox) { if (!geminiSandbox) { let currentDir = process.cwd(); while (true) { - const geminiEnv = join(currentDir, '.gemini', '.env'); + const geminiEnv = join(currentDir, GEMINI_DIR, '.env'); const regularEnv = join(currentDir, '.env'); if (existsSync(geminiEnv)) { dotenv.config({ path: geminiEnv, quiet: true }); diff --git a/scripts/telemetry.js b/scripts/telemetry.js index a1833c87eab..0da513c9817 100755 --- a/scripts/telemetry.js +++ b/scripts/telemetry.js @@ -9,20 +9,16 @@ import { execSync } from 'node:child_process'; import { join } from 'node:path'; import { existsSync, readFileSync } from 'node:fs'; +import { GEMINI_DIR } from '@google/gemini-cli-core'; const projectRoot = join(import.meta.dirname, '..'); -const SETTINGS_DIRECTORY_NAME = '.gemini'; const USER_SETTINGS_DIR = join( process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || '', - SETTINGS_DIRECTORY_NAME, + GEMINI_DIR, ); const USER_SETTINGS_PATH = join(USER_SETTINGS_DIR, 'settings.json'); -const WORKSPACE_SETTINGS_PATH = join( - projectRoot, - SETTINGS_DIRECTORY_NAME, - 'settings.json', -); +const WORKSPACE_SETTINGS_PATH = join(projectRoot, GEMINI_DIR, 'settings.json'); let settingsTarget = undefined; diff --git a/scripts/telemetry_utils.js b/scripts/telemetry_utils.js index ebf45a4e1cf..a891a5c9eb1 100644 --- a/scripts/telemetry_utils.js +++ b/scripts/telemetry_utils.js @@ -13,6 +13,7 @@ import os from 'node:os'; import { spawnSync } from 'node:child_process'; import { fileURLToPath } from 'node:url'; import crypto from 'node:crypto'; +import { GEMINI_DIR } from '@google/gemini-cli-core'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -24,9 +25,9 @@ const projectHash = crypto .digest('hex'); // User-level .gemini directory in home -const USER_GEMINI_DIR = path.join(os.homedir(), '.gemini'); +const USER_GEMINI_DIR = path.join(os.homedir(), GEMINI_DIR); // Project-level .gemini directory in the workspace -const WORKSPACE_GEMINI_DIR = path.join(projectRoot, '.gemini'); +const WORKSPACE_GEMINI_DIR = path.join(projectRoot, GEMINI_DIR); // Telemetry artifacts are stored in a hashed directory under the user's ~/.gemini/tmp export const OTEL_DIR = path.join(USER_GEMINI_DIR, 'tmp', projectHash, 'otel');