diff --git a/docs/cli/commands.md b/docs/cli/commands.md index 292b22d8460..6732b0a6b39 100644 --- a/docs/cli/commands.md +++ b/docs/cli/commands.md @@ -158,13 +158,15 @@ Slash commands provide meta-level control over the CLI itself. option or configured via [settings](../get-started/configuration.md). See [Checkpointing documentation](../cli/checkpointing.md) for more details. -- **`/settings`** +- [**`/settings`**](./settings.md) - **Description:** Open the settings editor to view and modify Gemini CLI settings. - **Details:** This command provides a user-friendly interface for changing settings that control the behavior and appearance of Gemini CLI. It is equivalent to manually editing the `.gemini/settings.json` file, but with - validation and guidance to prevent errors. + validation and guidance to prevent errors. See the + [settings documentation](./settings.md) for a full list of available + settings. - **Usage:** Simply run `/settings` and the editor will open. You can then browse or search for specific settings, view their current values, and modify them as desired. Changes to some settings are applied immediately, diff --git a/docs/cli/index.md b/docs/cli/index.md index 6344be4eb91..464647fca7a 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -7,11 +7,14 @@ overview of Gemini CLI, see the [main documentation page](../index.md). ## Basic features - **[Commands](./commands.md):** A reference for all built-in slash commands - (e.g., `/help`, `/chat`, `/tools`). - **[Custom Commands](./custom-commands.md):** Create your own commands and shortcuts for frequently used prompts. - **[Headless Mode](./headless.md):** Use Gemini CLI programmatically for scripting and automation. +- **[Model Selection](./model.md):** Configure the Gemini AI model used by the + CLI. +- **[Settings](./settings.md):** Configure various aspects of the CLI's behavior + and appearance. - **[Themes](./themes.md):** Customizing the CLI's appearance with different themes. - **[Keyboard Shortcuts](./keyboard-shortcuts.md):** A reference for all diff --git a/docs/cli/model.md b/docs/cli/model.md index c7a9da9db61..305cbce1eda 100644 --- a/docs/cli/model.md +++ b/docs/cli/model.md @@ -14,14 +14,28 @@ Use the following command in Gemini CLI: Running this command will open a dialog with your model options: -- **Auto (recommended):** Let the system choose the best model for your task. - Typically, this is the best option. -- **Pro:** For complex tasks that require deep reasoning and creativity. The Pro - model may take longer to return a response. -- **Flash:** For tasks that need a balance of speed and reasoning. The Flash - model will usually return a faster response than Pro. -- **Flash-Lite:** For simple tasks that need to be done quickly. The Flash-Lite - model is typically the fastest. +| Option | Description | Models | +| ------------------ | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| Auto (recommended) | Let the system choose the best model for your task. | gemini-3-pro-preview (if enabled), gemini-2.5-pro, gemini-2.5-flash, gemini-2.5-flash-lite | +| Pro | For complex tasks that require deep reasoning and creativity. | gemini-3-pro-preview (if enabled), gemini-2.5-pro | +| Flash | For tasks that need a balance of speed and reasoning. | gemini-2.5-flash | +| Flash-Lite | For simple tasks that need to be done quickly. | gemini-2.5-flash-lite | + +### Gemini 3 Pro and Preview Features + +Note: Gemini 3 is not currently available on all account types. To learn more +about Gemini 3 access, refer to +[Gemini 3 Pro on Gemini CLI](../get-started/gemini-3). + +To enable Gemini 3 Pro (if available), enable +[**Preview features** by using the `settings` command](../cli/settings). Once +enabled, Gemini CLI will attempt to use Gemini 3 Pro when you select **Auto** or +**Pro**. Both **Auto** and **Pro** will try to use Gemini 3 Pro before falling +back to Gemini 2.5 Pro. + +You can also use the `--model` flag to specify a particular Gemini model on +startup. For more details, refer to the +[configuration documentation](./configuration.md). Changes to these settings will be applied to all subsequent interactions with Gemini CLI. diff --git a/docs/cli/settings.md b/docs/cli/settings.md new file mode 100644 index 00000000000..9c5784108de --- /dev/null +++ b/docs/cli/settings.md @@ -0,0 +1,113 @@ +# Gemini CLI Settings (`/settings` Command) + +Control your Gemini CLI experience with the `/settings` command. The `/settings` +command opens a dialog to view and edit all your Gemini CLI settings, including +your UI experience, keybindings, and accessibility features. + +Your Gemini CLI settings are stored in a `settings.json` file. In addition to +using the `/settings` command, you can also edit them in one of the following +locations: + +- **User settings**: `~/.gemini/settings.json` +- **Workspace settings**: `your-project/.gemini/settings.json` + +Note: Workspace settings override user settings. + +## Settings reference + +Here is a list of all the available settings, grouped by category and ordered as +they appear in the UI. + +### General + +| UI Label | Setting | Description | Default | +| ------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------- | ----------- | +| Preview Features (e.g., models) | `general.previewFeatures` | Enable preview features (e.g., preview models). | `false` | +| Vim Mode | `general.vimMode` | Enable Vim keybindings. | `false` | +| Disable Auto Update | `general.disableAutoUpdate` | Disable automatic updates. | `false` | +| Enable Prompt Completion | `general.enablePromptCompletion` | Enable AI-powered prompt completion suggestions while typing. | `false` | +| Debug Keystroke Logging | `general.debugKeystrokeLogging` | Enable debug logging of keystrokes to the console. | `false` | +| Session Retention | `general.sessionRetention` | Settings for automatic session cleanup. This feature is disabled by default. | `undefined` | +| Enable Session Cleanup | `general.sessionRetention.enabled` | Enable automatic session cleanup. | `false` | + +### Output + +| UI Label | Setting | Description | Default | +| ------------- | --------------- | ------------------------------------------------------ | ------- | +| Output Format | `output.format` | The format of the CLI output. Can be `text` or `json`. | `text` | + +### UI + +| UI Label | Setting | Description | Default | +| ------------------------------ | ---------------------------------------- | -------------------------------------------------------------------- | ------- | +| Hide Window Title | `ui.hideWindowTitle` | Hide the window title bar. | `false` | +| Show Status in Title | `ui.showStatusInTitle` | Show Gemini CLI status and thoughts in the terminal window title. | `false` | +| Hide Tips | `ui.hideTips` | Hide helpful tips in the UI. | `false` | +| Hide Banner | `ui.hideBanner` | Hide the application banner. | `false` | +| Hide Context Summary | `ui.hideContextSummary` | Hide the context summary (GEMINI.md, MCP servers) above the input. | `false` | +| Hide CWD | `ui.footer.hideCWD` | Hide the current working directory path in the footer. | `false` | +| Hide Sandbox Status | `ui.footer.hideSandboxStatus` | Hide the sandbox status indicator in the footer. | `false` | +| Hide Model Info | `ui.footer.hideModelInfo` | Hide the model name and context usage in the footer. | `false` | +| Hide Context Window Percentage | `ui.footer.hideContextPercentage` | Hides the context window remaining percentage. | `true` | +| Hide Footer | `ui.hideFooter` | Hide the footer from the UI. | `false` | +| Show Memory Usage | `ui.showMemoryUsage` | Display memory usage information in the UI. | `false` | +| Show Line Numbers | `ui.showLineNumbers` | Show line numbers in the chat. | `false` | +| Show Citations | `ui.showCitations` | Show citations for generated text in the chat. | `false` | +| Use Full Width | `ui.useFullWidth` | Use the entire width of the terminal for output. | `true` | +| Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `true` | +| Disable Loading Phrases | `ui.accessibility.disableLoadingPhrases` | Disable loading phrases for accessibility. | `false` | +| Screen Reader Mode | `ui.accessibility.screenReader` | Render output in plain-text to be more screen reader accessible. | `false` | + +### IDE + +| UI Label | Setting | Description | Default | +| -------- | ------------- | ---------------------------- | ------- | +| IDE Mode | `ide.enabled` | Enable IDE integration mode. | `false` | + +### Model + +| UI Label | Setting | Description | Default | +| ----------------------- | ---------------------------- | -------------------------------------------------------------------------------------- | ------- | +| Max Session Turns | `model.maxSessionTurns` | Maximum number of user/model/tool turns to keep in a session. -1 means unlimited. | `-1` | +| Compression Threshold | `model.compressionThreshold` | The fraction of context usage at which to trigger context compression (e.g. 0.2, 0.3). | `0.2` | +| Skip Next Speaker Check | `model.skipNextSpeakerCheck` | Skip the next speaker check. | `true` | + +### Context + +| UI Label | Setting | Description | Default | +| ------------------------------------ | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| Memory Discovery Max Dirs | `context.discoveryMaxDirs` | Maximum number of directories to search for memory. | `200` | +| Load Memory From Include Directories | `context.loadMemoryFromIncludeDirectories` | Controls how /memory refresh loads GEMINI.md files. When true, include directories are scanned; when false, only the current directory is used. | `false` | +| Respect .gitignore | `context.fileFiltering.respectGitIgnore` | Respect .gitignore files when searching. | `true` | +| Respect .geminiignore | `context.fileFiltering.respectGeminiIgnore` | Respect .geminiignore files when searching. | `true` | +| Enable Recursive File Search | `context.fileFiltering.enableRecursiveFileSearch` | Enable recursive file search functionality when completing @ references in the prompt. | `true` | +| Disable Fuzzy Search | `context.fileFiltering.disableFuzzySearch` | Disable fuzzy search when searching for files. | `false` | + +### Tools + +| UI Label | Setting | Description | Default | +| -------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------- | ------- | +| Enable Interactive Shell | `tools.shell.enableInteractiveShell` | Use node-pty for an interactive shell experience. Fallback to child_process still applies. | `true` | +| Show Color | `tools.shell.showColor` | Show color in shell output. | `false` | +| Auto Accept | `tools.autoAccept` | Automatically accept and execute tool calls that are considered safe (e.g., read-only operations). | `false` | +| Use Ripgrep | `tools.useRipgrep` | Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance. | `true` | +| Enable Tool Output Truncation | `tools.enableToolOutputTruncation` | Enable truncation of large tool outputs. | `true` | +| Tool Output Truncation Threshold | `tools.truncateToolOutputThreshold` | Truncate tool output if it is larger than this many characters. Set to -1 to disable. | `10000` | +| Tool Output Truncation Lines | `tools.truncateToolOutputLines` | The number of lines to keep when truncating tool output. | `100` | +| Enable Message Bus Integration | `tools.enableMessageBusIntegration` | Enable policy-based tool confirmation via message bus integration. | `false` | + +### Security + +| UI Label | Setting | Description | Default | +| -------------------------- | ------------------------------ | -------------------------------------------------- | ------- | +| Disable YOLO Mode | `security.disableYoloMode` | Disable YOLO mode, even if enabled by a flag. | `false` | +| Blocks extensions from Git | `security.blockGitExtensions` | Blocks installing and loading extensions from Git. | `false` | +| Folder Trust | `security.folderTrust.enabled` | Setting to track whether Folder trust is enabled. | `false` | + +### Experimental + +| UI Label | Setting | Description | Default | +| ----------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------- | ------- | +| Use Model Router | `experimental.useModelRouter` | Enable model routing to route requests to the best model based on complexity. | `true` | +| Enable Codebase Investigator | `experimental.codebaseInvestigatorSettings.enabled` | Enable the Codebase Investigator agent. | `true` | +| Codebase Investigator Max Num Turns | `experimental.codebaseInvestigatorSettings.maxNumTurns` | Maximum number of turns for the Codebase Investigator agent. | `10` | diff --git a/docs/get-started/configuration.md b/docs/get-started/configuration.md index 633c10b6c72..5e1ddd27645 100644 --- a/docs/get-started/configuration.md +++ b/docs/get-started/configuration.md @@ -98,6 +98,11 @@ their corresponding top-level category object in your `settings.json` file. #### `general` +- **`general.previewFeatures`** (boolean): + - **Description:** Enable preview features (e.g., preview models). + - **Default:** `false` + - **Requires restart:** Yes + - **`general.preferredEditor`** (string): - **Description:** The preferred editor to open files in. - **Default:** `undefined` diff --git a/docs/get-started/gemini-3.md b/docs/get-started/gemini-3.md new file mode 100644 index 00000000000..dd1b19c2459 --- /dev/null +++ b/docs/get-started/gemini-3.md @@ -0,0 +1,107 @@ +# Gemini 3 Pro on Gemini CLI (Join the Waitlist) + +We’re excited to bring Gemini 3 Pro to Gemini CLI. For Google AI Ultra +subscribers and paid Gemini and Vertex API key holders, Gemini 3 Pro is already +available and ready to enable. For everyone else, we're gradually expanding +access through a waitlist. Sign up for the waitlist now to access Gemini 3 Pro +once approved. + +Note: Please wait until you have been approved to use Gemini 3 Pro to enable +**Preview Features**. If enabled early, the CLI will fallback to Gemini 2.5 Pro. + +## Do I need to join the waitlist? + +The following users will be **automatically granted access** to Gemini 3 Pro on +Gemini CLI: + +- Google AI Ultra subscribers. +- Gemini API key users + [with access to Gemini 3](https://ai.google.dev/gemini-api/docs/rate-limits). +- Vertex API key users + [with access to Gemini 3](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/quotas). + +For **Gemini Code Assist Enterprise users**, access is coming soon. + +Users not automatically granted access through one of these account types will +need to join the waitlist. This includes Google AI Pro, Gemini Code Assist +standard, and free tier users. + +Note: Whether you’re automatically granted access or accepted from the waitlist, +you’ll still need to enable Gemini 3 Pro +[using the `/settings` command](../cli/settings). + +## How to join the waitlist + +Users not automatically granted access will need to join the waitlist. Follow +these instructions to sign up: + +- Install Gemini CLI. +- Authenticate using the **Login with Google** option. You’ll see a banner that + says “Gemini 3 is now available.” If you do not see this banner, update your + installation of Gemini CLI to the most recent version. +- Fill out this Google form: + [Access Gemini 3 in Gemini CLI](https://goo.gle/geminicli-waitlist-signup). + Provide the email address of the account you used to authenticate with Gemini + CLI. + +Users will be onboarded in batches, subject to availability. When you’ve been +granted access to Gemini 3 Pro, you’ll receive an acceptance email to your +submitted email address. + +## How to use Gemini 3 Pro with Gemini CLI + +Once you receive your acceptance email–or if you are automatically granted +access–you still need to enable Gemini 3 Pro within Gemini CLI. + +To enable Gemini 3 Pro, use the `/settings` command in Gemini CLI and set +**Preview Features** to `true`. + +For more information, see [Gemini CLI Settings](../cli/settings). + +### Usage limits and fallback + +Gemini CLI will tell you when you reach your Gemini 3 Pro daily usage limit. +When you encounter that limit, you’ll be given the option to switch to Gemini +2.5 Pro, upgrade for higher limits, or stop. You’ll also be told when your usage +limit resets and Gemini 3 Pro can be used again. + +Similarly, when you reach your daily usage limit for Gemini 2.5 Pro, you’ll see +a message prompting fallback to Gemini 2.5 Flash. + +### Capacity errors + +There may be times when the Gemini 3 Pro model is overloaded. When that happens, +Gemini CLI will ask you to decide whether you want to keep trying Gemini 3 Pro +or fallback to Gemini 2.5 Pro. + +Note: The **Keep trying** option uses exponential backoff, in which Gemini CLI +waits longer between each retry, when the system is busy. If the retry doesn't +happen immediately, please wait a few minutes for the request to process. + +## Model selection & routing types + +When using Gemini CLI, you may want to control how your requests are routed +between models. By default, Gemini CLI uses **Auto** routing. + +When using Gemini 3 Pro, you may want to use Auto routing or Pro routing to +manage your usage limits: + +- **Auto routing:** Auto routing first determines whether a prompt involves a + complex or simple operation. For simple prompts, it will automatically use + Gemini 2.5 Flash. For complex prompts, if Gemini 3 Pro is enabled, it will use + Gemini 3 Pro; otherwise, it will use Gemini 2.5 Pro. +- **Pro routing:** If you want to ensure your task is processed by the most + capable model, use `/model` and select **Pro**. Gemini CLI will prioritize the + most capable model available, including Gemini 3 Pro if it has been enabled. + +To learn more about selecting a model and routing, refer to +[Gemini CLI Model Selection](../cli/model.md). + +## Need help? + +If you need help, we recommend searching for an existing +[GitHub issue](https://github.com/google-gemini/gemini-cli/issues). If you +cannot find a GitHub issue that matches your concern, you can +[create a new issue](https://github.com/google-gemini/gemini-cli/issues/new/choose). +For comments and feedback, consider opening a +[GitHub discussion](https://github.com/google-gemini/gemini-cli/discussions). diff --git a/docs/get-started/index.md b/docs/get-started/index.md index 30d5bbdad5d..5139b7423f2 100644 --- a/docs/get-started/index.md +++ b/docs/get-started/index.md @@ -63,3 +63,4 @@ To explore the power of Gemini CLI, see [Gemini CLI examples](./examples.md). - Find out more about [Gemini CLI's tools](../tools/index.md). - Review [Gemini CLI's commands](../cli/commands.md). +- Learn how to [get started with Gemini 3](./gemini-3.md). diff --git a/docs/ide-integration/index.md b/docs/ide-integration/index.md index 4bb49829386..67e751d2e60 100644 --- a/docs/ide-integration/index.md +++ b/docs/ide-integration/index.md @@ -4,8 +4,8 @@ Gemini CLI can integrate with your IDE to provide a more seamless and context-aware experience. This integration allows the CLI to understand your workspace better and enables powerful features like native in-editor diffing. -Currently, the only supported IDE is -[Visual Studio Code](https://code.visualstudio.com/) and other editors that +Currently, the supported IDEs are [Antigravity](https://antigravity.google), +[Visual Studio Code](https://code.visualstudio.com/), and other editors that support VS Code extensions. To build support for other editors, see the [IDE Companion Extension Spec](./ide-companion-spec.md). @@ -191,7 +191,7 @@ messages and how to resolve them. - **Cause:** You are running Gemini CLI in a terminal or environment that is not a supported IDE. - **Solution:** Run Gemini CLI from the integrated terminal of a supported - IDE, like VS Code. + IDE, like Antigravity or VS Code. - **Message:** `No installer is available for IDE. Please install the Gemini CLI Companion extension manually from the marketplace.` diff --git a/docs/index.md b/docs/index.md index 042c9ae24bc..e7e05ab775f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -28,12 +28,18 @@ This documentation is organized into the following sections: - **[Configuration](./get-started/configuration.md):** Information on configuring the CLI. - **[Examples](./get-started/examples.md):** Example usage of Gemini CLI. +- **[Get started with Gemini 3](./get-started/gemini-3.md):** Learn how to + enable and use Gemini 3. ### CLI - **[CLI overview](./cli/index.md):** Overview of the command-line interface. - **[Commands](./cli/commands.md):** Description of available CLI commands. - **[Enterprise](./cli/enterprise.md):** Gemini CLI for enterprise. +- **[Model Selection](./cli/model.md):** Select the model used to process your + commands with `/model`. +- **[Settings](./cli/settings.md):** Configure various aspects of the CLI's + behavior and appearance with `/settings`. - **[Themes](./cli/themes.md):** Themes for Gemini CLI. - **[Token Caching](./cli/token-caching.md):** Token caching and optimization. - **[Tutorials](./cli/tutorials.md):** Tutorials for Gemini CLI. diff --git a/docs/sidebar.json b/docs/sidebar.json index a4d0ba0e750..1a93aacf542 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -23,6 +23,10 @@ "label": "Gemini CLI Quickstart", "slug": "docs/get-started" }, + { + "label": "Gemini 3 Pro on Gemini CLI", + "slug": "docs/get-started/gemini-3" + }, { "label": "Authentication", "slug": "docs/get-started/authentication" @@ -80,6 +84,10 @@ "label": "Sandbox", "slug": "docs/cli/sandbox" }, + { + "label": "Settings", + "slug": "docs/cli/settings" + }, { "label": "Telemetry", "slug": "docs/cli/telemetry" diff --git a/integration-tests/test-helper.ts b/integration-tests/test-helper.ts index 507ffbccfdc..e83939420cc 100644 --- a/integration-tests/test-helper.ts +++ b/integration-tests/test-helper.ts @@ -308,6 +308,7 @@ export class TestRig { // Nightly releases sometimes becomes out of sync with local code and // triggers auto-update, which causes tests to fail. disableAutoUpdate: true, + previewFeatures: false, }, telemetry: { enabled: true, diff --git a/package-lock.json b/package-lock.json index ec434b94c93..72da9eb5242 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@google/gemini-cli", - "version": "0.16.0-nightly.20251113.ad1f0d99", + "version": "0.16.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@google/gemini-cli", - "version": "0.16.0-nightly.20251113.ad1f0d99", + "version": "0.16.0", "workspaces": [ "packages/*" ], @@ -2301,7 +2301,6 @@ "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", @@ -2482,7 +2481,6 @@ "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" } @@ -2516,7 +2514,6 @@ "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" }, @@ -2885,7 +2882,6 @@ "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" @@ -2919,7 +2915,6 @@ "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" @@ -2972,7 +2967,6 @@ "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", @@ -4154,7 +4148,6 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4442,7 +4435,6 @@ "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", @@ -5210,7 +5202,6 @@ "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" }, @@ -5565,7 +5556,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/array-includes": { "version": "3.1.9", @@ -6811,6 +6803,7 @@ "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" }, @@ -7817,7 +7810,6 @@ "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -8407,6 +8399,7 @@ "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" } @@ -8416,6 +8409,7 @@ "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" } @@ -8425,6 +8419,7 @@ "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" } @@ -8654,6 +8649,7 @@ "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", @@ -8672,6 +8668,7 @@ "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" } @@ -8680,13 +8677,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "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" } @@ -9891,7 +9890,6 @@ "resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.3.tgz", "integrity": "sha512-2qm05tjtdia+d1gD7LQjPJyCPJluKDuR5B+FI3ZZXshFoU1igZBFvXs2++x9OT6d9755q+gkRPOdtH8jzx5MiQ==", "license": "MIT", - "peer": true, "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.1", "ansi-escapes": "^7.0.0", @@ -12943,7 +12941,8 @@ "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" + "license": "MIT", + "peer": true }, "node_modules/path-type": { "version": "3.0.0", @@ -13477,7 +13476,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -13488,7 +13486,6 @@ "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" @@ -15549,7 +15546,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -15714,8 +15710,7 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tsx": { "version": "4.20.3", @@ -15723,7 +15718,6 @@ "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -15908,7 +15902,6 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16070,6 +16063,7 @@ "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" } @@ -16125,7 +16119,6 @@ "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -16242,7 +16235,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -16256,7 +16248,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -16954,7 +16945,6 @@ "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" } @@ -16970,7 +16960,7 @@ }, "packages/a2a-server": { "name": "@google/gemini-cli-a2a-server", - "version": "0.16.0-nightly.20251113.ad1f0d99", + "version": "0.16.0", "dependencies": { "@a2a-js/sdk": "^0.3.2", "@google-cloud/storage": "^7.16.0", @@ -17260,7 +17250,7 @@ }, "packages/cli": { "name": "@google/gemini-cli", - "version": "0.16.0-nightly.20251113.ad1f0d99", + "version": "0.16.0", "dependencies": { "@google/gemini-cli-core": "file:../core", "@google/genai": "1.16.0", @@ -17360,7 +17350,7 @@ }, "packages/core": { "name": "@google/gemini-cli-core", - "version": "0.16.0-nightly.20251113.ad1f0d99", + "version": "0.16.0", "dependencies": { "@google-cloud/logging": "^11.2.1", "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", @@ -17495,7 +17485,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -17505,7 +17494,7 @@ }, "packages/test-utils": { "name": "@google/gemini-cli-test-utils", - "version": "0.16.0-nightly.20251113.ad1f0d99", + "version": "0.16.0", "license": "Apache-2.0", "devDependencies": { "typescript": "^5.3.3" @@ -17516,7 +17505,7 @@ }, "packages/vscode-ide-companion": { "name": "gemini-cli-vscode-ide-companion", - "version": "0.16.0-nightly.20251113.ad1f0d99", + "version": "0.16.0", "license": "LICENSE", "dependencies": { "@modelcontextprotocol/sdk": "^1.15.1", diff --git a/package.json b/package.json index 3fb9b057247..a3975f52985 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@google/gemini-cli", - "version": "0.16.0-nightly.20251113.ad1f0d99", + "version": "0.16.0", "engines": { "node": ">=20.0.0" }, @@ -14,7 +14,7 @@ "url": "git+https://github.com/google-gemini/gemini-cli.git" }, "config": { - "sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.16.0-nightly.20251113.ad1f0d99" + "sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.16.0" }, "scripts": { "start": "cross-env NODE_ENV=development node scripts/start.js", diff --git a/packages/a2a-server/package.json b/packages/a2a-server/package.json index 869f26d4aa4..cecb61eb65d 100644 --- a/packages/a2a-server/package.json +++ b/packages/a2a-server/package.json @@ -1,6 +1,6 @@ { "name": "@google/gemini-cli-a2a-server", - "version": "0.16.0-nightly.20251113.ad1f0d99", + "version": "0.16.0", "description": "Gemini CLI A2A Server", "repository": { "type": "git", diff --git a/packages/cli/package.json b/packages/cli/package.json index 45d61285c99..d7aabb3f8ea 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@google/gemini-cli", - "version": "0.16.0-nightly.20251113.ad1f0d99", + "version": "0.16.0", "description": "Gemini CLI", "repository": { "type": "git", @@ -25,7 +25,7 @@ "dist" ], "config": { - "sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.16.0-nightly.20251113.ad1f0d99" + "sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.16.0" }, "dependencies": { "@google/gemini-cli-core": "file:../core", diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 7396728d3c6..e21020d4452 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -583,6 +583,7 @@ export async function loadCliConfig( settings.context?.loadMemoryFromIncludeDirectories || false, debugMode, question, + previewFeatures: settings.general?.previewFeatures, coreTools: settings.tools?.core || undefined, allowedTools: allowedTools.length > 0 ? allowedTools : undefined, diff --git a/packages/cli/src/config/settingsSchema.test.ts b/packages/cli/src/config/settingsSchema.test.ts index 421d51a7f87..fe2503029b5 100644 --- a/packages/cli/src/config/settingsSchema.test.ts +++ b/packages/cli/src/config/settingsSchema.test.ts @@ -322,6 +322,30 @@ describe('SettingsSchema', () => { ).toBe('Enable debug logging of keystrokes to the console.'); }); + it('should have previewFeatures setting in schema', () => { + expect( + getSettingsSchema().general.properties.previewFeatures, + ).toBeDefined(); + expect(getSettingsSchema().general.properties.previewFeatures.type).toBe( + 'boolean', + ); + expect( + getSettingsSchema().general.properties.previewFeatures.category, + ).toBe('General'); + expect( + getSettingsSchema().general.properties.previewFeatures.default, + ).toBe(false); + expect( + getSettingsSchema().general.properties.previewFeatures.requiresRestart, + ).toBe(true); + expect( + getSettingsSchema().general.properties.previewFeatures.showInDialog, + ).toBe(true); + expect( + getSettingsSchema().general.properties.previewFeatures.description, + ).toBe('Enable preview features (e.g., preview models).'); + }); + it('should have useModelRouter setting in schema', () => { expect( getSettingsSchema().experimental.properties.useModelRouter, diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index fe5670fd16c..d27517f685f 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -160,6 +160,15 @@ const SETTINGS_SCHEMA = { description: 'General application settings.', showInDialog: false, properties: { + previewFeatures: { + type: 'boolean', + label: 'Preview Features (e.g., models)', + category: 'General', + requiresRestart: true, + default: false, + description: 'Enable preview features (e.g., preview models).', + showInDialog: true, + }, preferredEditor: { type: 'string', label: 'Preferred Editor', @@ -251,6 +260,7 @@ const SETTINGS_SCHEMA = { category: 'General', requiresRestart: false, default: undefined as SessionRetentionSettings | undefined, + showInDialog: false, properties: { enabled: { type: 'boolean', diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx index de9213a3f6a..3e28a941740 100644 --- a/packages/cli/src/ui/App.test.tsx +++ b/packages/cli/src/ui/App.test.tsx @@ -72,6 +72,10 @@ describe('App', () => { }, history: [], pendingHistoryItems: [], + bannerData: { + defaultText: 'Mock Banner Text', + warningText: '', + }, }; const mockConfig = makeFakeConfig(); diff --git a/packages/cli/src/ui/AppContainer.test.tsx b/packages/cli/src/ui/AppContainer.test.tsx index bcff805fba0..acbf175d356 100644 --- a/packages/cli/src/ui/AppContainer.test.tsx +++ b/packages/cli/src/ui/AppContainer.test.tsx @@ -25,6 +25,7 @@ import { CoreEvent, type UserFeedbackPayload, type ResumedSessionData, + AuthType, } from '@google/gemini-cli-core'; // Mock coreEvents @@ -1796,4 +1797,21 @@ describe('AppContainer State Management', () => { unmount(); }); }); + describe('Banner Text', () => { + it('should render placeholder banner text for USE_GEMINI auth type', async () => { + const config = makeFakeConfig(); + vi.spyOn(config, 'getContentGeneratorConfig').mockReturnValue({ + authType: AuthType.USE_GEMINI, + apiKey: 'fake-key', + }); + const { unmount } = renderAppContainer(); + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + await vi.waitFor(() => { + expect(capturedUIState.bannerData.defaultText).toBeDefined(); + unmount(); + }); + }); + }); }); diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index 01fce895f3e..71e795fdd4c 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -109,7 +109,7 @@ import { disableMouseEvents, enableMouseEvents } from './utils/mouse.js'; import { useAlternateBuffer } from './hooks/useAlternateBuffer.js'; import { useSettings } from './contexts/SettingsContext.js'; -const CTRL_EXIT_PROMPT_DURATION_MS = 1000; +const WARNING_PROMPT_DURATION_MS = 1000; const QUEUE_ERROR_DISPLAY_DURATION_MS = 3000; function isToolExecuting(pendingHistoryItems: HistoryItemWithoutId[]) { @@ -176,6 +176,10 @@ export const AppContainer = (props: AppContainerProps) => { null, ); + const [defaultBannerText, setDefaultBannerText] = useState(''); + const [warningBannerText, setWarningBannerText] = useState(''); + const [bannerVisible, setBannerVisible] = useState(true); + const extensionManager = config.getExtensionLoader() as ExtensionManager; // We are in the interactive CLI, update how we request consent and settings. extensionManager.setRequestConsent((description) => @@ -585,6 +589,7 @@ Logging in with Google... Please restart Gemini CLI to continue. slashCommandActions, extensionsUpdateStateInternal, isConfigInitialized, + setBannerVisible, ); const performMemoryRefresh = useCallback(async () => { @@ -892,6 +897,7 @@ Logging in with Google... Please restart Gemini CLI to continue. >(); const [showEscapePrompt, setShowEscapePrompt] = useState(false); const [showIdeRestartPrompt, setShowIdeRestartPrompt] = useState(false); + const [warningMessage, setWarningMessage] = useState(null); const { isFolderTrustDialogOpen, handleFolderTrustSelect, isRestarting } = useFolderTrust(settings, setIsTrustedFolder, historyManager.addItem); @@ -901,6 +907,36 @@ Logging in with Google... Please restart Gemini CLI to continue. } = useIdeTrustListener(); const isInitialMount = useRef(true); + useEffect(() => { + let timeoutId: NodeJS.Timeout; + + const handleWarning = (message: string) => { + setWarningMessage(message); + if (timeoutId) { + clearTimeout(timeoutId); + } + timeoutId = setTimeout(() => { + setWarningMessage(null); + }, WARNING_PROMPT_DURATION_MS); + }; + + const handleSelectionWarning = () => { + handleWarning('Press Ctrl-S to enter selection mode to copy text.'); + }; + const handlePasteTimeout = () => { + handleWarning('Paste Timed out. Possibly due to slow connection.'); + }; + appEvents.on(AppEvent.SelectionWarning, handleSelectionWarning); + appEvents.on(AppEvent.PasteTimeout, handlePasteTimeout); + return () => { + appEvents.off(AppEvent.SelectionWarning, handleSelectionWarning); + appEvents.off(AppEvent.PasteTimeout, handlePasteTimeout); + if (timeoutId) { + clearTimeout(timeoutId); + } + }; + }, []); + useEffect(() => { if (ideNeedsRestart) { // IDE trust changed, force a restart. @@ -976,7 +1012,7 @@ Logging in with Google... Please restart Gemini CLI to continue. ctrlCTimerRef.current = setTimeout(() => { setCtrlCPressCount(0); ctrlCTimerRef.current = null; - }, CTRL_EXIT_PROMPT_DURATION_MS); + }, WARNING_PROMPT_DURATION_MS); } }, [ctrlCPressCount, config, setCtrlCPressCount, handleSlashCommand]); @@ -994,7 +1030,7 @@ Logging in with Google... Please restart Gemini CLI to continue. ctrlDTimerRef.current = setTimeout(() => { setCtrlDPressCount(0); ctrlDTimerRef.current = null; - }, CTRL_EXIT_PROMPT_DURATION_MS); + }, WARNING_PROMPT_DURATION_MS); } }, [ctrlDPressCount, config, setCtrlDPressCount, handleSlashCommand]); @@ -1259,6 +1295,38 @@ Logging in with Google... Please restart Gemini CLI to continue. }; }, []); + useEffect(() => { + let isMounted = true; + + const fetchBannerTexts = async () => { + const [defaultBanner, warningBanner] = await Promise.all([ + config.getBannerTextNoCapacityIssues(), + config.getBannerTextCapacityIssues(), + ]); + + if (isMounted) { + setDefaultBannerText(defaultBanner); + setWarningBannerText(warningBanner); + setBannerVisible(true); + refreshStatic(); + const authType = config.getContentGeneratorConfig()?.authType; + if ( + authType === AuthType.USE_GEMINI || + authType === AuthType.USE_VERTEX_AI + ) { + setDefaultBannerText( + 'Gemini 3 is now available.\nTo use Gemini 3, enable "Preview features" in /settings\nLearn more at https://goo.gle/enable-preview-features', + ); + } + } + }; + fetchBannerTexts(); + + return () => { + isMounted = false; + }; + }, [config, refreshStatic]); + const uiState: UIState = useMemo( () => ({ history: historyManager.history, @@ -1345,6 +1413,12 @@ Logging in with Google... Please restart Gemini CLI to continue. embeddedShellFocused, showDebugProfiler, copyModeEnabled, + warningMessage, + bannerData: { + defaultText: defaultBannerText, + warningText: warningBannerText, + }, + bannerVisible, }), [ isThemeDialogOpen, @@ -1430,6 +1504,10 @@ Logging in with Google... Please restart Gemini CLI to continue. apiKeyDefaultValue, authState, copyModeEnabled, + warningMessage, + defaultBannerText, + warningBannerText, + bannerVisible, ], ); @@ -1466,6 +1544,7 @@ Logging in with Google... Please restart Gemini CLI to continue. popAllMessages, handleApiKeySubmit, handleApiKeyCancel, + setBannerVisible, }), [ handleThemeSelect, @@ -1494,6 +1573,7 @@ Logging in with Google... Please restart Gemini CLI to continue. popAllMessages, handleApiKeySubmit, handleApiKeyCancel, + setBannerVisible, ], ); diff --git a/packages/cli/src/ui/commands/ideCommand.ts b/packages/cli/src/ui/commands/ideCommand.ts index 9ce40868092..3cb82a201bf 100644 --- a/packages/cli/src/ui/commands/ideCommand.ts +++ b/packages/cli/src/ui/commands/ideCommand.ts @@ -121,11 +121,12 @@ async function getIdeStatusMessageWithFiles(ideClient: IdeClient): Promise<{ async function setIdeModeAndSyncConnection( config: Config, value: boolean, + options: { logToConsole?: boolean } = {}, ): Promise { config.setIdeMode(value); const ideClient = await IdeClient.getInstance(); if (value) { - await ideClient.connect(); + await ideClient.connect(options); logIdeConnection(config, new IdeConnectionEvent(IdeConnectionType.SESSION)); } else { await ideClient.disconnect(); @@ -144,7 +145,7 @@ export const ideCommand = async (): Promise => { ({ type: 'message', messageType: 'error', - content: `IDE integration is not supported in your current environment. To use this feature, run Gemini CLI in one of these supported IDEs: VS Code or VS Code forks.`, + content: `IDE integration is not supported in your current environment. To use this feature, run Gemini CLI in one of these supported IDEs: Antigravity, VS Code, or VS Code forks.`, }) as const, }; } @@ -212,7 +213,9 @@ export const ideCommand = async (): Promise => { ); // Poll for up to 5 seconds for the extension to activate. for (let i = 0; i < 10; i++) { - await setIdeModeAndSyncConnection(context.services.config!, true); + await setIdeModeAndSyncConnection(context.services.config!, true, { + logToConsole: false, + }); if ( ideClient.getConnectionStatus().status === IDEConnectionStatus.Connected diff --git a/packages/cli/src/ui/components/AlternateBufferQuittingDisplay.test.tsx b/packages/cli/src/ui/components/AlternateBufferQuittingDisplay.test.tsx index 2d6b8803180..c1f488d9020 100644 --- a/packages/cli/src/ui/components/AlternateBufferQuittingDisplay.test.tsx +++ b/packages/cli/src/ui/components/AlternateBufferQuittingDisplay.test.tsx @@ -12,6 +12,10 @@ import { Text } from 'ink'; import { renderWithProviders } from '../../test-utils/render.js'; import type { Config } from '@google/gemini-cli-core'; +vi.mock('../utils/terminalSetup.js', () => ({ + getTerminalProgram: () => null, +})); + vi.mock('../contexts/AppContext.js', () => ({ useAppContext: () => ({ version: '0.10.0', @@ -85,6 +89,11 @@ const mockConfig = { getTargetDir: () => '/tmp', getDebugMode: () => false, getGeminiMdFileCount: () => 0, + getExperiments: () => ({ + flags: {}, + experimentIds: [], + }), + getPreviewFeatures: () => false, } as unknown as Config; describe('AlternateBufferQuittingDisplay', () => { @@ -101,6 +110,10 @@ describe('AlternateBufferQuittingDisplay', () => { activePtyId: undefined, embeddedShellFocused: false, renderMarkdown: false, + bannerData: { + defaultText: '', + warningText: '', + }, }, config: mockConfig, }, diff --git a/packages/cli/src/ui/components/AppHeader.test.tsx b/packages/cli/src/ui/components/AppHeader.test.tsx new file mode 100644 index 00000000000..a6f9103f188 --- /dev/null +++ b/packages/cli/src/ui/components/AppHeader.test.tsx @@ -0,0 +1,189 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { renderWithProviders } from '../../test-utils/render.js'; +import { AppHeader } from './AppHeader.js'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { makeFakeConfig } from '@google/gemini-cli-core'; + +const persistentStateMock = vi.hoisted(() => ({ + get: vi.fn(), + set: vi.fn(), +})); + +vi.mock('../../utils/persistentState.js', () => ({ + persistentState: persistentStateMock, +})); + +vi.mock('../utils/terminalSetup.js', () => ({ + getTerminalProgram: () => null, +})); + +describe('', () => { + beforeEach(() => { + vi.clearAllMocks(); + persistentStateMock.get.mockReturnValue(0); + }); + + it('should render the banner with default text', () => { + const mockConfig = makeFakeConfig(); + const uiState = { + bannerData: { + defaultText: 'This is the default banner', + warningText: '', + }, + bannerVisible: true, + }; + + const { lastFrame, unmount } = renderWithProviders( + , + { config: mockConfig, uiState }, + ); + + expect(lastFrame()).toContain('This is the default banner'); + expect(lastFrame()).toMatchSnapshot(); + unmount(); + }); + + it('should render the banner with warning text', () => { + const mockConfig = makeFakeConfig(); + const uiState = { + bannerData: { + defaultText: 'This is the default banner', + warningText: 'There are capacity issues', + }, + bannerVisible: true, + }; + + const { lastFrame, unmount } = renderWithProviders( + , + { config: mockConfig, uiState }, + ); + + expect(lastFrame()).toContain('There are capacity issues'); + expect(lastFrame()).toMatchSnapshot(); + unmount(); + }); + + it('should not render the banner when no flags are set', () => { + const mockConfig = makeFakeConfig(); + const uiState = { + bannerData: { + defaultText: '', + warningText: '', + }, + }; + + const { lastFrame, unmount } = renderWithProviders( + , + { config: mockConfig, uiState }, + ); + + expect(lastFrame()).not.toContain('Banner'); + expect(lastFrame()).toMatchSnapshot(); + unmount(); + }); + + it('should render the banner when previewFeatures is disabled', () => { + const mockConfig = makeFakeConfig({ previewFeatures: false }); + const uiState = { + bannerData: { + defaultText: 'This is the default banner', + warningText: '', + }, + bannerVisible: true, + }; + + const { lastFrame, unmount } = renderWithProviders( + , + { config: mockConfig, uiState }, + ); + + expect(lastFrame()).toContain('This is the default banner'); + expect(lastFrame()).toMatchSnapshot(); + unmount(); + }); + + it('should not render the banner when previewFeatures is enabled', () => { + const mockConfig = makeFakeConfig({ previewFeatures: true }); + const uiState = { + bannerData: { + defaultText: 'This is the default banner', + warningText: '', + }, + }; + + const { lastFrame, unmount } = renderWithProviders( + , + { config: mockConfig, uiState }, + ); + + expect(lastFrame()).not.toContain('This is the default banner'); + expect(lastFrame()).toMatchSnapshot(); + unmount(); + }); + + it('should not render the default banner if shown count is 5 or more', () => { + persistentStateMock.get.mockReturnValue(5); + const mockConfig = makeFakeConfig(); + const uiState = { + bannerData: { + defaultText: 'This is the default banner', + warningText: '', + }, + }; + + const { lastFrame, unmount } = renderWithProviders( + , + { config: mockConfig, uiState }, + ); + + expect(lastFrame()).not.toContain('This is the default banner'); + expect(lastFrame()).toMatchSnapshot(); + unmount(); + }); + + it('should increment the shown count when default banner is displayed', () => { + persistentStateMock.get.mockReturnValue(0); + const mockConfig = makeFakeConfig(); + const uiState = { + bannerData: { + defaultText: 'This is the default banner', + warningText: '', + }, + }; + + const { unmount } = renderWithProviders(, { + config: mockConfig, + uiState, + }); + + expect(persistentStateMock.set).toHaveBeenCalledWith( + 'defaultBannerShownCount', + 1, + ); + unmount(); + }); + + it('should render banner text with unescaped newlines', () => { + const mockConfig = makeFakeConfig(); + const uiState = { + bannerData: { + defaultText: 'First line\\nSecond line', + warningText: '', + }, + bannerVisible: true, + }; + + const { lastFrame, unmount } = renderWithProviders( + , + { config: mockConfig, uiState }, + ); + + expect(lastFrame()).not.toContain('First line\\nSecond line'); + unmount(); + }); +}); diff --git a/packages/cli/src/ui/components/AppHeader.tsx b/packages/cli/src/ui/components/AppHeader.tsx index 9b6c5fc3bd5..d66fad4a954 100644 --- a/packages/cli/src/ui/components/AppHeader.tsx +++ b/packages/cli/src/ui/components/AppHeader.tsx @@ -10,6 +10,11 @@ import { Tips } from './Tips.js'; import { useSettings } from '../contexts/SettingsContext.js'; import { useConfig } from '../contexts/ConfigContext.js'; import { useUIState } from '../contexts/UIStateContext.js'; +import { Banner } from './Banner.js'; +import { theme } from '../semantic-colors.js'; +import { Colors } from '../colors.js'; +import { persistentState } from '../../utils/persistentState.js'; +import { useState, useEffect, useRef } from 'react'; interface AppHeaderProps { version: string; @@ -18,12 +23,46 @@ interface AppHeaderProps { export const AppHeader = ({ version }: AppHeaderProps) => { const settings = useSettings(); const config = useConfig(); - const { nightly } = useUIState(); + const { nightly, mainAreaWidth, bannerData, bannerVisible } = useUIState(); + + const [defaultBannerShownCount] = useState( + () => persistentState.get('defaultBannerShownCount') || 0, + ); + + const { defaultText, warningText } = bannerData; + + const showDefaultBanner = + warningText === '' && + !config.getPreviewFeatures() && + defaultBannerShownCount < 5; + const bannerText = showDefaultBanner ? defaultText : warningText; + const unescapedBannerText = bannerText.replace(/\\n/g, '\n'); + + const defaultColor = Colors.AccentBlue; + const fontColor = warningText === '' ? defaultColor : theme.status.warning; + + const hasIncrementedRef = useRef(false); + useEffect(() => { + if (showDefaultBanner && defaultText && !hasIncrementedRef.current) { + hasIncrementedRef.current = true; + const current = persistentState.get('defaultBannerShownCount') || 0; + persistentState.set('defaultBannerShownCount', current + 1); + } + }, [showDefaultBanner, defaultText]); return ( {!(settings.merged.ui?.hideBanner || config.getScreenReader()) && ( -
+ <> +
+ {bannerVisible && unescapedBannerText && ( + + )} + )} {!(settings.merged.ui?.hideTips || config.getScreenReader()) && ( diff --git a/packages/cli/src/ui/components/Banner.tsx b/packages/cli/src/ui/components/Banner.tsx new file mode 100644 index 00000000000..8003abc9493 --- /dev/null +++ b/packages/cli/src/ui/components/Banner.tsx @@ -0,0 +1,33 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Box, Text } from 'ink'; +import Gradient from 'ink-gradient'; +import { theme } from '../semantic-colors.js'; + +interface BannerProps { + bannerText: string; + color: string; + width: number; +} + +export const Banner = ({ bannerText, color, width }: BannerProps) => { + const gradient = theme.ui.gradient; + return ( + + + {bannerText} + + + ); +}; diff --git a/packages/cli/src/ui/components/Composer.tsx b/packages/cli/src/ui/components/Composer.tsx index 6962ab21608..b4559a997f2 100644 --- a/packages/cli/src/ui/components/Composer.tsx +++ b/packages/cli/src/ui/components/Composer.tsx @@ -99,6 +99,8 @@ export const Composer = () => { Press Ctrl+C again to exit. + ) : uiState.warningMessage ? ( + {uiState.warningMessage} ) : uiState.ctrlDPressedOnce ? ( Press Ctrl+D again to exit. @@ -155,6 +157,7 @@ export const Composer = () => { suggestionsWidth={uiState.suggestionsWidth} onSubmit={uiActions.handleFinalSubmit} userMessages={uiState.userMessages} + setBannerVisible={uiActions.setBannerVisible} onClearScreen={uiActions.handleClearScreen} config={config} slashCommands={uiState.slashCommands || []} diff --git a/packages/cli/src/ui/components/DialogManager.tsx b/packages/cli/src/ui/components/DialogManager.tsx index 19ec2e276d1..4f2def324b1 100644 --- a/packages/cli/src/ui/components/DialogManager.tsx +++ b/packages/cli/src/ui/components/DialogManager.tsx @@ -53,8 +53,13 @@ export const DialogManager = ({ if (uiState.proQuotaRequest) { return ( ); } diff --git a/packages/cli/src/ui/components/EditorSettingsDialog.tsx b/packages/cli/src/ui/components/EditorSettingsDialog.tsx index 55434fdf9d2..ed15376de1a 100644 --- a/packages/cli/src/ui/components/EditorSettingsDialog.tsx +++ b/packages/cli/src/ui/components/EditorSettingsDialog.tsx @@ -9,7 +9,6 @@ import { useState } from 'react'; import { Box, Text } from 'ink'; import { theme } from '../semantic-colors.js'; import { - EDITOR_DISPLAY_NAMES, editorSettingsManager, type EditorDisplay, } from '../editors/editorSettingsManager.js'; @@ -19,8 +18,11 @@ import type { LoadedSettings, } from '../../config/settings.js'; import { SettingScope } from '../../config/settings.js'; -import type { EditorType } from '@google/gemini-cli-core'; -import { isEditorAvailable } from '@google/gemini-cli-core'; +import { + type EditorType, + isEditorAvailable, + EDITOR_DISPLAY_NAMES, +} from '@google/gemini-cli-core'; import { useKeypress } from '../hooks/useKeypress.js'; interface EditorDialogProps { diff --git a/packages/cli/src/ui/components/Footer.tsx b/packages/cli/src/ui/components/Footer.tsx index 3e51acd740d..6a803a39eb2 100644 --- a/packages/cli/src/ui/components/Footer.tsx +++ b/packages/cli/src/ui/components/Footer.tsx @@ -10,7 +10,7 @@ import { theme } from '../semantic-colors.js'; import { shortenPath, tildeifyPath } from '@google/gemini-cli-core'; import { ConsoleSummaryDisplay } from './ConsoleSummaryDisplay.js'; import process from 'node:process'; -import Gradient from 'ink-gradient'; +import { ThemedGradient } from './ThemedGradient.js'; import { MemoryUsageDisplay } from './MemoryUsageDisplay.js'; import { ContextUsageDisplay } from './ContextUsageDisplay.js'; import { DebugProfiler } from './DebugProfiler.js'; @@ -87,12 +87,10 @@ export const Footer: React.FC = () => { )} {!hideCWD && (nightly ? ( - - - {displayPath} - {branchName && ({branchName}*)} - - + + {displayPath} + {branchName && ({branchName}*)} + ) : ( {displayPath} diff --git a/packages/cli/src/ui/components/GradientRegression.test.tsx b/packages/cli/src/ui/components/GradientRegression.test.tsx new file mode 100644 index 00000000000..1b4bc8a4f72 --- /dev/null +++ b/packages/cli/src/ui/components/GradientRegression.test.tsx @@ -0,0 +1,94 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect, vi } from 'vitest'; +import { renderWithProviders } from '../../test-utils/render.js'; +import { Footer } from './Footer.js'; +import { StatsDisplay } from './StatsDisplay.js'; +import * as SessionContext from '../contexts/SessionContext.js'; +import type { SessionStatsState } from '../contexts/SessionContext.js'; + +// Mock the theme module +vi.mock('../semantic-colors.js', async (importOriginal) => { + const original = + await importOriginal(); + return { + ...original, + theme: { + ...original.theme, + ui: { + ...original.theme.ui, + gradient: [], // Empty array to potentially trigger the crash + }, + }, + }; +}); + +// Mock the context to provide controlled data for testing +vi.mock('../contexts/SessionContext.js', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useSessionStats: vi.fn(), + }; +}); + +const mockSessionStats: SessionStatsState = { + sessionId: 'test-session', + sessionStartTime: new Date(), + lastPromptTokenCount: 0, + promptCount: 0, + metrics: { + models: {}, + tools: { + totalCalls: 0, + totalSuccess: 0, + totalFail: 0, + totalDurationMs: 0, + totalDecisions: { accept: 0, reject: 0, modify: 0, auto_accept: 0 }, + byName: {}, + }, + files: { totalLinesAdded: 0, totalLinesRemoved: 0 }, + }, +}; + +const useSessionStatsMock = vi.mocked(SessionContext.useSessionStats); +useSessionStatsMock.mockReturnValue({ + stats: mockSessionStats, + getPromptCount: () => 0, + startNewPrompt: vi.fn(), +}); + +describe('Gradient Crash Regression Tests', () => { + it('