diff --git a/- b/- new file mode 100644 index 00000000000..e69de29bb2d diff --git a/.gemini/config.yaml b/.gemini/config.yaml index cbfb0c8059d..cb70ce967cf 100644 --- a/.gemini/config.yaml +++ b/.gemini/config.yaml @@ -3,10 +3,13 @@ have_fun: false code_review: disable: false - comment_severity_threshold: 'HIGH' - max_review_comments: -1 + comment_severity_threshold: 'MEDIUM' # Changed from HIGH to MEDIUM + max_review_comments: 20 # Changed from -1 to 20 pull_request_opened: help: false summary: true code_review: true -ignore_patterns: [] +ignore_patterns: + - "**/*.md" + - "**/package-lock.json" + - "**/dist/" \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8377d34af0e..a9b305b086d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,8 +1,11 @@ -# By default, require reviews from the maintainers for all files. -* @google-gemini/gemini-cli-maintainers +# By default, require reviews from the maintainers for all files + +- @google-gemini/gemini-cli-maintainers + +# Require reviews from the release approvers for critical files + +# These patterns override the rule above -# Require reviews from the release approvers for critical files. -# These patterns override the rule above. /package.json @google-gemini/gemini-cli-askmode-approvers /package-lock.json @google-gemini/gemini-cli-askmode-approvers /GEMINI.md @google-gemini/gemini-cli-askmode-approvers diff --git a/.vscode/settings.json b/.vscode/settings.json index 3661ecf9c2a..d490ce08ade 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,5 +16,7 @@ "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "vitest.disableWorkspaceWarning": true + "vitest.disableWorkspaceWarning": true, + "githubPullRequests.ignoredPullRequestBranches": ["main"], + "github.copilot.nextEditSuggestions.enabled": true } diff --git a/Happy b/Happy new file mode 100644 index 00000000000..e69de29bb2d diff --git a/package-lock.json b/package-lock.json index 0874de32a27..38c0fa3ca33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2500,6 +2500,7 @@ "integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.2", @@ -2680,6 +2681,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -2713,6 +2715,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -3081,6 +3084,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" @@ -3114,6 +3118,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz", "integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" @@ -3166,6 +3171,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz", "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", @@ -4396,6 +4402,7 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4673,6 +4680,7 @@ "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.35.0", "@typescript-eslint/types": "8.35.0", @@ -5684,6 +5692,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6129,8 +6138,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/array-includes": { "version": "3.1.9", @@ -7420,7 +7428,6 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "5.2.1" }, @@ -8744,6 +8751,7 @@ "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -9346,7 +9354,6 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -9356,7 +9363,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -9366,7 +9372,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -9620,7 +9625,6 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "license": "MIT", - "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -9639,7 +9643,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -9648,15 +9651,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/finalhandler/node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -10942,6 +10943,7 @@ "resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.6.tgz", "integrity": "sha512-QHl6l1cl3zPCaRMzt9TUbTX6Q5SzvkGEZDDad0DmSf5SPmT1/90k6pGPejEvDCJprkitwObXpPaTWGHItqsy4g==", "license": "MIT", + "peer": true, "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.1", "ansi-escapes": "^7.0.0", @@ -14136,8 +14138,7 @@ "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/path-type": { "version": "3.0.0", @@ -14718,6 +14719,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -14728,6 +14730,7 @@ "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" @@ -16987,6 +16990,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -17213,7 +17217,8 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsx": { "version": "4.20.3", @@ -17221,6 +17226,7 @@ "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -17405,6 +17411,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -17567,7 +17574,6 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4.0" } @@ -17623,6 +17629,7 @@ "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -17739,6 +17746,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -17752,6 +17760,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -18458,6 +18467,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -19017,6 +19027,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, diff --git a/packages/cli/src/config/geminiApiKey.test.ts b/packages/cli/src/config/geminiApiKey.test.ts new file mode 100644 index 00000000000..70d194488f6 --- /dev/null +++ b/packages/cli/src/config/geminiApiKey.test.ts @@ -0,0 +1,135 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AuthType , Config } from '@google/gemini-cli-core'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { createContentGeneratorConfig } from '@google/gemini-cli-core/core/contentGenerator'; +import { GoogleGenAI } from '@google/genai'; + +vi.mock('@google/genai', () => ({ + GoogleGenAI: vi.fn(() => ({ + models: {}, + })), +})); + +vi.mock('@google/gemini-cli-core/core/apiKeyCredentialStorage', () => ({ + loadApiKey: vi.fn(() => Promise.resolve(undefined)), +})); + +describe('Gemini API Key Configuration', () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.stubEnv('GEMINI_API_KEY', undefined); + }); + + it('should use geminiApiKey from Config when provided', async () => { + const mockApiKey = 'TEST_API_KEY_FROM_CONFIG'; + const mockConfig = new Config({ + sessionId: 'test', + targetDir: '/', + debugMode: false, + cwd: '/', + model: 'gemini-pro', + geminiApiKey: mockApiKey, + }); + + const contentGenConfig = await createContentGeneratorConfig( + mockConfig, + AuthType.USE_GEMINI, + ); + + expect(contentGenConfig.apiKey).toBe(mockApiKey); + expect(GoogleGenAI).toHaveBeenCalledWith( + expect.objectContaining({ + apiKey: mockApiKey, + }), + ); + }); + + it('should prioritize GEMINI_API_KEY environment variable over loadedApiKey if config.geminiApiKey is not set', async () => { + const envApiKey = 'TEST_API_KEY_FROM_ENV'; + vi.stubEnv('GEMINI_API_KEY', envApiKey); + + const loadApiKey = vi.mocked( + (await vi.importActual( + '@google/gemini-cli-core/core/apiKeyCredentialStorage', + )) as { loadApiKey: () => Promise }, + ).loadApiKey; + loadApiKey.mockResolvedValueOnce('LOADED_API_KEY'); + + const mockConfig = new Config({ + sessionId: 'test', + targetDir: '/', + debugMode: false, + cwd: '/', + model: 'gemini-pro', + }); + + const contentGenConfig = await createContentGeneratorConfig( + mockConfig, + AuthType.USE_GEMINI, + ); + + expect(contentGenConfig.apiKey).toBe(envApiKey); + expect(GoogleGenAI).toHaveBeenCalledWith( + expect.objectContaining({ + apiKey: envApiKey, + }), + ); + expect(loadApiKey).toHaveBeenCalled(); + }); + + it('should prioritize loadedApiKey if config.geminiApiKey and GEMINI_API_KEY env are not set', async () => { + const loadedApiKey = 'LOADED_API_KEY'; + const loadApiKey = vi.mocked( + (await vi.importActual( + '@google/gemini-cli-core/core/apiKeyCredentialStorage', + )) as { loadApiKey: () => Promise }, + ).loadApiKey; + loadApiKey.mockResolvedValueOnce(loadedApiKey); + + const mockConfig = new Config({ + sessionId: 'test', + targetDir: '/', + debugMode: false, + cwd: '/', + model: 'gemini-pro', + }); + + const contentGenConfig = await createContentGeneratorConfig( + mockConfig, + AuthType.USE_GEMINI, + ); + + expect(contentGenConfig.apiKey).toBe(loadedApiKey); + expect(GoogleGenAI).toHaveBeenCalledWith( + expect.objectContaining({ + apiKey: loadedApiKey, + }), + ); + expect(loadApiKey).toHaveBeenCalled(); + }); + + it('should not leak api key in proxy config', async () => { + const mockApiKey = 'TEST_API_KEY_FROM_CONFIG'; + const mockConfig = new Config({ + sessionId: 'test', + targetDir: '/', + debugMode: false, + cwd: '/', + model: 'gemini-pro', + geminiApiKey: mockApiKey, + proxy: `http://user:${mockApiKey}@someproxy.com`, + }); + + const contentGenConfig = await createContentGeneratorConfig( + mockConfig, + AuthType.USE_GEMINI, + ); + + expect(contentGenConfig.proxy).toBe('http://user:***@someproxy.com'); + }); +}); diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 9145918a488..f4c4c27183e 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -1240,6 +1240,15 @@ const SETTINGS_SCHEMA = { description: 'Whether to use an external authentication flow.', showInDialog: false, }, + geminiApiKey: { + type: 'string', + label: 'Gemini API Key', + category: 'Security', + requiresRestart: true, + default: undefined as string | undefined, + description: 'Your Gemini API Key.', + showInDialog: false, + }, }, }, }, diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index dc78a789660..69882bb8b41 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -310,6 +310,7 @@ export interface ConfigParameters { output?: OutputSettings; enableMessageBusIntegration?: boolean; disableModelRouterForAuth?: AuthType[]; + geminiApiKey?: string; codebaseInvestigatorSettings?: CodebaseInvestigatorSettings; introspectionAgentSettings?: IntrospectionAgentSettings; continueOnFailedApiCall?: boolean; diff --git a/packages/core/src/core/contentGenerator.ts b/packages/core/src/core/contentGenerator.ts index 7c733bce0f9..2e0b146c492 100644 --- a/packages/core/src/core/contentGenerator.ts +++ b/packages/core/src/core/contentGenerator.ts @@ -66,7 +66,7 @@ export async function createContentGeneratorConfig( authType: AuthType | undefined, ): Promise { const geminiApiKey = - process.env['GEMINI_API_KEY'] || (await loadApiKey()) || undefined; + const googleApiKey = process.env['GOOGLE_API_KEY'] || undefined; const googleCloudProject = process.env['GOOGLE_CLOUD_PROJECT'] || diff --git a/vitest.explorer-1.32.1.vsix b/vitest.explorer-1.32.1.vsix new file mode 100644 index 00000000000..86d1d00bedd Binary files /dev/null and b/vitest.explorer-1.32.1.vsix differ