diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index b3709ba0cd..8b7f833aef 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -925,6 +925,7 @@ export async function loadCliConfig( toolDiscoveryCommand: settings.tools?.discoveryCommand, toolCallCommand: settings.tools?.callCommand, mcpServerCommand, + shellToolRcFile: settings.tools?.shell?.rcFile, mcpServers, mcpEnablementCallbacks, mcpEnabled, diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 20d907ad54..ddeefde74e 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -1637,6 +1637,16 @@ const SETTINGS_SCHEMA = { 'Enable shell output efficiency optimizations for better performance.', showInDialog: false, }, + rcFile: { + type: 'string', + label: 'Shell Tool RC File', + category: 'Tools', + requiresRestart: false, + default: undefined as string | undefined, + description: + 'The path to a bash file (e.g., .bashrc) to source before executing shell commands.', + showInDialog: false, + }, }, }, diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 8b2f23c6ff..6190494db9 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -602,6 +602,7 @@ export interface ConfigParameters { toolDiscoveryCommand?: string; toolCallCommand?: string; mcpServerCommand?: string; + shellToolRcFile?: string; mcpServers?: Record; mcpEnablementCallbacks?: McpEnablementCallbacks; userMemory?: string | HierarchicalMemory; @@ -779,6 +780,7 @@ export class Config implements McpContext, AgentLoopContext { private readonly toolDiscoveryCommand: string | undefined; private readonly toolCallCommand: string | undefined; private readonly mcpServerCommand: string | undefined; + private readonly shellToolRcFile: string | undefined; private readonly mcpEnabled: boolean; private readonly extensionsEnabled: boolean; private mcpServers: Record | undefined; @@ -1047,6 +1049,7 @@ export class Config implements McpContext, AgentLoopContext { this.toolDiscoveryCommand = params.toolDiscoveryCommand; this.toolCallCommand = params.toolCallCommand; this.mcpServerCommand = params.mcpServerCommand; + this.shellToolRcFile = params.shellToolRcFile; this.mcpServers = params.mcpServers; this.mcpEnablementCallbacks = params.mcpEnablementCallbacks; this.mcpEnabled = params.mcpEnabled ?? true; @@ -2318,6 +2321,10 @@ export class Config implements McpContext, AgentLoopContext { return this.mcpServerCommand; } + getShellToolRcFile(): string | undefined { + return this.shellToolRcFile; + } + /** * The user configured MCP servers (via gemini settings files). * diff --git a/packages/core/src/tools/shell.test.ts b/packages/core/src/tools/shell.test.ts index 9f83b00bb6..17e78336ba 100644 --- a/packages/core/src/tools/shell.test.ts +++ b/packages/core/src/tools/shell.test.ts @@ -164,6 +164,7 @@ describe('ShellTool', () => { addPersistentApproval: vi.fn(), addSessionApproval: vi.fn(), }, + getShellToolRcFile: vi.fn().mockReturnValue(undefined), } as unknown as Config; const bus = createMockMessageBus(); @@ -486,6 +487,25 @@ EOF`; expect(mockShellExecutionService.mock.calls[0][0]).toMatch(/\nEOF\n\)\n/); }); + it('should source rcfile when shellToolRcFile setting is present', async () => { + const rcFilePath = '~/.geminirc'; + (mockConfig.getShellToolRcFile as Mock).mockReturnValue(rcFilePath); + + const invocation = shellTool.build({ command: 'my-command' }); + const promise = invocation.execute({ abortSignal: mockAbortSignal }); + resolveShellExecution(); + await promise; + + expect(mockShellExecutionService).toHaveBeenCalledWith( + expect.stringContaining(`source ${rcFilePath} && my-command`), + expect.any(String), + expect.any(Function), + expect.any(AbortSignal), + false, + expect.any(Object), + ); + }); + it('should format error messages correctly', async () => { const error = new Error('wrapped command failed'); const invocation = shellTool.build({ command: 'user-command' }); diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts index a2cb44aba0..90bca1a4be 100644 --- a/packages/core/src/tools/shell.ts +++ b/packages/core/src/tools/shell.ts @@ -462,13 +462,19 @@ export class ShellToolInvocation extends BaseToolInvocation< const combinedController = new AbortController(); const onAbort = () => combinedController.abort(); + let strippedCommandWithRc = strippedCommand; + const rcFilePath = this.context.config.getShellToolRcFile(); + if (rcFilePath) { + strippedCommandWithRc = `source ${rcFilePath} && ${strippedCommand}`; + } + try { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-shell-')); tempFilePath = path.join(tempDir, 'pgrep.tmp'); // pgrep is not available on Windows, so we can't get background PIDs const commandToExecute = this.wrapCommandForPgrep( - strippedCommand, + strippedCommandWithRc, tempFilePath, isWindows, );