diff --git a/docs/cli/configuration.md b/docs/cli/configuration.md index 1b880e0f..f7925725 100644 --- a/docs/cli/configuration.md +++ b/docs/cli/configuration.md @@ -44,6 +44,18 @@ When you create a `.gemini/settings.json` file for project-specific settings, or - **Default:** `GEMINI.md` - **Example:** `"contextFileName": "AGENTS.md"` +- **`bugCommand`** (object, optional): + + - **Description:** Overrides the default URL for the `/bug` command. + - **Properties:** + - **`urlTemplate`** (string, required): A URL that can contain `{title}` and `{body}` placeholders. + - **Example:** + ```json + "bugCommand": { + "urlTemplate": "https://bug.example.com/new?title={title}&body={body}" + } + ``` + - **`fileFiltering`** (object, optional): - **Description:** Controls git-aware file filtering behavior for @ commands and file discovery tools. diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 099d91d9..c1e51b16 100644 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -211,6 +211,7 @@ export async function loadCliConfig( telemetryOtlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? settings.telemetryOtlpEndpoint, fileDiscoveryService: fileService, + bugCommand: settings.bugCommand, }); } diff --git a/packages/cli/src/config/settings.ts b/packages/cli/src/config/settings.ts index bf9adb45..a0030a05 100644 --- a/packages/cli/src/config/settings.ts +++ b/packages/cli/src/config/settings.ts @@ -7,7 +7,11 @@ import * as fs from 'fs'; import * as path from 'path'; import { homedir } from 'os'; -import { MCPServerConfig, getErrorMessage } from '@gemini-cli/core'; +import { + MCPServerConfig, + getErrorMessage, + BugCommandSettings, +} from '@gemini-cli/core'; import stripJsonComments from 'strip-json-comments'; import { DefaultLight } from '../ui/themes/default-light.js'; import { DefaultDark } from '../ui/themes/default.js'; @@ -40,6 +44,7 @@ export interface Settings { telemetry?: boolean; telemetryOtlpEndpoint?: string; preferredEditor?: string; + bugCommand?: BugCommandSettings; // Git-aware file filtering settings fileFiltering?: { diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts index 73669651..3e0a768c 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts @@ -129,6 +129,7 @@ describe('useSlashCommandProcessor', () => { getModel: vi.fn(() => 'test-model'), getProjectRoot: vi.fn(() => '/test/dir'), getCheckpointEnabled: vi.fn(() => true), + getBugCommand: vi.fn(() => undefined), } as unknown as Config; mockCorgiMode = vi.fn(); mockUseSessionStats.mockReturnValue({ @@ -418,6 +419,47 @@ Add any other context about the problem here. expect(open).toHaveBeenCalledWith(expectedUrl); expect(commandResult).toBe(true); }); + + it('should use the custom bug command URL from config if available', async () => { + const bugCommand = { + urlTemplate: + 'https://custom-bug-tracker.com/new?title={title}&body={body}', + }; + mockConfig = { + ...mockConfig, + getBugCommand: vi.fn(() => bugCommand), + } as unknown as Config; + + const { handleSlashCommand } = getProcessor(); + const bugDescription = 'This is a custom bug'; + const diagnosticInfo = ` +## Describe the bug +A clear and concise description of what the bug is. + +## Additional context +Add any other context about the problem here. + +## Diagnostic Information +* **CLI Version:** unknown +* **Git Commit:** ${GIT_COMMIT_INFO} +* **Operating System:** test-platform test-node-version +* **Sandbox Environment:** no sandbox +* **Model Version:** test-model +* **Memory Usage:** 11.8 MB +`; + const expectedUrl = bugCommand.urlTemplate + .replace('{title}', encodeURIComponent(bugDescription)) + .replace('{body}', encodeURIComponent(diagnosticInfo)); + + let commandResult: SlashCommandActionReturn | boolean = false; + await act(async () => { + commandResult = await handleSlashCommand(`/bug ${bugDescription}`); + }); + + expect(mockAddItem).toHaveBeenCalledTimes(2); + expect(open).toHaveBeenCalledWith(expectedUrl); + expect(commandResult).toBe(true); + }); }); describe('/quit and /exit commands', () => { diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.ts index 97374e4f..9a9b7596 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.ts @@ -512,13 +512,14 @@ Add any other context about the problem here. `; let bugReportUrl = - 'https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.md'; - if (bugDescription) { - const encodedArgs = encodeURIComponent(bugDescription); - bugReportUrl += `&title=${encodedArgs}`; + 'https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.md&title={title}&body={body}'; + const bugCommand = config?.getBugCommand(); + if (bugCommand?.urlTemplate) { + bugReportUrl = bugCommand.urlTemplate; } - const encodedBody = encodeURIComponent(diagnosticInfo); - bugReportUrl += `&body=${encodedBody}`; + bugReportUrl = bugReportUrl + .replace('{title}', encodeURIComponent(bugDescription)) + .replace('{body}', encodeURIComponent(diagnosticInfo)); addMessage({ type: MessageType.INFO, diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index ac0fabb0..32c3129d 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -36,6 +36,10 @@ export interface AccessibilitySettings { disableLoadingPhrases?: boolean; } +export interface BugCommandSettings { + urlTemplate: string; +} + export class MCPServerConfig { constructor( // For stdio transport @@ -87,6 +91,7 @@ export interface ConfigParameters { proxy?: string; cwd: string; fileDiscoveryService?: FileDiscoveryService; + bugCommand?: BugCommandSettings; } export class Config { @@ -121,6 +126,7 @@ export class Config { private readonly checkpoint: boolean; private readonly proxy: string | undefined; private readonly cwd: string; + private readonly bugCommand: BugCommandSettings | undefined; constructor(params: ConfigParameters) { this.sessionId = params.sessionId; @@ -154,6 +160,7 @@ export class Config { this.proxy = params.proxy; this.cwd = params.cwd ?? process.cwd(); this.fileDiscoveryService = params.fileDiscoveryService ?? null; + this.bugCommand = params.bugCommand; if (params.contextFileName) { setGeminiMdFilename(params.contextFileName); @@ -309,6 +316,10 @@ export class Config { return this.cwd; } + getBugCommand(): BugCommandSettings | undefined { + return this.bugCommand; + } + async getFileService(): Promise { if (!this.fileDiscoveryService) { this.fileDiscoveryService = new FileDiscoveryService(this.targetDir);