mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-24 20:14:44 -07:00
chore: Extract '.gemini' to GEMINI_DIR constant (#10540)
Co-authored-by: Richie Foreman <richie.foreman@gmail.com>
This commit is contained in:
committed by
GitHub
parent
7beaa368a9
commit
518caae62e
@@ -19,23 +19,14 @@ import {
|
|||||||
} from 'node:fs/promises';
|
} from 'node:fs/promises';
|
||||||
import { join, dirname } from 'node:path';
|
import { join, dirname } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import * as os from 'node:os';
|
import { getGlobalMemoryFilePath } from '../packages/core/src/tools/memoryTool.js';
|
||||||
|
|
||||||
import {
|
|
||||||
GEMINI_CONFIG_DIR,
|
|
||||||
DEFAULT_CONTEXT_FILENAME,
|
|
||||||
} from '../packages/core/src/tools/memoryTool.js';
|
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
const rootDir = join(__dirname, '..');
|
const rootDir = join(__dirname, '..');
|
||||||
const integrationTestsDir = join(rootDir, '.integration-tests');
|
const integrationTestsDir = join(rootDir, '.integration-tests');
|
||||||
let runDir = ''; // Make runDir accessible in teardown
|
let runDir = ''; // Make runDir accessible in teardown
|
||||||
|
|
||||||
const memoryFilePath = join(
|
const memoryFilePath = getGlobalMemoryFilePath();
|
||||||
os.homedir(),
|
|
||||||
GEMINI_CONFIG_DIR,
|
|
||||||
DEFAULT_CONTEXT_FILENAME,
|
|
||||||
);
|
|
||||||
let originalMemoryContent: string | null = null;
|
let originalMemoryContent: string | null = null;
|
||||||
|
|
||||||
export async function setup() {
|
export async function setup() {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import fs from 'node:fs';
|
|||||||
import * as pty from '@lydell/node-pty';
|
import * as pty from '@lydell/node-pty';
|
||||||
import stripAnsi from 'strip-ansi';
|
import stripAnsi from 'strip-ansi';
|
||||||
import * as os from 'node:os';
|
import * as os from 'node:os';
|
||||||
|
import { GEMINI_DIR } from '../packages/core/src/utils/paths.js';
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
@@ -244,7 +245,7 @@ export class TestRig {
|
|||||||
mkdirSync(this.testDir, { recursive: true });
|
mkdirSync(this.testDir, { recursive: true });
|
||||||
|
|
||||||
// Create a settings file to point the CLI to the local collector
|
// 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 });
|
mkdirSync(geminiDir, { recursive: true });
|
||||||
// In sandbox mode, use an absolute path for telemetry inside the container
|
// 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
|
// The container mounts the test directory at the same path as the host
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
FileDiscoveryService,
|
FileDiscoveryService,
|
||||||
ApprovalMode,
|
ApprovalMode,
|
||||||
loadServerHierarchicalMemory,
|
loadServerHierarchicalMemory,
|
||||||
GEMINI_CONFIG_DIR,
|
GEMINI_DIR,
|
||||||
DEFAULT_GEMINI_EMBEDDING_MODEL,
|
DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||||
DEFAULT_GEMINI_MODEL,
|
DEFAULT_GEMINI_MODEL,
|
||||||
type GeminiCLIExtension,
|
type GeminiCLIExtension,
|
||||||
@@ -176,7 +176,7 @@ function findEnvFile(startDir: string): string | null {
|
|||||||
let currentDir = path.resolve(startDir);
|
let currentDir = path.resolve(startDir);
|
||||||
while (true) {
|
while (true) {
|
||||||
// prefer gemini-specific .env under GEMINI_DIR
|
// 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)) {
|
if (fs.existsSync(geminiEnvPath)) {
|
||||||
return geminiEnvPath;
|
return geminiEnvPath;
|
||||||
}
|
}
|
||||||
@@ -187,11 +187,7 @@ function findEnvFile(startDir: string): string | null {
|
|||||||
const parentDir = path.dirname(currentDir);
|
const parentDir = path.dirname(currentDir);
|
||||||
if (parentDir === currentDir || !parentDir) {
|
if (parentDir === currentDir || !parentDir) {
|
||||||
// check .env under home as fallback, again preferring gemini-specific .env
|
// check .env under home as fallback, again preferring gemini-specific .env
|
||||||
const homeGeminiEnvPath = path.join(
|
const homeGeminiEnvPath = path.join(process.cwd(), GEMINI_DIR, '.env');
|
||||||
process.cwd(),
|
|
||||||
GEMINI_CONFIG_DIR,
|
|
||||||
'.env',
|
|
||||||
);
|
|
||||||
if (fs.existsSync(homeGeminiEnvPath)) {
|
if (fs.existsSync(homeGeminiEnvPath)) {
|
||||||
return homeGeminiEnvPath;
|
return homeGeminiEnvPath;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,18 @@
|
|||||||
|
|
||||||
// Copied exactly from packages/cli/src/config/extension.ts, last PR #1026
|
// Copied exactly from packages/cli/src/config/extension.ts, last PR #1026
|
||||||
|
|
||||||
import type {
|
import {
|
||||||
MCPServerConfig,
|
GEMINI_DIR,
|
||||||
ExtensionInstallMetadata,
|
type MCPServerConfig,
|
||||||
GeminiCLIExtension,
|
type ExtensionInstallMetadata,
|
||||||
|
type GeminiCLIExtension,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import * as os from 'node:os';
|
import * as os from 'node:os';
|
||||||
import { logger } from '../utils/logger.js';
|
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 EXTENSIONS_CONFIG_FILENAME = 'gemini-extension.json';
|
||||||
export const INSTALL_METADATA_FILENAME = '.gemini-extension-install.json';
|
export const INSTALL_METADATA_FILENAME = '.gemini-extension-install.json';
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import { homedir } from 'node:os';
|
|||||||
|
|
||||||
import type { MCPServerConfig } from '@google/gemini-cli-core';
|
import type { MCPServerConfig } from '@google/gemini-cli-core';
|
||||||
import {
|
import {
|
||||||
|
GEMINI_DIR,
|
||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
type TelemetrySettings,
|
type TelemetrySettings,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import stripJsonComments from 'strip-json-comments';
|
import stripJsonComments from 'strip-json-comments';
|
||||||
|
|
||||||
export const SETTINGS_DIRECTORY_NAME = '.gemini';
|
export const USER_SETTINGS_DIR = path.join(homedir(), GEMINI_DIR);
|
||||||
export const USER_SETTINGS_DIR = path.join(homedir(), SETTINGS_DIRECTORY_NAME);
|
|
||||||
export const USER_SETTINGS_PATH = path.join(USER_SETTINGS_DIR, 'settings.json');
|
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
|
// 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(
|
const workspaceSettingsPath = path.join(
|
||||||
workspaceDir,
|
workspaceDir,
|
||||||
SETTINGS_DIRECTORY_NAME,
|
GEMINI_DIR,
|
||||||
'settings.json',
|
'settings.json',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ vi.mock('@google/gemini-cli-core', () => ({
|
|||||||
getWorkspaceSettingsPath: () => '/tmp/gemini/workspace-settings.json',
|
getWorkspaceSettingsPath: () => '/tmp/gemini/workspace-settings.json',
|
||||||
getProjectTempDir: () => '/test/home/.gemini/tmp/mocked_hash',
|
getProjectTempDir: () => '/test/home/.gemini/tmp/mocked_hash',
|
||||||
})),
|
})),
|
||||||
GEMINI_CONFIG_DIR: '.gemini',
|
GEMINI_DIR: '.gemini',
|
||||||
getErrorMessage: (e: unknown) => (e instanceof Error ? e.message : String(e)),
|
getErrorMessage: (e: unknown) => (e instanceof Error ? e.message : String(e)),
|
||||||
}));
|
}));
|
||||||
vi.mock('@modelcontextprotocol/sdk/client/index.js');
|
vi.mock('@modelcontextprotocol/sdk/client/index.js');
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { removeCommand } from './remove.js';
|
|||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import * as os from 'node:os';
|
import * as os from 'node:os';
|
||||||
|
import { GEMINI_DIR } from '@google/gemini-cli-core';
|
||||||
|
|
||||||
vi.mock('fs/promises', () => ({
|
vi.mock('fs/promises', () => ({
|
||||||
readFile: vi.fn(),
|
readFile: vi.fn(),
|
||||||
@@ -80,7 +81,7 @@ describe('mcp remove command', () => {
|
|||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
|
|
||||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mcp-remove-test-'));
|
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');
|
settingsPath = path.join(settingsDir, 'settings.json');
|
||||||
fs.mkdirSync(settingsDir, { recursive: true });
|
fs.mkdirSync(settingsDir, { recursive: true });
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
// 3. Spies on console functions (for logger output) are correctly set up if needed.
|
||||||
// Example of a previously failing test structure:
|
// Example of a previously failing test structure:
|
||||||
it.skip('should correctly use mocked homedir for global path', async () => {
|
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(
|
const MOCK_GLOBAL_PATH_LOCAL = path.join(
|
||||||
MOCK_GEMINI_DIR_LOCAL,
|
MOCK_GEMINI_DIR_LOCAL,
|
||||||
'GEMINI.md',
|
'GEMINI.md',
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import fs from 'node:fs';
|
|||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||||
import { ExtensionEnablementManager, Override } from './extensionEnablement.js';
|
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
|
// Helper to create a temporary directory for testing
|
||||||
function createTestDir() {
|
function createTestDir() {
|
||||||
@@ -27,7 +28,7 @@ let manager: ExtensionEnablementManager;
|
|||||||
describe('ExtensionEnablementManager', () => {
|
describe('ExtensionEnablementManager', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
testDir = createTestDir();
|
testDir = createTestDir();
|
||||||
configDir = path.join(testDir.path, '.gemini');
|
configDir = path.join(testDir.path, GEMINI_DIR);
|
||||||
manager = new ExtensionEnablementManager(configDir);
|
manager = new ExtensionEnablementManager(configDir);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ import {
|
|||||||
USER_SETTINGS_PATH, // This IS the mocked path.
|
USER_SETTINGS_PATH, // This IS the mocked path.
|
||||||
getSystemSettingsPath,
|
getSystemSettingsPath,
|
||||||
getSystemDefaultsPath,
|
getSystemDefaultsPath,
|
||||||
SETTINGS_DIRECTORY_NAME, // This is from the original module, but used by the mock.
|
|
||||||
migrateSettingsToV1,
|
migrateSettingsToV1,
|
||||||
needsMigration,
|
needsMigration,
|
||||||
type Settings,
|
type Settings,
|
||||||
@@ -70,10 +69,10 @@ import {
|
|||||||
import { FatalConfigError, GEMINI_DIR } from '@google/gemini-cli-core';
|
import { FatalConfigError, GEMINI_DIR } from '@google/gemini-cli-core';
|
||||||
|
|
||||||
const MOCK_WORKSPACE_DIR = '/mock/workspace';
|
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(
|
const MOCK_WORKSPACE_SETTINGS_PATH = pathActual.join(
|
||||||
MOCK_WORKSPACE_DIR,
|
MOCK_WORKSPACE_DIR,
|
||||||
SETTINGS_DIRECTORY_NAME,
|
GEMINI_DIR,
|
||||||
'settings.json',
|
'settings.json',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import * as dotenv from 'dotenv';
|
|||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
import {
|
import {
|
||||||
FatalConfigError,
|
FatalConfigError,
|
||||||
GEMINI_CONFIG_DIR as GEMINI_DIR,
|
GEMINI_DIR,
|
||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
Storage,
|
Storage,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
@@ -49,8 +49,6 @@ function getMergeStrategyForPath(path: string[]): MergeStrategy | undefined {
|
|||||||
|
|
||||||
export type { Settings, MemoryImportFormat };
|
export type { Settings, MemoryImportFormat };
|
||||||
|
|
||||||
export const SETTINGS_DIRECTORY_NAME = '.gemini';
|
|
||||||
|
|
||||||
export const USER_SETTINGS_PATH = Storage.getGlobalSettingsPath();
|
export const USER_SETTINGS_PATH = Storage.getGlobalSettingsPath();
|
||||||
export const USER_SETTINGS_DIR = path.dirname(USER_SETTINGS_PATH);
|
export const USER_SETTINGS_DIR = path.dirname(USER_SETTINGS_PATH);
|
||||||
export const DEFAULT_EXCLUDED_ENV_VARS = ['DEBUG', 'DEBUG_MODE'];
|
export const DEFAULT_EXCLUDED_ENV_VARS = ['DEBUG', 'DEBUG_MODE'];
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ import {
|
|||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
isWithinRoot,
|
isWithinRoot,
|
||||||
ideContextStore,
|
ideContextStore,
|
||||||
|
GEMINI_DIR,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import type { Settings } from './settings.js';
|
import type { Settings } from './settings.js';
|
||||||
import stripJsonComments from 'strip-json-comments';
|
import stripJsonComments from 'strip-json-comments';
|
||||||
|
|
||||||
export const TRUSTED_FOLDERS_FILENAME = 'trustedFolders.json';
|
export const TRUSTED_FOLDERS_FILENAME = 'trustedFolders.json';
|
||||||
export const SETTINGS_DIRECTORY_NAME = '.gemini';
|
export const USER_SETTINGS_DIR = path.join(homedir(), GEMINI_DIR);
|
||||||
export const USER_SETTINGS_DIR = path.join(homedir(), SETTINGS_DIRECTORY_NAME);
|
|
||||||
|
|
||||||
export function getTrustedFoldersPath(): string {
|
export function getTrustedFoldersPath(): string {
|
||||||
if (process.env['GEMINI_CLI_TRUSTED_FOLDERS_PATH']) {
|
if (process.env['GEMINI_CLI_TRUSTED_FOLDERS_PATH']) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import type { Config } from '@google/gemini-cli-core';
|
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 mock from 'mock-fs';
|
||||||
import { FileCommandLoader } from './FileCommandLoader.js';
|
import { FileCommandLoader } from './FileCommandLoader.js';
|
||||||
import { assert, vi } from 'vitest';
|
import { assert, vi } from 'vitest';
|
||||||
@@ -529,7 +529,9 @@ describe('FileCommandLoader', () => {
|
|||||||
).getProjectCommandsDir();
|
).getProjectCommandsDir();
|
||||||
const extensionDir = path.join(
|
const extensionDir = path.join(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'.gemini/extensions/test-ext',
|
GEMINI_DIR,
|
||||||
|
'extensions',
|
||||||
|
'test-ext',
|
||||||
);
|
);
|
||||||
|
|
||||||
mock({
|
mock({
|
||||||
@@ -582,7 +584,9 @@ describe('FileCommandLoader', () => {
|
|||||||
).getProjectCommandsDir();
|
).getProjectCommandsDir();
|
||||||
const extensionDir = path.join(
|
const extensionDir = path.join(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'.gemini/extensions/test-ext',
|
GEMINI_DIR,
|
||||||
|
'extensions',
|
||||||
|
'test-ext',
|
||||||
);
|
);
|
||||||
|
|
||||||
mock({
|
mock({
|
||||||
@@ -678,11 +682,15 @@ describe('FileCommandLoader', () => {
|
|||||||
it('only loads commands from active extensions', async () => {
|
it('only loads commands from active extensions', async () => {
|
||||||
const extensionDir1 = path.join(
|
const extensionDir1 = path.join(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'.gemini/extensions/active-ext',
|
GEMINI_DIR,
|
||||||
|
'extensions',
|
||||||
|
'active-ext',
|
||||||
);
|
);
|
||||||
const extensionDir2 = path.join(
|
const extensionDir2 = path.join(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'.gemini/extensions/inactive-ext',
|
GEMINI_DIR,
|
||||||
|
'extensions',
|
||||||
|
'inactive-ext',
|
||||||
);
|
);
|
||||||
|
|
||||||
mock({
|
mock({
|
||||||
@@ -737,7 +745,9 @@ describe('FileCommandLoader', () => {
|
|||||||
it('handles missing extension commands directory gracefully', async () => {
|
it('handles missing extension commands directory gracefully', async () => {
|
||||||
const extensionDir = path.join(
|
const extensionDir = path.join(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
'.gemini/extensions/no-commands',
|
GEMINI_DIR,
|
||||||
|
'extensions',
|
||||||
|
'no-commands',
|
||||||
);
|
);
|
||||||
|
|
||||||
mock({
|
mock({
|
||||||
@@ -769,7 +779,12 @@ describe('FileCommandLoader', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles nested command structure in extensions', async () => {
|
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({
|
mock({
|
||||||
[extensionDir]: {
|
[extensionDir]: {
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ import * as path from 'node:path';
|
|||||||
import { restoreCommand } from './restoreCommand.js';
|
import { restoreCommand } from './restoreCommand.js';
|
||||||
import { type CommandContext } from './types.js';
|
import { type CommandContext } from './types.js';
|
||||||
import { createMockCommandContext } from '../../test-utils/mockCommandContext.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', () => {
|
describe('restoreCommand', () => {
|
||||||
let mockContext: CommandContext;
|
let mockContext: CommandContext;
|
||||||
@@ -26,7 +30,7 @@ describe('restoreCommand', () => {
|
|||||||
testRootDir = await fs.mkdtemp(
|
testRootDir = await fs.mkdtemp(
|
||||||
path.join(os.tmpdir(), 'restore-command-test-'),
|
path.join(os.tmpdir(), 'restore-command-test-'),
|
||||||
);
|
);
|
||||||
geminiTempDir = path.join(testRootDir, '.gemini');
|
geminiTempDir = path.join(testRootDir, GEMINI_DIR);
|
||||||
checkpointsDir = path.join(geminiTempDir, 'checkpoints');
|
checkpointsDir = path.join(geminiTempDir, 'checkpoints');
|
||||||
// The command itself creates this, but for tests it's easier to have it ready.
|
// The command itself creates this, but for tests it's easier to have it ready.
|
||||||
// Some tests might remove it to test error paths.
|
// Some tests might remove it to test error paths.
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ import { theme } from '../semantic-colors.js';
|
|||||||
import { StreamingState } from '../types.js';
|
import { StreamingState } from '../types.js';
|
||||||
import { UpdateNotification } from './UpdateNotification.js';
|
import { UpdateNotification } from './UpdateNotification.js';
|
||||||
|
|
||||||
|
import { GEMINI_DIR } from '@google/gemini-cli-core';
|
||||||
import { homedir } from 'node:os';
|
import { homedir } from 'node:os';
|
||||||
import path from 'node:path';
|
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 = () => {
|
export const Notifications = () => {
|
||||||
const { startupWarnings } = useAppContext();
|
const { startupWarnings } = useAppContext();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ vi.mock('../contexts/ConfigContext.js');
|
|||||||
vi.mock('../contexts/UIStateContext.js');
|
vi.mock('../contexts/UIStateContext.js');
|
||||||
vi.mock('@google/gemini-cli-core', () => ({
|
vi.mock('@google/gemini-cli-core', () => ({
|
||||||
recordFlickerFrame: vi.fn(),
|
recordFlickerFrame: vi.fn(),
|
||||||
|
GEMINI_DIR: '.gemini',
|
||||||
}));
|
}));
|
||||||
vi.mock('ink', async (importOriginal) => {
|
vi.mock('ink', async (importOriginal) => {
|
||||||
const original = await importOriginal<typeof import('ink')>();
|
const original = await importOriginal<typeof import('ink')>();
|
||||||
|
|||||||
@@ -10,30 +10,34 @@ import * as fs from 'node:fs/promises';
|
|||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import * as os from 'node:os';
|
import * as os from 'node:os';
|
||||||
import * as crypto from 'node:crypto';
|
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(),
|
readFile: vi.fn(),
|
||||||
writeFile: vi.fn(),
|
writeFile: vi.fn(),
|
||||||
mkdir: vi.fn(),
|
mkdir: vi.fn(),
|
||||||
}));
|
}));
|
||||||
vi.mock('os');
|
vi.mock('node:os');
|
||||||
vi.mock('crypto');
|
vi.mock('node:crypto');
|
||||||
vi.mock('fs', async (importOriginal) => {
|
vi.mock('node:fs', async (importOriginal) => {
|
||||||
const actualFs = await importOriginal<typeof import('fs')>();
|
const actualFs = await importOriginal<typeof import('node:fs')>();
|
||||||
return {
|
return {
|
||||||
...actualFs,
|
...actualFs,
|
||||||
mkdirSync: vi.fn(),
|
mkdirSync: vi.fn(),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
vi.mock('@google/gemini-cli-core', () => {
|
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||||
|
const actual =
|
||||||
|
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||||
|
const path = await import('node:path');
|
||||||
class Storage {
|
class Storage {
|
||||||
getProjectTempDir(): string {
|
getProjectTempDir(): string {
|
||||||
return path.join('/test/home/', '.gemini', 'tmp', 'mocked_hash');
|
return path.join('/test/home/', actual.GEMINI_DIR, 'tmp', 'mocked_hash');
|
||||||
}
|
}
|
||||||
getHistoryFilePath(): string {
|
getHistoryFilePath(): string {
|
||||||
return path.join(
|
return path.join(
|
||||||
'/test/home/',
|
'/test/home/',
|
||||||
'.gemini',
|
actual.GEMINI_DIR,
|
||||||
'tmp',
|
'tmp',
|
||||||
'mocked_hash',
|
'mocked_hash',
|
||||||
'shell_history',
|
'shell_history',
|
||||||
@@ -41,6 +45,7 @@ vi.mock('@google/gemini-cli-core', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
...actual,
|
||||||
isNodeError: (err: unknown): err is NodeJS.ErrnoException =>
|
isNodeError: (err: unknown): err is NodeJS.ErrnoException =>
|
||||||
typeof err === 'object' && err !== null && 'code' in err,
|
typeof err === 'object' && err !== null && 'code' in err,
|
||||||
Storage,
|
Storage,
|
||||||
@@ -53,7 +58,7 @@ const MOCKED_PROJECT_HASH = 'mocked_hash';
|
|||||||
|
|
||||||
const MOCKED_HISTORY_DIR = path.join(
|
const MOCKED_HISTORY_DIR = path.join(
|
||||||
MOCKED_HOME_DIR,
|
MOCKED_HOME_DIR,
|
||||||
'.gemini',
|
GEMINI_DIR,
|
||||||
'tmp',
|
'tmp',
|
||||||
MOCKED_PROJECT_HASH,
|
MOCKED_PROJECT_HASH,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,13 +11,10 @@ import fs from 'node:fs';
|
|||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { quote, parse } from 'shell-quote';
|
import { quote, parse } from 'shell-quote';
|
||||||
import {
|
import { USER_SETTINGS_DIR } from '../config/settings.js';
|
||||||
USER_SETTINGS_DIR,
|
|
||||||
SETTINGS_DIRECTORY_NAME,
|
|
||||||
} from '../config/settings.js';
|
|
||||||
import { promisify } from 'node:util';
|
import { promisify } from 'node:util';
|
||||||
import type { Config, SandboxConfig } from '@google/gemini-cli-core';
|
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 { ConsolePatcher } from '../ui/utils/ConsolePatcher.js';
|
||||||
import { randomBytes } from 'node:crypto';
|
import { randomBytes } from 'node:crypto';
|
||||||
|
|
||||||
@@ -156,10 +153,7 @@ function entrypoint(workdir: string, cliArgs: string[]): string[] {
|
|||||||
shellCmds.push(`export PYTHONPATH="$PYTHONPATH${pythonPathSuffix}";`);
|
shellCmds.push(`export PYTHONPATH="$PYTHONPATH${pythonPathSuffix}";`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectSandboxBashrc = path.join(
|
const projectSandboxBashrc = path.join(GEMINI_DIR, 'sandbox.bashrc');
|
||||||
SETTINGS_DIRECTORY_NAME,
|
|
||||||
'sandbox.bashrc',
|
|
||||||
);
|
|
||||||
if (fs.existsSync(projectSandboxBashrc)) {
|
if (fs.existsSync(projectSandboxBashrc)) {
|
||||||
shellCmds.push(`source ${getContainerPath(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 profile name is not recognized, then look for file under project settings directory
|
||||||
if (!BUILTIN_SEATBELT_PROFILES.includes(profile)) {
|
if (!BUILTIN_SEATBELT_PROFILES.includes(profile)) {
|
||||||
profileFile = path.join(
|
profileFile = path.join(GEMINI_DIR, `sandbox-macos-${profile}.sb`);
|
||||||
SETTINGS_DIRECTORY_NAME,
|
|
||||||
`sandbox-macos-${profile}.sb`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (!fs.existsSync(profileFile)) {
|
if (!fs.existsSync(profileFile)) {
|
||||||
throw new FatalSandboxError(
|
throw new FatalSandboxError(
|
||||||
@@ -359,7 +350,7 @@ export async function start_sandbox(
|
|||||||
const gcPath = fs.realpathSync(process.argv[1]);
|
const gcPath = fs.realpathSync(process.argv[1]);
|
||||||
|
|
||||||
const projectSandboxDockerfile = path.join(
|
const projectSandboxDockerfile = path.join(
|
||||||
SETTINGS_DIRECTORY_NAME,
|
GEMINI_DIR,
|
||||||
'sandbox.Dockerfile',
|
'sandbox.Dockerfile',
|
||||||
);
|
);
|
||||||
const isCustomProjectSandbox = fs.existsSync(projectSandboxDockerfile);
|
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
|
// if project folder has sandbox.Dockerfile under project settings folder, use that
|
||||||
let buildArgs = '';
|
let buildArgs = '';
|
||||||
const projectSandboxDockerfile = path.join(
|
const projectSandboxDockerfile = path.join(
|
||||||
SETTINGS_DIRECTORY_NAME,
|
GEMINI_DIR,
|
||||||
'sandbox.Dockerfile',
|
'sandbox.Dockerfile',
|
||||||
);
|
);
|
||||||
if (isCustomProjectSandbox) {
|
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
|
// note user/home changes inside sandbox and we mount at BOTH paths for consistency
|
||||||
const userSettingsDirOnHost = USER_SETTINGS_DIR;
|
const userSettingsDirOnHost = USER_SETTINGS_DIR;
|
||||||
const userSettingsDirInSandbox = getContainerPath(
|
const userSettingsDirInSandbox = getContainerPath(
|
||||||
`/home/node/${SETTINGS_DIRECTORY_NAME}`,
|
`/home/node/${GEMINI_DIR}`,
|
||||||
);
|
);
|
||||||
if (!fs.existsSync(userSettingsDirOnHost)) {
|
if (!fs.existsSync(userSettingsDirOnHost)) {
|
||||||
fs.mkdirSync(userSettingsDirOnHost);
|
fs.mkdirSync(userSettingsDirOnHost);
|
||||||
@@ -665,10 +656,7 @@ export async function start_sandbox(
|
|||||||
?.toLowerCase()
|
?.toLowerCase()
|
||||||
.startsWith(workdir.toLowerCase())
|
.startsWith(workdir.toLowerCase())
|
||||||
) {
|
) {
|
||||||
const sandboxVenvPath = path.resolve(
|
const sandboxVenvPath = path.resolve(GEMINI_DIR, 'sandbox.venv');
|
||||||
SETTINGS_DIRECTORY_NAME,
|
|
||||||
'sandbox.venv',
|
|
||||||
);
|
|
||||||
if (!fs.existsSync(sandboxVenvPath)) {
|
if (!fs.existsSync(sandboxVenvPath)) {
|
||||||
fs.mkdirSync(sandboxVenvPath, { recursive: true });
|
fs.mkdirSync(sandboxVenvPath, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import type { OAuthCredentials } from '../mcp/token-storage/types.js';
|
|||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import * as os from 'node:os';
|
import * as os from 'node:os';
|
||||||
import { promises as fs } from 'node:fs';
|
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 KEYCHAIN_SERVICE_NAME = 'gemini-cli-oauth';
|
||||||
const MAIN_ACCOUNT_KEY = 'main-account';
|
const MAIN_ACCOUNT_KEY = 'main-account';
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { AuthType } from '../core/contentGenerator.js';
|
|||||||
import type { Config } from '../config/config.js';
|
import type { Config } from '../config/config.js';
|
||||||
import readline from 'node:readline';
|
import readline from 'node:readline';
|
||||||
import { FORCE_ENCRYPTED_FILE_ENV_VAR } from '../mcp/token-storage/index.js';
|
import { FORCE_ENCRYPTED_FILE_ENV_VAR } from '../mcp/token-storage/index.js';
|
||||||
|
import { GEMINI_DIR } from '../utils/paths.js';
|
||||||
|
|
||||||
vi.mock('os', async (importOriginal) => {
|
vi.mock('os', async (importOriginal) => {
|
||||||
const os = await importOriginal<typeof import('os')>();
|
const os = await importOriginal<typeof import('os')>();
|
||||||
@@ -182,7 +183,7 @@ describe('oauth2', () => {
|
|||||||
// Verify Google Account was cached
|
// Verify Google Account was cached
|
||||||
const googleAccountPath = path.join(
|
const googleAccountPath = path.join(
|
||||||
tempHomeDir,
|
tempHomeDir,
|
||||||
'.gemini',
|
GEMINI_DIR,
|
||||||
'google_accounts.json',
|
'google_accounts.json',
|
||||||
);
|
);
|
||||||
expect(fs.existsSync(googleAccountPath)).toBe(true);
|
expect(fs.existsSync(googleAccountPath)).toBe(true);
|
||||||
@@ -290,7 +291,11 @@ describe('oauth2', () => {
|
|||||||
|
|
||||||
it('should attempt to load cached credentials first', async () => {
|
it('should attempt to load cached credentials first', async () => {
|
||||||
const cachedCreds = { refresh_token: 'cached-token' };
|
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.mkdir(path.dirname(credsPath), { recursive: true });
|
||||||
await fs.promises.writeFile(credsPath, JSON.stringify(cachedCreds));
|
await fs.promises.writeFile(credsPath, JSON.stringify(cachedCreds));
|
||||||
|
|
||||||
@@ -328,7 +333,11 @@ describe('oauth2', () => {
|
|||||||
|
|
||||||
await getOauthClient(AuthType.CLOUD_SHELL, mockConfig);
|
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);
|
expect(fs.existsSync(credsPath)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -355,7 +364,7 @@ describe('oauth2', () => {
|
|||||||
const defaultCreds = { refresh_token: 'default-cached-token' };
|
const defaultCreds = { refresh_token: 'default-cached-token' };
|
||||||
const defaultCredsPath = path.join(
|
const defaultCredsPath = path.join(
|
||||||
tempHomeDir,
|
tempHomeDir,
|
||||||
'.gemini',
|
GEMINI_DIR,
|
||||||
'oauth_creds.json',
|
'oauth_creds.json',
|
||||||
);
|
);
|
||||||
await fs.promises.mkdir(path.dirname(defaultCredsPath), {
|
await fs.promises.mkdir(path.dirname(defaultCredsPath), {
|
||||||
@@ -463,7 +472,7 @@ describe('oauth2', () => {
|
|||||||
// Verify Google Account was cached
|
// Verify Google Account was cached
|
||||||
const googleAccountPath = path.join(
|
const googleAccountPath = path.join(
|
||||||
tempHomeDir,
|
tempHomeDir,
|
||||||
'.gemini',
|
GEMINI_DIR,
|
||||||
'google_accounts.json',
|
'google_accounts.json',
|
||||||
);
|
);
|
||||||
const cachedContent = fs.readFileSync(googleAccountPath, 'utf-8');
|
const cachedContent = fs.readFileSync(googleAccountPath, 'utf-8');
|
||||||
@@ -493,7 +502,11 @@ describe('oauth2', () => {
|
|||||||
|
|
||||||
// Make it fall through to cached credentials path
|
// Make it fall through to cached credentials path
|
||||||
const cachedCreds = { refresh_token: 'cached-token' };
|
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.mkdir(path.dirname(credsPath), { recursive: true });
|
||||||
await fs.promises.writeFile(credsPath, JSON.stringify(cachedCreds));
|
await fs.promises.writeFile(credsPath, JSON.stringify(cachedCreds));
|
||||||
|
|
||||||
@@ -524,7 +537,11 @@ describe('oauth2', () => {
|
|||||||
|
|
||||||
// Make it fall through to cached credentials path
|
// Make it fall through to cached credentials path
|
||||||
const cachedCreds = { refresh_token: 'cached-token' };
|
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.mkdir(path.dirname(credsPath), { recursive: true });
|
||||||
await fs.promises.writeFile(credsPath, JSON.stringify(cachedCreds));
|
await fs.promises.writeFile(credsPath, JSON.stringify(cachedCreds));
|
||||||
|
|
||||||
@@ -916,13 +933,17 @@ describe('oauth2', () => {
|
|||||||
describe('clearCachedCredentialFile', () => {
|
describe('clearCachedCredentialFile', () => {
|
||||||
it('should clear cached credentials and Google account', async () => {
|
it('should clear cached credentials and Google account', async () => {
|
||||||
const cachedCreds = { refresh_token: 'test-token' };
|
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.mkdir(path.dirname(credsPath), { recursive: true });
|
||||||
await fs.promises.writeFile(credsPath, JSON.stringify(cachedCreds));
|
await fs.promises.writeFile(credsPath, JSON.stringify(cachedCreds));
|
||||||
|
|
||||||
const googleAccountPath = path.join(
|
const googleAccountPath = path.join(
|
||||||
tempHomeDir,
|
tempHomeDir,
|
||||||
'.gemini',
|
GEMINI_DIR,
|
||||||
'google_accounts.json',
|
'google_accounts.json',
|
||||||
);
|
);
|
||||||
const accountData = { active: 'test@example.com', old: [] };
|
const accountData = { active: 'test@example.com', old: [] };
|
||||||
@@ -965,7 +986,11 @@ describe('oauth2', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Pre-populate credentials to make getOauthClient resolve quickly
|
// 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.mkdir(path.dirname(credsPath), { recursive: true });
|
||||||
await fs.promises.writeFile(
|
await fs.promises.writeFile(
|
||||||
credsPath,
|
credsPath,
|
||||||
@@ -1104,7 +1129,7 @@ describe('oauth2', () => {
|
|||||||
expect(
|
expect(
|
||||||
OAuthCredentialStorage.saveCredentials as Mock,
|
OAuthCredentialStorage.saveCredentials as Mock,
|
||||||
).toHaveBeenCalledWith(mockTokens);
|
).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);
|
expect(fs.existsSync(credsPath)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1120,7 +1145,7 @@ describe('oauth2', () => {
|
|||||||
// Create a dummy unencrypted credential file.
|
// Create a dummy unencrypted credential file.
|
||||||
// If the logic is correct, this file should be ignored.
|
// If the logic is correct, this file should be ignored.
|
||||||
const unencryptedCreds = { refresh_token: 'unencrypted-token' };
|
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.mkdir(path.dirname(credsPath), { recursive: true });
|
||||||
await fs.promises.writeFile(credsPath, JSON.stringify(unencryptedCreds));
|
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.
|
// 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.mkdir(path.dirname(credsPath), { recursive: true });
|
||||||
await fs.promises.writeFile(credsPath, '{}');
|
await fs.promises.writeFile(credsPath, '{}');
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ vi.mock('../tools/memoryTool', () => ({
|
|||||||
setGeminiMdFilename: vi.fn(),
|
setGeminiMdFilename: vi.fn(),
|
||||||
getCurrentGeminiMdFilename: vi.fn(() => 'GEMINI.md'), // Mock the original filename
|
getCurrentGeminiMdFilename: vi.fn(() => 'GEMINI.md'), // Mock the original filename
|
||||||
DEFAULT_CONTEXT_FILENAME: 'GEMINI.md',
|
DEFAULT_CONTEXT_FILENAME: 'GEMINI.md',
|
||||||
GEMINI_CONFIG_DIR: '.gemini',
|
GEMINI_DIR: '.gemini',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../core/contentGenerator.js');
|
vi.mock('../core/contentGenerator.js');
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ vi.mock('fs', async (importOriginal) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
import { Storage } from './storage.js';
|
import { Storage } from './storage.js';
|
||||||
|
import { GEMINI_DIR } from '../utils/paths.js';
|
||||||
|
|
||||||
describe('Storage – getGlobalSettingsPath', () => {
|
describe('Storage – getGlobalSettingsPath', () => {
|
||||||
it('returns path to ~/.gemini/settings.json', () => {
|
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);
|
expect(Storage.getGlobalSettingsPath()).toBe(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -30,31 +31,31 @@ describe('Storage – additional helpers', () => {
|
|||||||
const storage = new Storage(projectRoot);
|
const storage = new Storage(projectRoot);
|
||||||
|
|
||||||
it('getWorkspaceSettingsPath returns project/.gemini/settings.json', () => {
|
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);
|
expect(storage.getWorkspaceSettingsPath()).toBe(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getUserCommandsDir returns ~/.gemini/commands', () => {
|
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);
|
expect(Storage.getUserCommandsDir()).toBe(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getProjectCommandsDir returns project/.gemini/commands', () => {
|
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);
|
expect(storage.getProjectCommandsDir()).toBe(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getMcpOAuthTokensPath returns ~/.gemini/mcp-oauth-tokens.json', () => {
|
it('getMcpOAuthTokensPath returns ~/.gemini/mcp-oauth-tokens.json', () => {
|
||||||
const expected = path.join(
|
const expected = path.join(
|
||||||
os.homedir(),
|
os.homedir(),
|
||||||
'.gemini',
|
GEMINI_DIR,
|
||||||
'mcp-oauth-tokens.json',
|
'mcp-oauth-tokens.json',
|
||||||
);
|
);
|
||||||
expect(Storage.getMcpOAuthTokensPath()).toBe(expected);
|
expect(Storage.getMcpOAuthTokensPath()).toBe(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getGlobalBinDir returns ~/.gemini/tmp/bin', () => {
|
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);
|
expect(Storage.getGlobalBinDir()).toBe(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import * as path from 'node:path';
|
|||||||
import * as os from 'node:os';
|
import * as os from 'node:os';
|
||||||
import * as crypto from 'node:crypto';
|
import * as crypto from 'node:crypto';
|
||||||
import * as fs from 'node:fs';
|
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 GOOGLE_ACCOUNTS_FILENAME = 'google_accounts.json';
|
||||||
export const OAUTH_FILE = 'oauth_creds.json';
|
export const OAUTH_FILE = 'oauth_creds.json';
|
||||||
const TMP_DIR_NAME = 'tmp';
|
const TMP_DIR_NAME = 'tmp';
|
||||||
@@ -25,7 +25,7 @@ export class Storage {
|
|||||||
static getGlobalGeminiDir(): string {
|
static getGlobalGeminiDir(): string {
|
||||||
const homeDir = os.homedir();
|
const homeDir = os.homedir();
|
||||||
if (!homeDir) {
|
if (!homeDir) {
|
||||||
return path.join(os.tmpdir(), '.gemini');
|
return path.join(os.tmpdir(), GEMINI_DIR);
|
||||||
}
|
}
|
||||||
return path.join(homeDir, GEMINI_DIR);
|
return path.join(homeDir, GEMINI_DIR);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,20 +27,15 @@ import type { Content } from '@google/genai';
|
|||||||
|
|
||||||
import crypto from 'node:crypto';
|
import crypto from 'node:crypto';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
|
import { GEMINI_DIR } from '../utils/paths.js';
|
||||||
|
|
||||||
const GEMINI_DIR_NAME = '.gemini';
|
|
||||||
const TMP_DIR_NAME = 'tmp';
|
const TMP_DIR_NAME = 'tmp';
|
||||||
const LOG_FILE_NAME = 'logs.json';
|
const LOG_FILE_NAME = 'logs.json';
|
||||||
const CHECKPOINT_FILE_NAME = 'checkpoint.json';
|
const CHECKPOINT_FILE_NAME = 'checkpoint.json';
|
||||||
|
|
||||||
const projectDir = process.cwd();
|
const projectDir = process.cwd();
|
||||||
const hash = crypto.createHash('sha256').update(projectDir).digest('hex');
|
const hash = crypto.createHash('sha256').update(projectDir).digest('hex');
|
||||||
const TEST_GEMINI_DIR = path.join(
|
const TEST_GEMINI_DIR = path.join(os.homedir(), GEMINI_DIR, TMP_DIR_NAME, hash);
|
||||||
os.homedir(),
|
|
||||||
GEMINI_DIR_NAME,
|
|
||||||
TMP_DIR_NAME,
|
|
||||||
hash,
|
|
||||||
);
|
|
||||||
|
|
||||||
const TEST_LOG_FILE_PATH = path.join(TEST_GEMINI_DIR, LOG_FILE_NAME);
|
const TEST_LOG_FILE_PATH = path.join(TEST_GEMINI_DIR, LOG_FILE_NAME);
|
||||||
const TEST_CHECKPOINT_FILE_PATH = path.join(
|
const TEST_CHECKPOINT_FILE_PATH = path.join(
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import { isGitRepository } from '../utils/gitUtils.js';
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { GEMINI_CONFIG_DIR } from '../tools/memoryTool.js';
|
|
||||||
import type { Config } from '../config/config.js';
|
import type { Config } from '../config/config.js';
|
||||||
import { CodebaseInvestigatorAgent } from '../agents/codebase-investigator.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
|
// Mock tool names if they are dynamically generated or complex
|
||||||
vi.mock('../tools/ls', () => ({ LSTool: { Name: 'list_directory' } }));
|
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"', () => {
|
it('should read from default path when GEMINI_SYSTEM_MD is "true"', () => {
|
||||||
const defaultPath = path.resolve(
|
const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md'));
|
||||||
path.join(GEMINI_CONFIG_DIR, 'system.md'),
|
|
||||||
);
|
|
||||||
vi.stubEnv('GEMINI_SYSTEM_MD', 'true');
|
vi.stubEnv('GEMINI_SYSTEM_MD', 'true');
|
||||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||||
vi.mocked(fs.readFileSync).mockReturnValue('custom system prompt');
|
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"', () => {
|
it('should read from default path when GEMINI_SYSTEM_MD is "1"', () => {
|
||||||
const defaultPath = path.resolve(
|
const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md'));
|
||||||
path.join(GEMINI_CONFIG_DIR, 'system.md'),
|
|
||||||
);
|
|
||||||
vi.stubEnv('GEMINI_SYSTEM_MD', '1');
|
vi.stubEnv('GEMINI_SYSTEM_MD', '1');
|
||||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||||
vi.mocked(fs.readFileSync).mockReturnValue('custom system prompt');
|
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"', () => {
|
it('should write to default path when GEMINI_WRITE_SYSTEM_MD is "true"', () => {
|
||||||
const defaultPath = path.resolve(
|
const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md'));
|
||||||
path.join(GEMINI_CONFIG_DIR, 'system.md'),
|
|
||||||
);
|
|
||||||
vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', 'true');
|
vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', 'true');
|
||||||
getCoreSystemPrompt(mockConfig);
|
getCoreSystemPrompt(mockConfig);
|
||||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
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"', () => {
|
it('should write to default path when GEMINI_WRITE_SYSTEM_MD is "1"', () => {
|
||||||
const defaultPath = path.resolve(
|
const defaultPath = path.resolve(path.join(GEMINI_DIR, 'system.md'));
|
||||||
path.join(GEMINI_CONFIG_DIR, 'system.md'),
|
|
||||||
);
|
|
||||||
vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', '1');
|
vi.stubEnv('GEMINI_WRITE_SYSTEM_MD', '1');
|
||||||
getCoreSystemPrompt(mockConfig);
|
getCoreSystemPrompt(mockConfig);
|
||||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||||
|
|||||||
@@ -17,9 +17,10 @@ import { ShellTool } from '../tools/shell.js';
|
|||||||
import { WRITE_FILE_TOOL_NAME } from '../tools/tool-names.js';
|
import { WRITE_FILE_TOOL_NAME } from '../tools/tool-names.js';
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
import { isGitRepository } from '../utils/gitUtils.js';
|
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 { CodebaseInvestigatorAgent } from '../agents/codebase-investigator.js';
|
||||||
import type { Config } from '../config/config.js';
|
import type { Config } from '../config/config.js';
|
||||||
|
import { GEMINI_DIR } from '../utils/paths.js';
|
||||||
|
|
||||||
export function resolvePathFromEnv(envVar?: string): {
|
export function resolvePathFromEnv(envVar?: string): {
|
||||||
isSwitch: boolean;
|
isSwitch: boolean;
|
||||||
@@ -78,7 +79,7 @@ export function getCoreSystemPrompt(
|
|||||||
// A flag to indicate whether the system prompt override is active.
|
// A flag to indicate whether the system prompt override is active.
|
||||||
let systemMdEnabled = false;
|
let systemMdEnabled = false;
|
||||||
// The default path for the system prompt file. This can be overridden.
|
// 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.
|
// Resolve the environment variable to get either a path or a switch value.
|
||||||
const systemMdResolution = resolvePathFromEnv(
|
const systemMdResolution = resolvePathFromEnv(
|
||||||
process.env['GEMINI_SYSTEM_MD'],
|
process.env['GEMINI_SYSTEM_MD'],
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { promises as fs } from 'node:fs';
|
|||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import { FileTokenStorage } from './file-token-storage.js';
|
import { FileTokenStorage } from './file-token-storage.js';
|
||||||
import type { OAuthCredentials } from './types.js';
|
import type { OAuthCredentials } from './types.js';
|
||||||
|
import { GEMINI_DIR } from '../../utils/paths.js';
|
||||||
|
|
||||||
vi.mock('node:fs', () => ({
|
vi.mock('node:fs', () => ({
|
||||||
promises: {
|
promises: {
|
||||||
@@ -135,7 +136,7 @@ describe('FileTokenStorage', () => {
|
|||||||
await storage.setCredentials(credentials);
|
await storage.setCredentials(credentials);
|
||||||
|
|
||||||
expect(mockFs.mkdir).toHaveBeenCalledWith(
|
expect(mockFs.mkdir).toHaveBeenCalledWith(
|
||||||
path.join('/home/test', '.gemini'),
|
path.join('/home/test', GEMINI_DIR),
|
||||||
{ recursive: true, mode: 0o700 },
|
{ recursive: true, mode: 0o700 },
|
||||||
);
|
);
|
||||||
expect(mockFs.writeFile).toHaveBeenCalled();
|
expect(mockFs.writeFile).toHaveBeenCalled();
|
||||||
@@ -201,7 +202,7 @@ describe('FileTokenStorage', () => {
|
|||||||
await storage.deleteCredentials('test-server');
|
await storage.deleteCredentials('test-server');
|
||||||
|
|
||||||
expect(mockFs.unlink).toHaveBeenCalledWith(
|
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();
|
await storage.clearAll();
|
||||||
|
|
||||||
expect(mockFs.unlink).toHaveBeenCalledWith(
|
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'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import * as os from 'node:os';
|
|||||||
import * as crypto from 'node:crypto';
|
import * as crypto from 'node:crypto';
|
||||||
import { BaseTokenStorage } from './base-token-storage.js';
|
import { BaseTokenStorage } from './base-token-storage.js';
|
||||||
import type { OAuthCredentials } from './types.js';
|
import type { OAuthCredentials } from './types.js';
|
||||||
|
import { GEMINI_DIR } from '../../utils/paths.js';
|
||||||
|
|
||||||
export class FileTokenStorage extends BaseTokenStorage {
|
export class FileTokenStorage extends BaseTokenStorage {
|
||||||
private readonly tokenFilePath: string;
|
private readonly tokenFilePath: string;
|
||||||
@@ -17,7 +18,7 @@ export class FileTokenStorage extends BaseTokenStorage {
|
|||||||
|
|
||||||
constructor(serviceName: string) {
|
constructor(serviceName: string) {
|
||||||
super(serviceName);
|
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.tokenFilePath = path.join(configDir, 'mcp-oauth-tokens-v2.json');
|
||||||
this.encryptionKey = this.deriveEncryptionKey();
|
this.encryptionKey = this.deriveEncryptionKey();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import * as path from 'node:path';
|
|||||||
import * as os from 'node:os';
|
import * as os from 'node:os';
|
||||||
import { ToolConfirmationOutcome } from './tools.js';
|
import { ToolConfirmationOutcome } from './tools.js';
|
||||||
import { ToolErrorType } from './tool-error.js';
|
import { ToolErrorType } from './tool-error.js';
|
||||||
|
import { GEMINI_DIR } from '../utils/paths.js';
|
||||||
|
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
vi.mock(import('node:fs/promises'), async (importOriginal) => {
|
vi.mock(import('node:fs/promises'), async (importOriginal) => {
|
||||||
@@ -105,7 +106,7 @@ describe('MemoryTool', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
testFilePath = path.join(
|
testFilePath = path.join(
|
||||||
os.homedir(),
|
os.homedir(),
|
||||||
'.gemini',
|
GEMINI_DIR,
|
||||||
DEFAULT_CONTEXT_FILENAME,
|
DEFAULT_CONTEXT_FILENAME,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -237,7 +238,7 @@ describe('MemoryTool', () => {
|
|||||||
// Use getCurrentGeminiMdFilename for the default expectation before any setGeminiMdFilename calls in a test
|
// Use getCurrentGeminiMdFilename for the default expectation before any setGeminiMdFilename calls in a test
|
||||||
const expectedFilePath = path.join(
|
const expectedFilePath = path.join(
|
||||||
os.homedir(),
|
os.homedir(),
|
||||||
'.gemini',
|
GEMINI_DIR,
|
||||||
getCurrentGeminiMdFilename(), // This will be DEFAULT_CONTEXT_FILENAME unless changed by a test
|
getCurrentGeminiMdFilename(), // This will be DEFAULT_CONTEXT_FILENAME unless changed by a test
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -317,9 +318,11 @@ describe('MemoryTool', () => {
|
|||||||
expect(result).not.toBe(false);
|
expect(result).not.toBe(false);
|
||||||
|
|
||||||
if (result && result.type === 'edit') {
|
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.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.fileName).toContain('GEMINI.md');
|
||||||
expect(result.fileDiff).toContain('Index: GEMINI.md');
|
expect(result.fileDiff).toContain('Index: GEMINI.md');
|
||||||
expect(result.fileDiff).toContain('+## Gemini Added Memories');
|
expect(result.fileDiff).toContain('+## Gemini Added Memories');
|
||||||
@@ -334,7 +337,7 @@ describe('MemoryTool', () => {
|
|||||||
const params = { fact: 'Test fact' };
|
const params = { fact: 'Test fact' };
|
||||||
const memoryFilePath = path.join(
|
const memoryFilePath = path.join(
|
||||||
os.homedir(),
|
os.homedir(),
|
||||||
'.gemini',
|
GEMINI_DIR,
|
||||||
getCurrentGeminiMdFilename(),
|
getCurrentGeminiMdFilename(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -352,7 +355,7 @@ describe('MemoryTool', () => {
|
|||||||
const params = { fact: 'Test fact' };
|
const params = { fact: 'Test fact' };
|
||||||
const memoryFilePath = path.join(
|
const memoryFilePath = path.join(
|
||||||
os.homedir(),
|
os.homedir(),
|
||||||
'.gemini',
|
GEMINI_DIR,
|
||||||
getCurrentGeminiMdFilename(),
|
getCurrentGeminiMdFilename(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -378,7 +381,7 @@ describe('MemoryTool', () => {
|
|||||||
const params = { fact: 'Test fact' };
|
const params = { fact: 'Test fact' };
|
||||||
const memoryFilePath = path.join(
|
const memoryFilePath = path.join(
|
||||||
os.homedir(),
|
os.homedir(),
|
||||||
'.gemini',
|
GEMINI_DIR,
|
||||||
getCurrentGeminiMdFilename(),
|
getCurrentGeminiMdFilename(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -415,7 +418,7 @@ describe('MemoryTool', () => {
|
|||||||
expect(result).not.toBe(false);
|
expect(result).not.toBe(false);
|
||||||
|
|
||||||
if (result && result.type === 'edit') {
|
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.title).toBe(`Confirm Memory Save: ${expectedPath}`);
|
||||||
expect(result.fileDiff).toContain('Index: GEMINI.md');
|
expect(result.fileDiff).toContain('Index: GEMINI.md');
|
||||||
expect(result.fileDiff).toContain('+- New fact');
|
expect(result.fileDiff).toContain('+- New fact');
|
||||||
|
|||||||
@@ -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".
|
- \`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 DEFAULT_CONTEXT_FILENAME = 'GEMINI.md';
|
||||||
export const MEMORY_SECTION_HEADER = '## Gemini Added Memories';
|
export const MEMORY_SECTION_HEADER = '## Gemini Added Memories';
|
||||||
|
|
||||||
@@ -98,7 +97,7 @@ interface SaveMemoryParams {
|
|||||||
modified_content?: string;
|
modified_content?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGlobalMemoryFilePath(): string {
|
export function getGlobalMemoryFilePath(): string {
|
||||||
return path.join(Storage.getGlobalGeminiDir(), getCurrentGeminiMdFilename());
|
return path.join(Storage.getGlobalGeminiDir(), getCurrentGeminiMdFilename());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import * as os from 'node:os';
|
|||||||
import { getFolderStructure } from './getFolderStructure.js';
|
import { getFolderStructure } from './getFolderStructure.js';
|
||||||
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
|
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
|
import { GEMINI_DIR } from './paths.js';
|
||||||
|
|
||||||
describe('getFolderStructure', () => {
|
describe('getFolderStructure', () => {
|
||||||
let testRootDir: string;
|
let testRootDir: string;
|
||||||
@@ -255,8 +256,8 @@ ${testRootDir}${path.sep}
|
|||||||
await createTestFile('file1.txt');
|
await createTestFile('file1.txt');
|
||||||
await createTestFile('node_modules', 'some-package', 'index.js');
|
await createTestFile('node_modules', 'some-package', 'index.js');
|
||||||
await createTestFile('ignored.txt');
|
await createTestFile('ignored.txt');
|
||||||
await createTestFile('.gemini', 'config.yaml');
|
await createTestFile(GEMINI_DIR, 'config.yaml');
|
||||||
await createTestFile('.gemini', 'logs.json');
|
await createTestFile(GEMINI_DIR, 'logs.json');
|
||||||
|
|
||||||
const fileService = new FileDiscoveryService(testRootDir);
|
const fileService = new FileDiscoveryService(testRootDir);
|
||||||
const structure = await getFolderStructure(testRootDir, {
|
const structure = await getFolderStructure(testRootDir, {
|
||||||
@@ -301,8 +302,8 @@ ${testRootDir}${path.sep}
|
|||||||
await createTestFile('file1.txt');
|
await createTestFile('file1.txt');
|
||||||
await createTestFile('node_modules', 'some-package', 'index.js');
|
await createTestFile('node_modules', 'some-package', 'index.js');
|
||||||
await createTestFile('ignored.txt');
|
await createTestFile('ignored.txt');
|
||||||
await createTestFile('.gemini', 'config.yaml');
|
await createTestFile(GEMINI_DIR, 'config.yaml');
|
||||||
await createTestFile('.gemini', 'logs.json');
|
await createTestFile(GEMINI_DIR, 'logs.json');
|
||||||
|
|
||||||
const fileService = new FileDiscoveryService(testRootDir);
|
const fileService = new FileDiscoveryService(testRootDir);
|
||||||
const structure = await getFolderStructure(testRootDir, {
|
const structure = await getFolderStructure(testRootDir, {
|
||||||
@@ -321,8 +322,8 @@ ${testRootDir}${path.sep}
|
|||||||
await createTestFile('file1.txt');
|
await createTestFile('file1.txt');
|
||||||
await createTestFile('node_modules', 'some-package', 'index.js');
|
await createTestFile('node_modules', 'some-package', 'index.js');
|
||||||
await createTestFile('ignored.txt');
|
await createTestFile('ignored.txt');
|
||||||
await createTestFile('.gemini', 'config.yaml');
|
await createTestFile(GEMINI_DIR, 'config.yaml');
|
||||||
await createTestFile('.gemini', 'logs.json');
|
await createTestFile(GEMINI_DIR, 'logs.json');
|
||||||
|
|
||||||
const fileService = new FileDiscoveryService(testRootDir);
|
const fileService = new FileDiscoveryService(testRootDir);
|
||||||
const structure = await getFolderStructure(testRootDir, {
|
const structure = await getFolderStructure(testRootDir, {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import * as fs from 'node:fs';
|
|||||||
import * as os from 'node:os';
|
import * as os from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
|
import { GEMINI_DIR } from './paths.js';
|
||||||
|
|
||||||
vi.mock('node:fs', async (importOriginal) => {
|
vi.mock('node:fs', async (importOriginal) => {
|
||||||
const actual = await importOriginal<typeof import('node:fs')>();
|
const actual = await importOriginal<typeof import('node:fs')>();
|
||||||
@@ -41,7 +42,7 @@ describe('InstallationManager', () => {
|
|||||||
let tempHomeDir: string;
|
let tempHomeDir: string;
|
||||||
let installationManager: InstallationManager;
|
let installationManager: InstallationManager;
|
||||||
const installationIdFile = () =>
|
const installationIdFile = () =>
|
||||||
path.join(tempHomeDir, '.gemini', 'installation_id');
|
path.join(tempHomeDir, GEMINI_DIR, 'installation_id');
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tempHomeDir = fs.mkdtempSync(
|
tempHomeDir = fs.mkdtempSync(
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { UserAccountManager } from './userAccountManager.js';
|
|||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
import * as os from 'node:os';
|
import * as os from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import { GEMINI_DIR } from './paths.js';
|
||||||
|
|
||||||
vi.mock('os', async (importOriginal) => {
|
vi.mock('os', async (importOriginal) => {
|
||||||
const os = await importOriginal<typeof import('os')>();
|
const os = await importOriginal<typeof import('os')>();
|
||||||
@@ -30,7 +31,7 @@ describe('UserAccountManager', () => {
|
|||||||
);
|
);
|
||||||
(os.homedir as Mock).mockReturnValue(tempHomeDir);
|
(os.homedir as Mock).mockReturnValue(tempHomeDir);
|
||||||
accountsFile = () =>
|
accountsFile = () =>
|
||||||
path.join(tempHomeDir, '.gemini', 'google_accounts.json');
|
path.join(tempHomeDir, GEMINI_DIR, 'google_accounts.json');
|
||||||
userAccountManager = new UserAccountManager();
|
userAccountManager = new UserAccountManager();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import os from 'node:os';
|
|||||||
import yargs from 'yargs';
|
import yargs from 'yargs';
|
||||||
import { hideBin } from 'yargs/helpers';
|
import { hideBin } from 'yargs/helpers';
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
|
import { GEMINI_DIR } from '@google/gemini-cli-core';
|
||||||
|
|
||||||
const argv = yargs(hideBin(process.argv)).option('q', {
|
const argv = yargs(hideBin(process.argv)).option('q', {
|
||||||
alias: 'quiet',
|
alias: 'quiet',
|
||||||
@@ -35,7 +36,7 @@ const argv = yargs(hideBin(process.argv)).option('q', {
|
|||||||
let geminiSandbox = process.env.GEMINI_SANDBOX;
|
let geminiSandbox = process.env.GEMINI_SANDBOX;
|
||||||
|
|
||||||
if (!geminiSandbox) {
|
if (!geminiSandbox) {
|
||||||
const userSettingsFile = join(os.homedir(), '.gemini', 'settings.json');
|
const userSettingsFile = join(os.homedir(), GEMINI_DIR, 'settings.json');
|
||||||
if (existsSync(userSettingsFile)) {
|
if (existsSync(userSettingsFile)) {
|
||||||
const settings = JSON.parse(
|
const settings = JSON.parse(
|
||||||
stripJsonComments(readFileSync(userSettingsFile, 'utf-8')),
|
stripJsonComments(readFileSync(userSettingsFile, 'utf-8')),
|
||||||
@@ -49,7 +50,7 @@ if (!geminiSandbox) {
|
|||||||
if (!geminiSandbox) {
|
if (!geminiSandbox) {
|
||||||
let currentDir = process.cwd();
|
let currentDir = process.cwd();
|
||||||
while (true) {
|
while (true) {
|
||||||
const geminiEnv = join(currentDir, '.gemini', '.env');
|
const geminiEnv = join(currentDir, GEMINI_DIR, '.env');
|
||||||
const regularEnv = join(currentDir, '.env');
|
const regularEnv = join(currentDir, '.env');
|
||||||
if (existsSync(geminiEnv)) {
|
if (existsSync(geminiEnv)) {
|
||||||
dotenv.config({ path: geminiEnv, quiet: true });
|
dotenv.config({ path: geminiEnv, quiet: true });
|
||||||
|
|||||||
@@ -9,20 +9,16 @@
|
|||||||
import { execSync } from 'node:child_process';
|
import { execSync } from 'node:child_process';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { existsSync, readFileSync } from 'node:fs';
|
import { existsSync, readFileSync } from 'node:fs';
|
||||||
|
import { GEMINI_DIR } from '@google/gemini-cli-core';
|
||||||
|
|
||||||
const projectRoot = join(import.meta.dirname, '..');
|
const projectRoot = join(import.meta.dirname, '..');
|
||||||
|
|
||||||
const SETTINGS_DIRECTORY_NAME = '.gemini';
|
|
||||||
const USER_SETTINGS_DIR = join(
|
const USER_SETTINGS_DIR = join(
|
||||||
process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || '',
|
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 USER_SETTINGS_PATH = join(USER_SETTINGS_DIR, 'settings.json');
|
||||||
const WORKSPACE_SETTINGS_PATH = join(
|
const WORKSPACE_SETTINGS_PATH = join(projectRoot, GEMINI_DIR, 'settings.json');
|
||||||
projectRoot,
|
|
||||||
SETTINGS_DIRECTORY_NAME,
|
|
||||||
'settings.json',
|
|
||||||
);
|
|
||||||
|
|
||||||
let settingsTarget = undefined;
|
let settingsTarget = undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import os from 'node:os';
|
|||||||
import { spawnSync } from 'node:child_process';
|
import { spawnSync } from 'node:child_process';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import crypto from 'node:crypto';
|
import crypto from 'node:crypto';
|
||||||
|
import { GEMINI_DIR } from '@google/gemini-cli-core';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
@@ -24,9 +25,9 @@ const projectHash = crypto
|
|||||||
.digest('hex');
|
.digest('hex');
|
||||||
|
|
||||||
// User-level .gemini directory in home
|
// 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
|
// 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
|
// 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');
|
export const OTEL_DIR = path.join(USER_GEMINI_DIR, 'tmp', projectHash, 'otel');
|
||||||
|
|||||||
Reference in New Issue
Block a user