mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-24 16:50:59 -07:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5765905f64 | |||
| 5a36725f22 | |||
| 9d028db351 | |||
| 30e2820dc2 | |||
| ae04632a59 | |||
| 703759cfae | |||
| 0063581e47 | |||
| 6757d4b5c5 | |||
| a153ff587b | |||
| cd3a8c3f07 | |||
| b2214a6676 | |||
| 6c65a2d813 | |||
| fae0639ba2 | |||
| 76f70d65ff | |||
| fb6ff847dd | |||
| 1ca5c05d0d | |||
| 0c6c9c6a62 | |||
| a1367e9cdd | |||
| c89d4f9c6c | |||
| 08ee136132 | |||
| 07f51c324e | |||
| ba149afa0b | |||
| f6533c0250 | |||
| 5e3b1fe945 | |||
| 05ef2eb362 | |||
| 966b9059d0 | |||
| ca13458412 | |||
| 20d884da2f | |||
| 6a0f4d3bdd | |||
| 0b682538d2 | |||
| 7f8ce8657c | |||
| 1c8951334a | |||
| b2b6092c24 | |||
| e00e8f4728 | |||
| b2cbf518e8 | |||
| c914fd0700 | |||
| 5d24d6a9e1 | |||
| ea48bd9414 | |||
| b2d6844f9b | |||
| fdd844b405 | |||
| 23905bcd77 |
@@ -0,0 +1,29 @@
|
||||
description = "Promote behavioral evals that have a 100% success rate over the last 7 nightly runs."
|
||||
prompt = """
|
||||
You are an expert at analyzing and promoting behavioral evaluations.
|
||||
|
||||
1. **Investigate**:
|
||||
- Use 'gh' cli to fetch the results from the most recent run from the main branch: https://github.com/google-gemini/gemini-cli/actions/workflows/evals-nightly.yml.
|
||||
- DO NOT push any changes or start any runs. The rest of your evaluation will be local.
|
||||
- Evals are in evals/ directory and are documented by evals/README.md.
|
||||
- Identify tests that have passed 100% of the time for ALL enabled models across the past 7 runs in a row.
|
||||
- NOTE: the results summary from the most recent run contains the last 7 runs test results. 100% means the test passed 3/3 times for that model and run.
|
||||
- If a test meets this criteria, it is a candidate for promotion.
|
||||
|
||||
2. **Promote**:
|
||||
- For each candidate test, locate the test file in the evals/ directory.
|
||||
- Promote the test according to the project's standard promotion process (e.g., moving it to a stable suite, updating its tags, or removing skip/flaky annotations).
|
||||
- Ensure you follow any guidelines in evals/README.md for stable tests.
|
||||
- Your **final** change should be **minimal and targeted** to just promoting the test status.
|
||||
|
||||
3. **Verify**:
|
||||
- Run the promoted tests locally to validate that they still execute correctly. Be sure to run vitest in non-interactive mode.
|
||||
- Check that the test is now part of the expected standard or stable test suites.
|
||||
|
||||
4. **Report**:
|
||||
- Provide a summary of the tests that were promoted.
|
||||
- Include the success rate evidence (7/7 runs passed for all models) for each promoted test.
|
||||
- If no tests met the criteria for promotion, clearly state that and summarize the closest candidates.
|
||||
|
||||
{{args}}
|
||||
"""
|
||||
@@ -9,4 +9,5 @@ code_review:
|
||||
help: false
|
||||
summary: true
|
||||
code_review: true
|
||||
include_drafts: false
|
||||
ignore_patterns: []
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
name: github-issue-creator
|
||||
description:
|
||||
Use this skill when asked to create a GitHub issue. It handles different issue
|
||||
types (bug, feature, etc.) using repository templates and ensures proper
|
||||
labeling.
|
||||
---
|
||||
|
||||
# GitHub Issue Creator
|
||||
|
||||
This skill guides the creation of high-quality GitHub issues that adhere to the
|
||||
repository's standards and use the appropriate templates.
|
||||
|
||||
## Workflow
|
||||
|
||||
Follow these steps to create a GitHub issue:
|
||||
|
||||
1. **Identify Issue Type**: Determine if the request is a bug report, feature
|
||||
request, or other category.
|
||||
|
||||
2. **Locate Template**: Search for issue templates in
|
||||
`.github/ISSUE_TEMPLATE/`.
|
||||
- `bug_report.yml`
|
||||
- `feature_request.yml`
|
||||
- `website_issue.yml`
|
||||
- If no relevant YAML template is found, look for `.md` templates in the same
|
||||
directory.
|
||||
|
||||
3. **Read Template**: Read the content of the identified template file to
|
||||
understand the required fields.
|
||||
|
||||
4. **Draft Content**: Draft the issue title and body/fields.
|
||||
- If using a YAML template (form), prepare values for each `id` defined in
|
||||
the template.
|
||||
- If using a Markdown template, follow its structure exactly.
|
||||
- **Default Label**: Always include the `🔒 maintainer only` label unless the
|
||||
user explicitly requests otherwise.
|
||||
|
||||
5. **Create Issue**: Use the `gh` CLI to create the issue.
|
||||
- **CRITICAL:** To avoid shell escaping and formatting issues with
|
||||
multi-line Markdown or complex text, ALWAYS write the description/body to
|
||||
a temporary file first.
|
||||
|
||||
**For Markdown Templates or Simple Body:**
|
||||
```bash
|
||||
# 1. Write the drafted content to a temporary file
|
||||
# 2. Create the issue using the --body-file flag
|
||||
gh issue create --title "Succinct title" --body-file <temp_file_path> --label "🔒 maintainer only"
|
||||
# 3. Remove the temporary file
|
||||
rm <temp_file_path>
|
||||
```
|
||||
|
||||
**For YAML Templates (Forms):**
|
||||
While `gh issue create` supports `--body-file`, YAML forms usually expect
|
||||
key-value pairs via flags if you want to bypass the interactive prompt.
|
||||
However, the most reliable non-interactive way to ensure formatting is
|
||||
preserved for long text fields is to use the `--body` or `--body-file` if the
|
||||
form has been converted to a standard body, OR to use the `--field` flags
|
||||
for YAML forms.
|
||||
|
||||
*Note: For the `gemini-cli` repository which uses YAML forms, you can often
|
||||
submit the content as a single body if a specific field-based submission is
|
||||
not required by the automation.*
|
||||
|
||||
6. **Verify**: Confirm the issue was created successfully and provide the link
|
||||
to the user.
|
||||
|
||||
## Principles
|
||||
|
||||
- **Clarity**: Titles should be descriptive and follow project conventions.
|
||||
- **Defensive Formatting**: Always use temporary files with `--body-file` to
|
||||
prevent newline and special character issues.
|
||||
- **Maintainer Priority**: Default to internal/maintainer labels to keep the
|
||||
backlog organized.
|
||||
- **Completeness**: Provide all requested information (e.g., version info,
|
||||
reproduction steps).
|
||||
@@ -0,0 +1,50 @@
|
||||
name: 'Deploy GitHub Pages'
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: 'read'
|
||||
pages: 'write'
|
||||
id-token: 'write'
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run
|
||||
# in-progress and latest queued. However, do NOT cancel in-progress runs as we
|
||||
# want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}'
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: "github.repository == 'google-gemini/gemini-cli' && !contains(github.ref_name, 'nightly')"
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
||||
|
||||
- name: 'Setup Pages'
|
||||
uses: 'actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b' # ratchet:actions/configure-pages@v5
|
||||
|
||||
- name: 'Build with Jekyll'
|
||||
uses: 'actions/jekyll-build-pages@44a6e6beabd48582f863aeeb6cb2151cc1716697' # ratchet:actions/jekyll-build-pages@v1
|
||||
with:
|
||||
source: './'
|
||||
destination: './_site'
|
||||
|
||||
- name: 'Upload artifact'
|
||||
uses: 'actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa' # ratchet:actions/upload-pages-artifact@v3
|
||||
|
||||
deploy:
|
||||
if: "github.repository == 'google-gemini/gemini-cli'"
|
||||
environment:
|
||||
name: 'github-pages'
|
||||
url: '${{ steps.deployment.outputs.page_url }}'
|
||||
runs-on: 'ubuntu-latest'
|
||||
needs: 'build'
|
||||
steps:
|
||||
- name: 'Deploy to GitHub Pages'
|
||||
id: 'deployment'
|
||||
uses: 'actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e' # ratchet:actions/deploy-pages@v4
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
branch-name: 'release/${{ steps.nightly_version.outputs.RELEASE_TAG }}'
|
||||
pr-title: 'chore/release: bump version to ${{ steps.nightly_version.outputs.RELEASE_VERSION }}'
|
||||
pr-body: 'Automated version bump for nightly release.'
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
github-token: '${{ secrets.GEMINI_CLI_ROBOT_GITHUB_PAT }}'
|
||||
dry-run: '${{ steps.vars.outputs.is_dry_run }}'
|
||||
working-directory: './release'
|
||||
|
||||
|
||||
@@ -335,6 +335,7 @@ jobs:
|
||||
name: 'Create Nightly PR'
|
||||
needs: ['publish-stable', 'calculate-versions']
|
||||
runs-on: 'ubuntu-latest'
|
||||
environment: "${{ github.event.inputs.environment || 'prod' }}"
|
||||
permissions:
|
||||
contents: 'write'
|
||||
pull-requests: 'write'
|
||||
@@ -397,7 +398,7 @@ jobs:
|
||||
branch-name: '${{ steps.release_branch.outputs.BRANCH_NAME }}'
|
||||
pr-title: 'chore(release): bump version to ${{ needs.calculate-versions.outputs.NEXT_NIGHTLY_VERSION }}'
|
||||
pr-body: 'Automated version bump to prepare for the next nightly release.'
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
github-token: '${{ secrets.GEMINI_CLI_ROBOT_GITHUB_PAT }}'
|
||||
dry-run: '${{ github.event.inputs.dry_run }}'
|
||||
|
||||
- name: 'Create Issue on Failure'
|
||||
|
||||
@@ -18,6 +18,27 @@ on GitHub.
|
||||
| [Preview](preview.md) | Experimental features ready for early feedback. |
|
||||
| [Stable](latest.md) | Stable, recommended for general use. |
|
||||
|
||||
## Announcements: v0.31.0 - 2026-02-27
|
||||
|
||||
- **Gemini 3.1 Pro Preview:** Gemini CLI now supports the new Gemini 3.1 Pro
|
||||
Preview model
|
||||
([#19676](https://github.com/google-gemini/gemini-cli/pull/19676) by
|
||||
@sehoon38).
|
||||
- **Experimental Browser Agent:** We've introduced a new experimental browser
|
||||
agent to interact with web pages
|
||||
([#19284](https://github.com/google-gemini/gemini-cli/pull/19284) by
|
||||
@gsquared94).
|
||||
- **Policy Engine Updates:** The policy engine now supports project-level
|
||||
policies, MCP server wildcards, and tool annotation matching
|
||||
([#18682](https://github.com/google-gemini/gemini-cli/pull/18682) by
|
||||
@Abhijit-2592,
|
||||
[#20024](https://github.com/google-gemini/gemini-cli/pull/20024) by @jerop).
|
||||
- **Web Fetch Improvements:** We've implemented an experimental direct web fetch
|
||||
feature and added rate limiting to mitigate DDoS risks
|
||||
([#19557](https://github.com/google-gemini/gemini-cli/pull/19557) by @mbleigh,
|
||||
[#19567](https://github.com/google-gemini/gemini-cli/pull/19567) by
|
||||
@mattKorwel).
|
||||
|
||||
## Announcements: v0.30.0 - 2026-02-25
|
||||
|
||||
- **SDK & Custom Skills:** Introduced the initial SDK package, enabling dynamic
|
||||
|
||||
+390
-308
@@ -1,6 +1,6 @@
|
||||
# Latest stable release: v0.30.0
|
||||
# Latest stable release: v0.31.0
|
||||
|
||||
Released: February 25, 2026
|
||||
Released: February 27, 2026
|
||||
|
||||
For most users, our latest stable release is the recommended release. Install
|
||||
the latest stable version with:
|
||||
@@ -11,323 +11,405 @@ npm install -g @google/gemini-cli
|
||||
|
||||
## Highlights
|
||||
|
||||
- **SDK & Custom Skills**: Introduced the initial SDK package, dynamic system
|
||||
instructions, `SessionContext` for SDK tool calls, and support for custom
|
||||
skills.
|
||||
- **Policy Engine Enhancements**: Added a `--policy` flag for user-defined
|
||||
policies, strict seatbelt profiles, and transitioned away from
|
||||
`--allowed-tools`.
|
||||
- **UI & Themes**: Introduced a generic searchable list for settings and
|
||||
extensions, added Solarized Dark and Light themes, text wrapping capabilities
|
||||
to markdown tables, and a clean UI toggle prototype.
|
||||
- **Vim Support & Ctrl-Z**: Improved Vim support to provide a more complete
|
||||
experience and added support for Ctrl-Z suspension.
|
||||
- **Plan Mode & Tools**: Plan Mode now supports project exploration without
|
||||
planning and skills can be enabled in plan mode. Tool output masking is
|
||||
enabled by default, and core tool definitions have been centralized.
|
||||
- **Gemini 3.1 Pro Preview:** Gemini CLI now supports the new Gemini 3.1 Pro
|
||||
Preview model.
|
||||
- **Experimental Browser Agent:** We've introduced a new experimental browser
|
||||
agent to directly interact with web pages and retrieve context.
|
||||
- **Policy Engine Updates:** The policy engine has been expanded to support
|
||||
project-level policies, MCP server wildcards, and tool annotation matching,
|
||||
providing greater control over tool executions.
|
||||
- **Web Fetch Enhancements:** A new experimental direct web fetch tool has been
|
||||
implemented, alongside rate-limiting features for enhanced security.
|
||||
- **Improved Plan Mode:** Plan Mode now includes support for custom storage
|
||||
directories, automatic model switching, and summarizing work after execution.
|
||||
|
||||
## What's Changed
|
||||
|
||||
- feat(ux): added text wrapping capabilities to markdown tables by @devr0306 in
|
||||
[#18240](https://github.com/google-gemini/gemini-cli/pull/18240)
|
||||
- Revert "fix(mcp): ensure MCP transport is closed to prevent memory leaks" by
|
||||
@skeshive in [#18771](https://github.com/google-gemini/gemini-cli/pull/18771)
|
||||
- chore(release): bump version to 0.30.0-nightly.20260210.a2174751d by
|
||||
@gemini-cli-robot in
|
||||
[#18772](https://github.com/google-gemini/gemini-cli/pull/18772)
|
||||
- chore: cleanup unused and add unlisted dependencies in packages/core by
|
||||
@adamfweidman in
|
||||
[#18762](https://github.com/google-gemini/gemini-cli/pull/18762)
|
||||
- chore(core): update activate_skill prompt verbiage to be more direct by
|
||||
@NTaylorMullen in
|
||||
[#18605](https://github.com/google-gemini/gemini-cli/pull/18605)
|
||||
- Add autoconfigure memory usage setting to the dialog by @jacob314 in
|
||||
[#18510](https://github.com/google-gemini/gemini-cli/pull/18510)
|
||||
- fix(core): prevent race condition in policy persistence by @braddux in
|
||||
[#18506](https://github.com/google-gemini/gemini-cli/pull/18506)
|
||||
- fix(evals): prevent false positive in hierarchical memory test by
|
||||
@Abhijit-2592 in
|
||||
[#18777](https://github.com/google-gemini/gemini-cli/pull/18777)
|
||||
- test(evals): mark all `save_memory` evals as `USUALLY_PASSES` due to
|
||||
unreliability by @jerop in
|
||||
[#18786](https://github.com/google-gemini/gemini-cli/pull/18786)
|
||||
- feat(cli): add setting to hide shortcuts hint UI by @LyalinDotCom in
|
||||
[#18562](https://github.com/google-gemini/gemini-cli/pull/18562)
|
||||
- feat(core): formalize 5-phase sequential planning workflow by @jerop in
|
||||
[#18759](https://github.com/google-gemini/gemini-cli/pull/18759)
|
||||
- Introduce limits for search results. by @gundermanc in
|
||||
[#18767](https://github.com/google-gemini/gemini-cli/pull/18767)
|
||||
- fix(cli): allow closing debug console after auto-open via flicker by
|
||||
- Use ranged reads and limited searches and fuzzy editing improvements by
|
||||
@gundermanc in
|
||||
[#19240](https://github.com/google-gemini/gemini-cli/pull/19240)
|
||||
- Fix bottom border color by @jacob314 in
|
||||
[#19266](https://github.com/google-gemini/gemini-cli/pull/19266)
|
||||
- Release note generator fix by @g-samroberts in
|
||||
[#19363](https://github.com/google-gemini/gemini-cli/pull/19363)
|
||||
- test(evals): add behavioral tests for tool output masking by @NTaylorMullen in
|
||||
[#19172](https://github.com/google-gemini/gemini-cli/pull/19172)
|
||||
- docs: clarify preflight instructions in GEMINI.md by @NTaylorMullen in
|
||||
[#19377](https://github.com/google-gemini/gemini-cli/pull/19377)
|
||||
- feat(cli): add gemini --resume hint on exit by @Mag1ck in
|
||||
[#16285](https://github.com/google-gemini/gemini-cli/pull/16285)
|
||||
- fix: optimize height calculations for ask_user dialog by @jackwotherspoon in
|
||||
[#19017](https://github.com/google-gemini/gemini-cli/pull/19017)
|
||||
- feat(cli): add Alt+D for forward word deletion by @scidomino in
|
||||
[#19300](https://github.com/google-gemini/gemini-cli/pull/19300)
|
||||
- Disable failing eval test by @chrstnb in
|
||||
[#19455](https://github.com/google-gemini/gemini-cli/pull/19455)
|
||||
- fix(cli): support legacy onConfirm callback in ToolActionsContext by
|
||||
@SandyTao520 in
|
||||
[#18795](https://github.com/google-gemini/gemini-cli/pull/18795)
|
||||
- feat(masking): enable tool output masking by default by @abhipatel12 in
|
||||
[#18564](https://github.com/google-gemini/gemini-cli/pull/18564)
|
||||
- perf(ui): optimize table rendering by memoizing styled characters by @devr0306
|
||||
in [#18770](https://github.com/google-gemini/gemini-cli/pull/18770)
|
||||
- feat: multi-line text answers in ask-user tool by @jackwotherspoon in
|
||||
[#18741](https://github.com/google-gemini/gemini-cli/pull/18741)
|
||||
- perf(cli): truncate large debug logs and limit message history by @mattKorwel
|
||||
in [#18663](https://github.com/google-gemini/gemini-cli/pull/18663)
|
||||
- fix(core): complete MCP discovery when configured servers are skipped by
|
||||
[#19369](https://github.com/google-gemini/gemini-cli/pull/19369)
|
||||
- chore(deps): bump tar from 7.5.7 to 7.5.8 by @.github/dependabot.yml[bot] in
|
||||
[#19367](https://github.com/google-gemini/gemini-cli/pull/19367)
|
||||
- fix(plan): allow safe fallback when experiment setting for plan is not enabled
|
||||
but approval mode at startup is plan by @Adib234 in
|
||||
[#19439](https://github.com/google-gemini/gemini-cli/pull/19439)
|
||||
- Add explicit color-convert dependency by @chrstnb in
|
||||
[#19460](https://github.com/google-gemini/gemini-cli/pull/19460)
|
||||
- feat(devtools): migrate devtools package into monorepo by @SandyTao520 in
|
||||
[#18936](https://github.com/google-gemini/gemini-cli/pull/18936)
|
||||
- fix(core): clarify plan mode constraints and exit mechanism by @jerop in
|
||||
[#19438](https://github.com/google-gemini/gemini-cli/pull/19438)
|
||||
- feat(cli): add macOS run-event notifications (interactive only) by
|
||||
@LyalinDotCom in
|
||||
[#18586](https://github.com/google-gemini/gemini-cli/pull/18586)
|
||||
- fix(core): cache CLI version to ensure consistency during sessions by
|
||||
@sehoon38 in [#18793](https://github.com/google-gemini/gemini-cli/pull/18793)
|
||||
- fix(cli): resolve double rendering in shpool and address vscode lint warnings
|
||||
by @braddux in
|
||||
[#18704](https://github.com/google-gemini/gemini-cli/pull/18704)
|
||||
- feat(plan): document and validate Plan Mode policy overrides by @jerop in
|
||||
[#18825](https://github.com/google-gemini/gemini-cli/pull/18825)
|
||||
- Fix pressing any key to exit select mode. by @jacob314 in
|
||||
[#18421](https://github.com/google-gemini/gemini-cli/pull/18421)
|
||||
- fix(cli): update F12 behavior to only open drawer if browser fails by
|
||||
[#19056](https://github.com/google-gemini/gemini-cli/pull/19056)
|
||||
- Changelog for v0.29.0 by @gemini-cli-robot in
|
||||
[#19361](https://github.com/google-gemini/gemini-cli/pull/19361)
|
||||
- fix(ui): preventing empty history items from being added by @devr0306 in
|
||||
[#19014](https://github.com/google-gemini/gemini-cli/pull/19014)
|
||||
- Changelog for v0.30.0-preview.0 by @gemini-cli-robot in
|
||||
[#19364](https://github.com/google-gemini/gemini-cli/pull/19364)
|
||||
- feat(core): add support for MCP progress updates by @NTaylorMullen in
|
||||
[#19046](https://github.com/google-gemini/gemini-cli/pull/19046)
|
||||
- fix(core): ensure directory exists before writing conversation file by
|
||||
@godwiniheuwa in
|
||||
[#18429](https://github.com/google-gemini/gemini-cli/pull/18429)
|
||||
- fix(ui): move margin from top to bottom in ToolGroupMessage by @imadraude in
|
||||
[#17198](https://github.com/google-gemini/gemini-cli/pull/17198)
|
||||
- fix(cli): treat unknown slash commands as regular input instead of showing
|
||||
error by @skyvanguard in
|
||||
[#17393](https://github.com/google-gemini/gemini-cli/pull/17393)
|
||||
- feat(core): experimental in-progress steering hints (2 of 2) by @joshualitt in
|
||||
[#19307](https://github.com/google-gemini/gemini-cli/pull/19307)
|
||||
- docs(plan): add documentation for plan mode command by @Adib234 in
|
||||
[#19467](https://github.com/google-gemini/gemini-cli/pull/19467)
|
||||
- fix(core): ripgrep fails when pattern looks like ripgrep flag by @syvb in
|
||||
[#18858](https://github.com/google-gemini/gemini-cli/pull/18858)
|
||||
- fix(cli): disable auto-completion on Shift+Tab to preserve mode cycling by
|
||||
@NTaylorMullen in
|
||||
[#19451](https://github.com/google-gemini/gemini-cli/pull/19451)
|
||||
- use issuer instead of authorization_endpoint for oauth discovery by
|
||||
@garrettsparks in
|
||||
[#17332](https://github.com/google-gemini/gemini-cli/pull/17332)
|
||||
- feat(cli): include `/dir add` directories in @ autocomplete suggestions by
|
||||
@jasmeetsb in [#19246](https://github.com/google-gemini/gemini-cli/pull/19246)
|
||||
- feat(admin): Admin settings should only apply if adminControlsApplicable =
|
||||
true and fetch errors should be fatal by @skeshive in
|
||||
[#19453](https://github.com/google-gemini/gemini-cli/pull/19453)
|
||||
- Format strict-development-rules command by @g-samroberts in
|
||||
[#19484](https://github.com/google-gemini/gemini-cli/pull/19484)
|
||||
- feat(core): centralize compatibility checks and add TrueColor detection by
|
||||
@spencer426 in
|
||||
[#19478](https://github.com/google-gemini/gemini-cli/pull/19478)
|
||||
- Remove unused files and update index and sidebar. by @g-samroberts in
|
||||
[#19479](https://github.com/google-gemini/gemini-cli/pull/19479)
|
||||
- Migrate core render util to use xterm.js as part of the rendering loop. by
|
||||
@jacob314 in [#19044](https://github.com/google-gemini/gemini-cli/pull/19044)
|
||||
- Changelog for v0.30.0-preview.1 by @gemini-cli-robot in
|
||||
[#19496](https://github.com/google-gemini/gemini-cli/pull/19496)
|
||||
- build: replace deprecated built-in punycode with userland package by @jacob314
|
||||
in [#19502](https://github.com/google-gemini/gemini-cli/pull/19502)
|
||||
- Speculative fixes to try to fix react error. by @jacob314 in
|
||||
[#19508](https://github.com/google-gemini/gemini-cli/pull/19508)
|
||||
- fix spacing by @jacob314 in
|
||||
[#19494](https://github.com/google-gemini/gemini-cli/pull/19494)
|
||||
- fix(core): ensure user rejections update tool outcome for telemetry by
|
||||
@abhiasap in [#18982](https://github.com/google-gemini/gemini-cli/pull/18982)
|
||||
- fix(acp): Initialize config (#18897) by @Mervap in
|
||||
[#18898](https://github.com/google-gemini/gemini-cli/pull/18898)
|
||||
- fix(core): add error logging for IDE fetch failures by @yuvrajangadsingh in
|
||||
[#17981](https://github.com/google-gemini/gemini-cli/pull/17981)
|
||||
- feat(acp): support set_mode interface (#18890) by @Mervap in
|
||||
[#18891](https://github.com/google-gemini/gemini-cli/pull/18891)
|
||||
- fix(core): robust workspace-based IDE connection discovery by @ehedlund in
|
||||
[#18443](https://github.com/google-gemini/gemini-cli/pull/18443)
|
||||
- Deflake windows tests. by @jacob314 in
|
||||
[#19511](https://github.com/google-gemini/gemini-cli/pull/19511)
|
||||
- Fix: Avoid tool confirmation timeout when no UI listeners are present by
|
||||
@pdHaku0 in [#17955](https://github.com/google-gemini/gemini-cli/pull/17955)
|
||||
- format md file by @scidomino in
|
||||
[#19474](https://github.com/google-gemini/gemini-cli/pull/19474)
|
||||
- feat(cli): add experimental.useOSC52Copy setting by @scidomino in
|
||||
[#19488](https://github.com/google-gemini/gemini-cli/pull/19488)
|
||||
- feat(cli): replace loading phrases boolean with enum setting by @LyalinDotCom
|
||||
in [#19347](https://github.com/google-gemini/gemini-cli/pull/19347)
|
||||
- Update skill to adjust for generated results. by @g-samroberts in
|
||||
[#19500](https://github.com/google-gemini/gemini-cli/pull/19500)
|
||||
- Fix message too large issue. by @gundermanc in
|
||||
[#19499](https://github.com/google-gemini/gemini-cli/pull/19499)
|
||||
- fix(core): prevent duplicate tool approval entries in auto-saved.toml by
|
||||
@Abhijit-2592 in
|
||||
[#19487](https://github.com/google-gemini/gemini-cli/pull/19487)
|
||||
- fix(core): resolve crash in ClearcutLogger when os.cpus() is empty by @Adib234
|
||||
in [#19555](https://github.com/google-gemini/gemini-cli/pull/19555)
|
||||
- chore(core): improve encapsulation and remove unused exports by @adamfweidman
|
||||
in [#19556](https://github.com/google-gemini/gemini-cli/pull/19556)
|
||||
- Revert "Add generic searchable list to back settings and extensions (… by
|
||||
@chrstnb in [#19434](https://github.com/google-gemini/gemini-cli/pull/19434)
|
||||
- fix(core): improve error type extraction for telemetry by @yunaseoul in
|
||||
[#19565](https://github.com/google-gemini/gemini-cli/pull/19565)
|
||||
- fix: remove extra padding in Composer by @jackwotherspoon in
|
||||
[#19529](https://github.com/google-gemini/gemini-cli/pull/19529)
|
||||
- feat(plan): support configuring custom plans storage directory by @jerop in
|
||||
[#19577](https://github.com/google-gemini/gemini-cli/pull/19577)
|
||||
- Migrate files to resource or references folder. by @g-samroberts in
|
||||
[#19503](https://github.com/google-gemini/gemini-cli/pull/19503)
|
||||
- feat(policy): implement project-level policy support by @Abhijit-2592 in
|
||||
[#18682](https://github.com/google-gemini/gemini-cli/pull/18682)
|
||||
- feat(core): Implement parallel FC for read only tools. by @joshualitt in
|
||||
[#18791](https://github.com/google-gemini/gemini-cli/pull/18791)
|
||||
- chore(skills): adds pr-address-comments skill to work on PR feedback by
|
||||
@mbleigh in [#19576](https://github.com/google-gemini/gemini-cli/pull/19576)
|
||||
- refactor(sdk): introduce session-based architecture by @mbleigh in
|
||||
[#19180](https://github.com/google-gemini/gemini-cli/pull/19180)
|
||||
- fix(ci): add fallback JSON extraction to issue triage workflow by @bdmorgan in
|
||||
[#19593](https://github.com/google-gemini/gemini-cli/pull/19593)
|
||||
- feat(core): refine Edit and WriteFile tool schemas for Gemini 3 by
|
||||
@SandyTao520 in
|
||||
[#18829](https://github.com/google-gemini/gemini-cli/pull/18829)
|
||||
- feat(plan): allow skills to be enabled in plan mode by @Adib234 in
|
||||
[#18817](https://github.com/google-gemini/gemini-cli/pull/18817)
|
||||
- docs(plan): add documentation for plan mode tools by @jerop in
|
||||
[#18827](https://github.com/google-gemini/gemini-cli/pull/18827)
|
||||
- Remove experimental note in extension settings docs by @chrstnb in
|
||||
[#18822](https://github.com/google-gemini/gemini-cli/pull/18822)
|
||||
- Update prompt and grep tool definition to limit context size by @gundermanc in
|
||||
[#18780](https://github.com/google-gemini/gemini-cli/pull/18780)
|
||||
- docs(plan): add `ask_user` tool documentation by @jerop in
|
||||
[#18830](https://github.com/google-gemini/gemini-cli/pull/18830)
|
||||
- Revert unintended credentials exposure by @Adib234 in
|
||||
[#18840](https://github.com/google-gemini/gemini-cli/pull/18840)
|
||||
- feat(core): update internal utility models to Gemini 3 by @SandyTao520 in
|
||||
[#18773](https://github.com/google-gemini/gemini-cli/pull/18773)
|
||||
- feat(a2a): add value-resolver for auth credential resolution by @adamfweidman
|
||||
in [#18653](https://github.com/google-gemini/gemini-cli/pull/18653)
|
||||
- Removed getPlainTextLength by @devr0306 in
|
||||
[#18848](https://github.com/google-gemini/gemini-cli/pull/18848)
|
||||
- More grep prompt tweaks by @gundermanc in
|
||||
[#18846](https://github.com/google-gemini/gemini-cli/pull/18846)
|
||||
- refactor(cli): Reactive useSettingsStore hook by @psinha40898 in
|
||||
[#14915](https://github.com/google-gemini/gemini-cli/pull/14915)
|
||||
- fix(mcp): Ensure that stdio MCP server execution has the `GEMINI_CLI=1` env
|
||||
variable populated. by @richieforeman in
|
||||
[#18832](https://github.com/google-gemini/gemini-cli/pull/18832)
|
||||
- fix(core): improve headless mode detection for flags and query args by @galz10
|
||||
in [#18855](https://github.com/google-gemini/gemini-cli/pull/18855)
|
||||
- refactor(cli): simplify UI and remove legacy inline tool confirmation logic by
|
||||
@abhipatel12 in
|
||||
[#18566](https://github.com/google-gemini/gemini-cli/pull/18566)
|
||||
- feat(cli): deprecate --allowed-tools and excludeTools in favor of policy
|
||||
engine by @Abhijit-2592 in
|
||||
[#18508](https://github.com/google-gemini/gemini-cli/pull/18508)
|
||||
- fix(workflows): improve maintainer detection for automated PR actions by
|
||||
@bdmorgan in [#18869](https://github.com/google-gemini/gemini-cli/pull/18869)
|
||||
- refactor(cli): consolidate useToolScheduler and delete legacy implementation
|
||||
by @abhipatel12 in
|
||||
[#18567](https://github.com/google-gemini/gemini-cli/pull/18567)
|
||||
- Update changelog for v0.28.0 and v0.29.0-preview0 by @g-samroberts in
|
||||
[#18819](https://github.com/google-gemini/gemini-cli/pull/18819)
|
||||
- fix(core): ensure sub-agents are registered regardless of tools.allowed by
|
||||
[#19476](https://github.com/google-gemini/gemini-cli/pull/19476)
|
||||
- Changelog for v0.30.0-preview.3 by @gemini-cli-robot in
|
||||
[#19585](https://github.com/google-gemini/gemini-cli/pull/19585)
|
||||
- fix(plan): exclude EnterPlanMode tool from YOLO mode by @Adib234 in
|
||||
[#19570](https://github.com/google-gemini/gemini-cli/pull/19570)
|
||||
- chore: resolve build warnings and update dependencies by @mattKorwel in
|
||||
[#18880](https://github.com/google-gemini/gemini-cli/pull/18880)
|
||||
- feat(ui): add source indicators to slash commands by @ehedlund in
|
||||
[#18839](https://github.com/google-gemini/gemini-cli/pull/18839)
|
||||
- docs: refine Plan Mode documentation structure and workflow by @jerop in
|
||||
[#19644](https://github.com/google-gemini/gemini-cli/pull/19644)
|
||||
- Docs: Update release information regarding Gemini 3.1 by @jkcinouye in
|
||||
[#19568](https://github.com/google-gemini/gemini-cli/pull/19568)
|
||||
- fix(security): rate limit web_fetch tool to mitigate DDoS via prompt injection
|
||||
by @mattKorwel in
|
||||
[#19567](https://github.com/google-gemini/gemini-cli/pull/19567)
|
||||
- Add initial implementation of /extensions explore command by @chrstnb in
|
||||
[#19029](https://github.com/google-gemini/gemini-cli/pull/19029)
|
||||
- fix: use discoverOAuthFromWWWAuthenticate for reactive OAuth flow (#18760) by
|
||||
@maximus12793 in
|
||||
[#19038](https://github.com/google-gemini/gemini-cli/pull/19038)
|
||||
- Search updates by @alisa-alisa in
|
||||
[#19482](https://github.com/google-gemini/gemini-cli/pull/19482)
|
||||
- feat(cli): add support for numpad SS3 sequences by @scidomino in
|
||||
[#19659](https://github.com/google-gemini/gemini-cli/pull/19659)
|
||||
- feat(cli): enhance folder trust with configuration discovery and security
|
||||
warnings by @galz10 in
|
||||
[#19492](https://github.com/google-gemini/gemini-cli/pull/19492)
|
||||
- feat(ui): improve startup warnings UX with dismissal and show-count limits by
|
||||
@spencer426 in
|
||||
[#19584](https://github.com/google-gemini/gemini-cli/pull/19584)
|
||||
- feat(a2a): Add API key authentication provider by @adamfweidman in
|
||||
[#19548](https://github.com/google-gemini/gemini-cli/pull/19548)
|
||||
- Send accepted/removed lines with ACCEPT_FILE telemetry. by @gundermanc in
|
||||
[#19670](https://github.com/google-gemini/gemini-cli/pull/19670)
|
||||
- feat(models): support Gemini 3.1 Pro Preview and fixes by @sehoon38 in
|
||||
[#19676](https://github.com/google-gemini/gemini-cli/pull/19676)
|
||||
- feat(plan): enforce read-only constraints in Plan Mode by @mattKorwel in
|
||||
[#19433](https://github.com/google-gemini/gemini-cli/pull/19433)
|
||||
- fix(cli): allow perfect match @scripts/test-windows-paths.js completions to
|
||||
submit on Enter by @spencer426 in
|
||||
[#19562](https://github.com/google-gemini/gemini-cli/pull/19562)
|
||||
- fix(core): treat 503 Service Unavailable as retryable quota error by @sehoon38
|
||||
in [#19642](https://github.com/google-gemini/gemini-cli/pull/19642)
|
||||
- Update sidebar.json for to allow top nav tabs. by @g-samroberts in
|
||||
[#19595](https://github.com/google-gemini/gemini-cli/pull/19595)
|
||||
- security: strip deceptive Unicode characters from terminal output by @ehedlund
|
||||
in [#19026](https://github.com/google-gemini/gemini-cli/pull/19026)
|
||||
- Fixes 'input.on' is not a function error in Gemini CLI by @gundermanc in
|
||||
[#19691](https://github.com/google-gemini/gemini-cli/pull/19691)
|
||||
- Revert "feat(ui): add source indicators to slash commands" by @ehedlund in
|
||||
[#19695](https://github.com/google-gemini/gemini-cli/pull/19695)
|
||||
- security: implement deceptive URL detection and disclosure in tool
|
||||
confirmations by @ehedlund in
|
||||
[#19288](https://github.com/google-gemini/gemini-cli/pull/19288)
|
||||
- fix(core): restore auth consent in headless mode and add unit tests by
|
||||
@ehedlund in [#19689](https://github.com/google-gemini/gemini-cli/pull/19689)
|
||||
- Fix unsafe assertions in code_assist folder. by @gundermanc in
|
||||
[#19706](https://github.com/google-gemini/gemini-cli/pull/19706)
|
||||
- feat(cli): make JetBrains warning more specific by @jacob314 in
|
||||
[#19687](https://github.com/google-gemini/gemini-cli/pull/19687)
|
||||
- fix(cli): extensions dialog UX polish by @jacob314 in
|
||||
[#19685](https://github.com/google-gemini/gemini-cli/pull/19685)
|
||||
- fix(cli): use getDisplayString for manual model selection in dialog by
|
||||
@sehoon38 in [#19726](https://github.com/google-gemini/gemini-cli/pull/19726)
|
||||
- feat(policy): repurpose "Always Allow" persistence to workspace level by
|
||||
@Abhijit-2592 in
|
||||
[#19707](https://github.com/google-gemini/gemini-cli/pull/19707)
|
||||
- fix(cli): re-enable CLI banner by @sehoon38 in
|
||||
[#19741](https://github.com/google-gemini/gemini-cli/pull/19741)
|
||||
- Disallow and suppress unsafe assignment by @gundermanc in
|
||||
[#19736](https://github.com/google-gemini/gemini-cli/pull/19736)
|
||||
- feat(core): migrate read_file to 1-based start_line/end_line parameters by
|
||||
@adamfweidman in
|
||||
[#19526](https://github.com/google-gemini/gemini-cli/pull/19526)
|
||||
- feat(cli): improve CTRL+O experience for both standard and alternate screen
|
||||
buffer (ASB) modes by @jwhelangoog in
|
||||
[#19010](https://github.com/google-gemini/gemini-cli/pull/19010)
|
||||
- Utilize pipelining of grep_search -> read_file to eliminate turns by
|
||||
@gundermanc in
|
||||
[#19574](https://github.com/google-gemini/gemini-cli/pull/19574)
|
||||
- refactor(core): remove unsafe type assertions in error utils (Phase 1.1) by
|
||||
@mattKorwel in
|
||||
[#18870](https://github.com/google-gemini/gemini-cli/pull/18870)
|
||||
- Show notification when there's a conflict with an extensions command by
|
||||
@chrstnb in [#17890](https://github.com/google-gemini/gemini-cli/pull/17890)
|
||||
- fix(cli): dismiss '?' shortcuts help on hotkeys and active states by
|
||||
@LyalinDotCom in
|
||||
[#18583](https://github.com/google-gemini/gemini-cli/pull/18583)
|
||||
- fix(core): prioritize conditional policy rules and harden Plan Mode by
|
||||
@Abhijit-2592 in
|
||||
[#18882](https://github.com/google-gemini/gemini-cli/pull/18882)
|
||||
- feat(core): refine Plan Mode system prompt for agentic execution by
|
||||
[#19750](https://github.com/google-gemini/gemini-cli/pull/19750)
|
||||
- Disallow unsafe returns. by @gundermanc in
|
||||
[#19767](https://github.com/google-gemini/gemini-cli/pull/19767)
|
||||
- fix(cli): filter subagent sessions from resume history by @abhipatel12 in
|
||||
[#19698](https://github.com/google-gemini/gemini-cli/pull/19698)
|
||||
- chore(lint): fix lint errors seen when running npm run lint by @abhipatel12 in
|
||||
[#19844](https://github.com/google-gemini/gemini-cli/pull/19844)
|
||||
- feat(core): remove unnecessary login verbiage from Code Assist auth by
|
||||
@NTaylorMullen in
|
||||
[#18799](https://github.com/google-gemini/gemini-cli/pull/18799)
|
||||
- feat(plan): create metrics for usage of `AskUser` tool by @Adib234 in
|
||||
[#18820](https://github.com/google-gemini/gemini-cli/pull/18820)
|
||||
- feat(cli): support Ctrl-Z suspension by @scidomino in
|
||||
[#18931](https://github.com/google-gemini/gemini-cli/pull/18931)
|
||||
- fix(github-actions): use robot PAT for release creation to trigger release
|
||||
notes by @SandyTao520 in
|
||||
[#18794](https://github.com/google-gemini/gemini-cli/pull/18794)
|
||||
- feat: add strict seatbelt profiles and remove unusable closed profiles by
|
||||
[#19861](https://github.com/google-gemini/gemini-cli/pull/19861)
|
||||
- fix(plan): time share by approval mode dashboard reporting negative time
|
||||
shares by @Adib234 in
|
||||
[#19847](https://github.com/google-gemini/gemini-cli/pull/19847)
|
||||
- fix(core): allow any preview model in quota access check by @bdmorgan in
|
||||
[#19867](https://github.com/google-gemini/gemini-cli/pull/19867)
|
||||
- fix(core): prevent omission placeholder deletions in replace/write_file by
|
||||
@nsalerni in [#19870](https://github.com/google-gemini/gemini-cli/pull/19870)
|
||||
- fix(core): add uniqueness guard to edit tool by @Shivangisharma4 in
|
||||
[#19890](https://github.com/google-gemini/gemini-cli/pull/19890)
|
||||
- refactor(config): remove enablePromptCompletion from settings by @sehoon38 in
|
||||
[#19974](https://github.com/google-gemini/gemini-cli/pull/19974)
|
||||
- refactor(core): move session conversion logic to core by @abhipatel12 in
|
||||
[#19972](https://github.com/google-gemini/gemini-cli/pull/19972)
|
||||
- Fix: Persist manual model selection on restart #19864 by @Nixxx19 in
|
||||
[#19891](https://github.com/google-gemini/gemini-cli/pull/19891)
|
||||
- fix(core): increase default retry attempts and add quota error backoff by
|
||||
@sehoon38 in [#19949](https://github.com/google-gemini/gemini-cli/pull/19949)
|
||||
- feat(core): add policy chain support for Gemini 3.1 by @sehoon38 in
|
||||
[#19991](https://github.com/google-gemini/gemini-cli/pull/19991)
|
||||
- Updates command reference and /stats command. by @g-samroberts in
|
||||
[#19794](https://github.com/google-gemini/gemini-cli/pull/19794)
|
||||
- Fix for silent failures in non-interactive mode by @owenofbrien in
|
||||
[#19905](https://github.com/google-gemini/gemini-cli/pull/19905)
|
||||
- fix(plan): allow plan mode writes on Windows and fix prompt paths by @Adib234
|
||||
in [#19658](https://github.com/google-gemini/gemini-cli/pull/19658)
|
||||
- fix(core): prevent OAuth server crash on unexpected requests by @reyyanxahmed
|
||||
in [#19668](https://github.com/google-gemini/gemini-cli/pull/19668)
|
||||
- feat: Map tool kinds to explicit ACP.ToolKind values and update test … by
|
||||
@sripasg in [#19547](https://github.com/google-gemini/gemini-cli/pull/19547)
|
||||
- chore: restrict gemini-automted-issue-triage to only allow echo by @galz10 in
|
||||
[#20047](https://github.com/google-gemini/gemini-cli/pull/20047)
|
||||
- Allow ask headers longer than 16 chars by @scidomino in
|
||||
[#20041](https://github.com/google-gemini/gemini-cli/pull/20041)
|
||||
- fix(core): prevent state corruption in McpClientManager during collis by @h30s
|
||||
in [#19782](https://github.com/google-gemini/gemini-cli/pull/19782)
|
||||
- fix(bundling): copy devtools package to bundle for runtime resolution by
|
||||
@SandyTao520 in
|
||||
[#18876](https://github.com/google-gemini/gemini-cli/pull/18876)
|
||||
- chore: cleanup unused and add unlisted dependencies in packages/a2a-server by
|
||||
@adamfweidman in
|
||||
[#18916](https://github.com/google-gemini/gemini-cli/pull/18916)
|
||||
- fix(plan): isolate plan files per session by @Adib234 in
|
||||
[#18757](https://github.com/google-gemini/gemini-cli/pull/18757)
|
||||
- fix: character truncation in raw markdown mode by @jackwotherspoon in
|
||||
[#18938](https://github.com/google-gemini/gemini-cli/pull/18938)
|
||||
- feat(cli): prototype clean UI toggle and minimal-mode bleed-through by
|
||||
@LyalinDotCom in
|
||||
[#18683](https://github.com/google-gemini/gemini-cli/pull/18683)
|
||||
- ui(polish) blend background color with theme by @jacob314 in
|
||||
[#18802](https://github.com/google-gemini/gemini-cli/pull/18802)
|
||||
- Add generic searchable list to back settings and extensions by @chrstnb in
|
||||
[#18838](https://github.com/google-gemini/gemini-cli/pull/18838)
|
||||
- feat(ui): align `AskUser` color scheme with UX spec by @jerop in
|
||||
[#18943](https://github.com/google-gemini/gemini-cli/pull/18943)
|
||||
- Hide AskUser tool validation errors from UI (agent self-corrects) by @jerop in
|
||||
[#18954](https://github.com/google-gemini/gemini-cli/pull/18954)
|
||||
- bug(cli) fix flicker due to AppContainer continuous initialization by
|
||||
@jacob314 in [#18958](https://github.com/google-gemini/gemini-cli/pull/18958)
|
||||
- feat(admin): Add admin controls documentation by @skeshive in
|
||||
[#18644](https://github.com/google-gemini/gemini-cli/pull/18644)
|
||||
- feat(cli): disable ctrl-s shortcut outside of alternate buffer mode by
|
||||
@jacob314 in [#18887](https://github.com/google-gemini/gemini-cli/pull/18887)
|
||||
- fix(vim): vim support that feels (more) complete by @ppgranger in
|
||||
[#18755](https://github.com/google-gemini/gemini-cli/pull/18755)
|
||||
- feat(policy): add --policy flag for user defined policies by @allenhutchison
|
||||
in [#18500](https://github.com/google-gemini/gemini-cli/pull/18500)
|
||||
- Update installation guide by @g-samroberts in
|
||||
[#18823](https://github.com/google-gemini/gemini-cli/pull/18823)
|
||||
- refactor(core): centralize tool definitions (Group 1: replace, search, grep)
|
||||
by @aishaneeshah in
|
||||
[#18944](https://github.com/google-gemini/gemini-cli/pull/18944)
|
||||
- refactor(cli): finalize event-driven transition and remove interaction bridge
|
||||
by @abhipatel12 in
|
||||
[#18569](https://github.com/google-gemini/gemini-cli/pull/18569)
|
||||
- Fix drag and drop escaping by @scidomino in
|
||||
[#18965](https://github.com/google-gemini/gemini-cli/pull/18965)
|
||||
- feat(sdk): initial package bootstrap for SDK by @mbleigh in
|
||||
[#18861](https://github.com/google-gemini/gemini-cli/pull/18861)
|
||||
- feat(sdk): implements SessionContext for SDK tool calls by @mbleigh in
|
||||
[#18862](https://github.com/google-gemini/gemini-cli/pull/18862)
|
||||
- fix(plan): make question type required in AskUser tool by @Adib234 in
|
||||
[#18959](https://github.com/google-gemini/gemini-cli/pull/18959)
|
||||
- fix(core): ensure --yolo does not force headless mode by @NTaylorMullen in
|
||||
[#18976](https://github.com/google-gemini/gemini-cli/pull/18976)
|
||||
- refactor(core): adopt `CoreToolCallStatus` enum for type safety by @jerop in
|
||||
[#18998](https://github.com/google-gemini/gemini-cli/pull/18998)
|
||||
- Enable in-CLI extension management commands for team by @chrstnb in
|
||||
[#18957](https://github.com/google-gemini/gemini-cli/pull/18957)
|
||||
- Adjust lint rules to avoid unnecessary warning. by @scidomino in
|
||||
[#18970](https://github.com/google-gemini/gemini-cli/pull/18970)
|
||||
- fix(vscode): resolve unsafe type assertion lint errors by @ehedlund in
|
||||
[#19006](https://github.com/google-gemini/gemini-cli/pull/19006)
|
||||
- Remove unnecessary eslint config file by @scidomino in
|
||||
[#19015](https://github.com/google-gemini/gemini-cli/pull/19015)
|
||||
- fix(core): Prevent loop detection false positives on lists with long shared
|
||||
prefixes by @SandyTao520 in
|
||||
[#18975](https://github.com/google-gemini/gemini-cli/pull/18975)
|
||||
- feat(core): fallback to chat-base when using unrecognized models for chat by
|
||||
@SandyTao520 in
|
||||
[#19016](https://github.com/google-gemini/gemini-cli/pull/19016)
|
||||
- docs: fix inconsistent commandRegex example in policy engine by @NTaylorMullen
|
||||
in [#19027](https://github.com/google-gemini/gemini-cli/pull/19027)
|
||||
- fix(plan): persist the approval mode in UI even when agent is thinking by
|
||||
@Adib234 in [#18955](https://github.com/google-gemini/gemini-cli/pull/18955)
|
||||
- feat(sdk): Implement dynamic system instructions by @mbleigh in
|
||||
[#18863](https://github.com/google-gemini/gemini-cli/pull/18863)
|
||||
- Docs: Refresh docs to organize and standardize reference materials. by
|
||||
@jkcinouye in [#18403](https://github.com/google-gemini/gemini-cli/pull/18403)
|
||||
- fix windows escaping (and broken tests) by @scidomino in
|
||||
[#19011](https://github.com/google-gemini/gemini-cli/pull/19011)
|
||||
- refactor: use `CoreToolCallStatus` in the the history data model by @jerop in
|
||||
[#19033](https://github.com/google-gemini/gemini-cli/pull/19033)
|
||||
- feat(cleanup): enable 30-day session retention by default by @skeshive in
|
||||
[#18854](https://github.com/google-gemini/gemini-cli/pull/18854)
|
||||
- feat(plan): hide plan write and edit operations on plans in Plan Mode by
|
||||
@jerop in [#19012](https://github.com/google-gemini/gemini-cli/pull/19012)
|
||||
- bug(ui) fix flicker refreshing background color by @jacob314 in
|
||||
[#19041](https://github.com/google-gemini/gemini-cli/pull/19041)
|
||||
- chore: fix dep vulnerabilities by @scidomino in
|
||||
[#19036](https://github.com/google-gemini/gemini-cli/pull/19036)
|
||||
- Revamp automated changelog skill by @g-samroberts in
|
||||
[#18974](https://github.com/google-gemini/gemini-cli/pull/18974)
|
||||
- feat(sdk): implement support for custom skills by @mbleigh in
|
||||
[#19031](https://github.com/google-gemini/gemini-cli/pull/19031)
|
||||
- refactor(core): complete centralization of core tool definitions by
|
||||
[#19766](https://github.com/google-gemini/gemini-cli/pull/19766)
|
||||
- feat(policy): Support MCP Server Wildcards in Policy Engine by @jerop in
|
||||
[#20024](https://github.com/google-gemini/gemini-cli/pull/20024)
|
||||
- docs(CONTRIBUTING): update React DevTools version to 6 by @mmgok in
|
||||
[#20014](https://github.com/google-gemini/gemini-cli/pull/20014)
|
||||
- feat(core): optimize tool descriptions and schemas for Gemini 3 by
|
||||
@aishaneeshah in
|
||||
[#18991](https://github.com/google-gemini/gemini-cli/pull/18991)
|
||||
- feat: add /commands reload to refresh custom TOML commands by @korade-krushna
|
||||
in [#19078](https://github.com/google-gemini/gemini-cli/pull/19078)
|
||||
- fix(cli): wrap terminal capability queries in hidden sequence by @srithreepo
|
||||
in [#19080](https://github.com/google-gemini/gemini-cli/pull/19080)
|
||||
- fix(workflows): fix GitHub App token permissions for maintainer detection by
|
||||
@bdmorgan in [#19139](https://github.com/google-gemini/gemini-cli/pull/19139)
|
||||
- test: fix hook integration test flakiness on Windows CI by @NTaylorMullen in
|
||||
[#18665](https://github.com/google-gemini/gemini-cli/pull/18665)
|
||||
- fix(core): Encourage non-interactive flags for scaffolding commands by
|
||||
@NTaylorMullen in
|
||||
[#18804](https://github.com/google-gemini/gemini-cli/pull/18804)
|
||||
- fix(core): propagate User-Agent header to setup-phase CodeAssist API calls by
|
||||
@gsquared94 in
|
||||
[#19182](https://github.com/google-gemini/gemini-cli/pull/19182)
|
||||
- docs: document .agents/skills alias and discovery precedence by @kevmoo in
|
||||
[#19166](https://github.com/google-gemini/gemini-cli/pull/19166)
|
||||
- feat(cli): add loading state to new agents notification by @sehoon38 in
|
||||
[#19190](https://github.com/google-gemini/gemini-cli/pull/19190)
|
||||
- Add base branch to workflow. by @g-samroberts in
|
||||
[#19189](https://github.com/google-gemini/gemini-cli/pull/19189)
|
||||
- feat(cli): handle invalid model names in useQuotaAndFallback by @sehoon38 in
|
||||
[#19222](https://github.com/google-gemini/gemini-cli/pull/19222)
|
||||
- docs: custom themes in extensions by @jackwotherspoon in
|
||||
[#19219](https://github.com/google-gemini/gemini-cli/pull/19219)
|
||||
- Disable workspace settings when starting GCLI in the home directory. by
|
||||
@kevinjwang1 in
|
||||
[#19034](https://github.com/google-gemini/gemini-cli/pull/19034)
|
||||
- feat(cli): refactor model command to support set and manage subcommands by
|
||||
@sehoon38 in [#19221](https://github.com/google-gemini/gemini-cli/pull/19221)
|
||||
- Add refresh/reload aliases to slash command subcommands by @korade-krushna in
|
||||
[#19218](https://github.com/google-gemini/gemini-cli/pull/19218)
|
||||
- refactor: consolidate development rules and add cli guidelines by @jacob314 in
|
||||
[#19214](https://github.com/google-gemini/gemini-cli/pull/19214)
|
||||
- chore(ui): remove outdated tip about model routing by @sehoon38 in
|
||||
[#19226](https://github.com/google-gemini/gemini-cli/pull/19226)
|
||||
- feat(core): support custom reasoning models by default by @NTaylorMullen in
|
||||
[#19227](https://github.com/google-gemini/gemini-cli/pull/19227)
|
||||
- Add Solarized Dark and Solarized Light themes by @rmedranollamas in
|
||||
[#19064](https://github.com/google-gemini/gemini-cli/pull/19064)
|
||||
- fix(telemetry): replace JSON.stringify with safeJsonStringify in file
|
||||
exporters by @gsquared94 in
|
||||
[#19244](https://github.com/google-gemini/gemini-cli/pull/19244)
|
||||
- feat(telemetry): add keychain availability and token storage metrics by
|
||||
@abhipatel12 in
|
||||
[#18971](https://github.com/google-gemini/gemini-cli/pull/18971)
|
||||
- feat(cli): update approval mode cycle order by @jerop in
|
||||
[#19254](https://github.com/google-gemini/gemini-cli/pull/19254)
|
||||
- refactor(cli): code review cleanup fix for tab+tab by @jacob314 in
|
||||
[#18967](https://github.com/google-gemini/gemini-cli/pull/18967)
|
||||
- feat(plan): support project exploration without planning when in plan mode by
|
||||
@Adib234 in [#18992](https://github.com/google-gemini/gemini-cli/pull/18992)
|
||||
- feat: add role-specific statistics to telemetry and UI (cont. #15234) by
|
||||
@yunaseoul in [#18824](https://github.com/google-gemini/gemini-cli/pull/18824)
|
||||
- feat(cli): remove Plan Mode from rotation when actively working by @jerop in
|
||||
[#19262](https://github.com/google-gemini/gemini-cli/pull/19262)
|
||||
- Fix side breakage where anchors don't work in slugs. by @g-samroberts in
|
||||
[#19261](https://github.com/google-gemini/gemini-cli/pull/19261)
|
||||
- feat(config): add setting to make directory tree context configurable by
|
||||
@kevin-ramdass in
|
||||
[#19053](https://github.com/google-gemini/gemini-cli/pull/19053)
|
||||
- fix(acp): Wait for mcp initialization in acp (#18893) by @Mervap in
|
||||
[#18894](https://github.com/google-gemini/gemini-cli/pull/18894)
|
||||
- docs: format UTC times in releases doc by @pavan-sh in
|
||||
[#18169](https://github.com/google-gemini/gemini-cli/pull/18169)
|
||||
- Docs: Clarify extensions documentation. by @jkcinouye in
|
||||
[#19277](https://github.com/google-gemini/gemini-cli/pull/19277)
|
||||
- refactor(core): modularize tool definitions by model family by @aishaneeshah
|
||||
in [#19269](https://github.com/google-gemini/gemini-cli/pull/19269)
|
||||
- fix(paths): Add cross-platform path normalization by @spencer426 in
|
||||
[#18939](https://github.com/google-gemini/gemini-cli/pull/18939)
|
||||
- feat(core): experimental in-progress steering hints (1 of 3) by @joshualitt in
|
||||
[#19008](https://github.com/google-gemini/gemini-cli/pull/19008)
|
||||
- fix(patch): cherry-pick 261788c to release/v0.30.0-preview.0-pr-19453 to patch
|
||||
version v0.30.0-preview.0 and create version 0.30.0-preview.1 by
|
||||
[#19643](https://github.com/google-gemini/gemini-cli/pull/19643)
|
||||
- feat(core): implement experimental direct web fetch by @mbleigh in
|
||||
[#19557](https://github.com/google-gemini/gemini-cli/pull/19557)
|
||||
- feat(core): replace expected_replacements with allow_multiple in replace tool
|
||||
by @SandyTao520 in
|
||||
[#20033](https://github.com/google-gemini/gemini-cli/pull/20033)
|
||||
- fix(sandbox): harden image packaging integrity checks by @aviralgarg05 in
|
||||
[#19552](https://github.com/google-gemini/gemini-cli/pull/19552)
|
||||
- fix(core): allow environment variable expansion and explicit overrides for MCP
|
||||
servers by @galz10 in
|
||||
[#18837](https://github.com/google-gemini/gemini-cli/pull/18837)
|
||||
- feat(policy): Implement Tool Annotation Matching in Policy Engine by @jerop in
|
||||
[#20029](https://github.com/google-gemini/gemini-cli/pull/20029)
|
||||
- fix(core): prevent utility calls from changing session active model by
|
||||
@adamfweidman in
|
||||
[#20035](https://github.com/google-gemini/gemini-cli/pull/20035)
|
||||
- fix(cli): skip workspace policy loading when in home directory by
|
||||
@Abhijit-2592 in
|
||||
[#20054](https://github.com/google-gemini/gemini-cli/pull/20054)
|
||||
- fix(scripts): Add Windows (win32/x64) support to lint.js by @ZafeerMahmood in
|
||||
[#16193](https://github.com/google-gemini/gemini-cli/pull/16193)
|
||||
- fix(a2a-server): Remove unsafe type assertions in agent by @Nixxx19 in
|
||||
[#19723](https://github.com/google-gemini/gemini-cli/pull/19723)
|
||||
- Fix: Handle corrupted token file gracefully when switching auth types (#19845)
|
||||
by @Nixxx19 in
|
||||
[#19850](https://github.com/google-gemini/gemini-cli/pull/19850)
|
||||
- fix critical dep vulnerability by @scidomino in
|
||||
[#20087](https://github.com/google-gemini/gemini-cli/pull/20087)
|
||||
- Add new setting to configure maxRetries by @kevinjwang1 in
|
||||
[#20064](https://github.com/google-gemini/gemini-cli/pull/20064)
|
||||
- Stabilize tests. by @gundermanc in
|
||||
[#20095](https://github.com/google-gemini/gemini-cli/pull/20095)
|
||||
- make windows tests mandatory by @scidomino in
|
||||
[#20096](https://github.com/google-gemini/gemini-cli/pull/20096)
|
||||
- Add 3.1 pro preview to behavioral evals. by @gundermanc in
|
||||
[#20088](https://github.com/google-gemini/gemini-cli/pull/20088)
|
||||
- feat:PR-rate-limit by @JagjeevanAK in
|
||||
[#19804](https://github.com/google-gemini/gemini-cli/pull/19804)
|
||||
- feat(cli): allow expanding full details of MCP tool on approval by @y-okt in
|
||||
[#19916](https://github.com/google-gemini/gemini-cli/pull/19916)
|
||||
- feat(security): Introduce Conseca framework by @shrishabh in
|
||||
[#13193](https://github.com/google-gemini/gemini-cli/pull/13193)
|
||||
- fix(cli): Remove unsafe type assertions in activityLogger #19713 by @Nixxx19
|
||||
in [#19745](https://github.com/google-gemini/gemini-cli/pull/19745)
|
||||
- feat: implement AfterTool tail tool calls by @googlestrobe in
|
||||
[#18486](https://github.com/google-gemini/gemini-cli/pull/18486)
|
||||
- ci(actions): fix PR rate limiter excluding maintainers by @scidomino in
|
||||
[#20117](https://github.com/google-gemini/gemini-cli/pull/20117)
|
||||
- Shortcuts: Move SectionHeader title below top line and refine styling by
|
||||
@keithguerin in
|
||||
[#18721](https://github.com/google-gemini/gemini-cli/pull/18721)
|
||||
- refactor(ui): Update and simplify use of gray colors in themes by @keithguerin
|
||||
in [#20141](https://github.com/google-gemini/gemini-cli/pull/20141)
|
||||
- fix punycode2 by @jacob314 in
|
||||
[#20154](https://github.com/google-gemini/gemini-cli/pull/20154)
|
||||
- feat(ide): add GEMINI_CLI_IDE_PID env var to override IDE process detection by
|
||||
@kiryltech in [#15842](https://github.com/google-gemini/gemini-cli/pull/15842)
|
||||
- feat(policy): Propagate Tool Annotations for MCP Servers by @jerop in
|
||||
[#20083](https://github.com/google-gemini/gemini-cli/pull/20083)
|
||||
- fix(a2a-server): pass allowedTools settings to core Config by @reyyanxahmed in
|
||||
[#19680](https://github.com/google-gemini/gemini-cli/pull/19680)
|
||||
- feat(mcp): add progress bar, throttling, and input validation for MCP tool
|
||||
progress by @jasmeetsb in
|
||||
[#19772](https://github.com/google-gemini/gemini-cli/pull/19772)
|
||||
- feat(policy): centralize plan mode tool visibility in policy engine by @jerop
|
||||
in [#20178](https://github.com/google-gemini/gemini-cli/pull/20178)
|
||||
- feat(browser): implement experimental browser agent by @gsquared94 in
|
||||
[#19284](https://github.com/google-gemini/gemini-cli/pull/19284)
|
||||
- feat(plan): summarize work after executing a plan by @jerop in
|
||||
[#19432](https://github.com/google-gemini/gemini-cli/pull/19432)
|
||||
- fix(core): create new McpClient on restart to apply updated config by @h30s in
|
||||
[#20126](https://github.com/google-gemini/gemini-cli/pull/20126)
|
||||
- Changelog for v0.30.0-preview.5 by @gemini-cli-robot in
|
||||
[#20107](https://github.com/google-gemini/gemini-cli/pull/20107)
|
||||
- Update packages. by @jacob314 in
|
||||
[#20152](https://github.com/google-gemini/gemini-cli/pull/20152)
|
||||
- Fix extension env dir loading issue by @chrstnb in
|
||||
[#20198](https://github.com/google-gemini/gemini-cli/pull/20198)
|
||||
- restrict /assign to help-wanted issues by @scidomino in
|
||||
[#20207](https://github.com/google-gemini/gemini-cli/pull/20207)
|
||||
- feat(plan): inject message when user manually exits Plan mode by @jerop in
|
||||
[#20203](https://github.com/google-gemini/gemini-cli/pull/20203)
|
||||
- feat(extensions): enforce folder trust for local extension install by @galz10
|
||||
in [#19703](https://github.com/google-gemini/gemini-cli/pull/19703)
|
||||
- feat(hooks): adds support for RuntimeHook functions. by @mbleigh in
|
||||
[#19598](https://github.com/google-gemini/gemini-cli/pull/19598)
|
||||
- Docs: Update UI links. by @jkcinouye in
|
||||
[#20224](https://github.com/google-gemini/gemini-cli/pull/20224)
|
||||
- feat: prompt users to run /terminal-setup with yes/no by @ishaanxgupta in
|
||||
[#16235](https://github.com/google-gemini/gemini-cli/pull/16235)
|
||||
- fix: additional high vulnerabilities (minimatch, cross-spawn) by @adamfweidman
|
||||
in [#20221](https://github.com/google-gemini/gemini-cli/pull/20221)
|
||||
- feat(telemetry): Add context breakdown to API response event by @SandyTao520
|
||||
in [#19699](https://github.com/google-gemini/gemini-cli/pull/19699)
|
||||
- Docs: Add nested sub-folders for related topics by @g-samroberts in
|
||||
[#20235](https://github.com/google-gemini/gemini-cli/pull/20235)
|
||||
- feat(plan): support automatic model switching for Plan Mode by @jerop in
|
||||
[#20240](https://github.com/google-gemini/gemini-cli/pull/20240)
|
||||
- fix(patch): cherry-pick 58df1c6 to release/v0.31.0-preview.0-pr-20374 to patch
|
||||
version v0.31.0-preview.0 and create version 0.31.0-preview.1 by
|
||||
@gemini-cli-robot in
|
||||
[#19490](https://github.com/google-gemini/gemini-cli/pull/19490)
|
||||
- fix(patch): cherry-pick c43500c to release/v0.30.0-preview.1-pr-19502 to patch
|
||||
version v0.30.0-preview.1 and create version 0.30.0-preview.2 by
|
||||
[#20568](https://github.com/google-gemini/gemini-cli/pull/20568)
|
||||
- fix(patch): cherry-pick ea48bd9 to release/v0.31.0-preview.1-pr-20577
|
||||
[CONFLICTS] by @gemini-cli-robot in
|
||||
[#20592](https://github.com/google-gemini/gemini-cli/pull/20592)
|
||||
- fix(patch): cherry-pick 32e777f to release/v0.31.0-preview.2-pr-20531 to patch
|
||||
version v0.31.0-preview.2 and create version 0.31.0-preview.3 by
|
||||
@gemini-cli-robot in
|
||||
[#19521](https://github.com/google-gemini/gemini-cli/pull/19521)
|
||||
- fix(patch): cherry-pick aa9163d to release/v0.30.0-preview.3-pr-19991 to patch
|
||||
version v0.30.0-preview.3 and create version 0.30.0-preview.4 by
|
||||
@gemini-cli-robot in
|
||||
[#20040](https://github.com/google-gemini/gemini-cli/pull/20040)
|
||||
- fix(patch): cherry-pick 2c1d6f8 to release/v0.30.0-preview.4-pr-19369 to patch
|
||||
version v0.30.0-preview.4 and create version 0.30.0-preview.5 by
|
||||
@gemini-cli-robot in
|
||||
[#20086](https://github.com/google-gemini/gemini-cli/pull/20086)
|
||||
- fix(patch): cherry-pick d96bd05 to release/v0.30.0-preview.5-pr-19867 to patch
|
||||
version v0.30.0-preview.5 and create version 0.30.0-preview.6 by
|
||||
@gemini-cli-robot in
|
||||
[#20112](https://github.com/google-gemini/gemini-cli/pull/20112)
|
||||
[#20607](https://github.com/google-gemini/gemini-cli/pull/20607)
|
||||
|
||||
**Full Changelog**:
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.29.7...v0.30.0
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.30.1...v0.31.0
|
||||
|
||||
+188
-392
@@ -1,6 +1,6 @@
|
||||
# Preview release: v0.31.0-preview.0
|
||||
# Preview release: v0.32.0-preview.0
|
||||
|
||||
Released: February 25, 2026
|
||||
Released: February 27, 2026
|
||||
|
||||
Our preview release includes the latest, new, and experimental features. This
|
||||
release may not be as stable as our [latest weekly release](latest.md).
|
||||
@@ -13,400 +13,196 @@ npm install -g @google/gemini-cli@preview
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Plan Mode Enhancements**: Numerous additions including automatic model
|
||||
switching, custom storage directory configuration, message injection upon
|
||||
manual exit, enforcement of read-only constraints, and centralized tool
|
||||
visibility in the policy engine.
|
||||
- **Policy Engine Updates**: Project-level policy support added, alongside MCP
|
||||
server wildcard support, tool annotation propagation and matching, and
|
||||
workspace-level "Always Allow" persistence.
|
||||
- **MCP Integration Improvements**: Better integration through support for MCP
|
||||
progress updates with input validation and throttling, environment variable
|
||||
expansion for servers, and full details expansion on tool approval.
|
||||
- **CLI & Core UX Enhancements**: Several UI and quality-of-life updates such as
|
||||
Alt+D for forward word deletion, macOS run-event notifications, enhanced
|
||||
folder trust configurations with security warnings, improved startup warnings,
|
||||
and a new experimental browser agent.
|
||||
- **Security & Stability**: Introduced the Conseca framework, deceptive URL and
|
||||
Unicode character detection, stricter access checks, rate limits on web fetch,
|
||||
and resolved multiple dependency vulnerabilities.
|
||||
- **Plan Mode Enhancements**: Significant updates to Plan Mode, including
|
||||
support for modifying plans in external editors, adaptive workflows based on
|
||||
task complexity, and new integration tests.
|
||||
- **Agent and Core Engine Updates**: Enabled the generalist agent, introduced
|
||||
`Kind.Agent` for sub-agent classification, implemented task tracking
|
||||
foundation, and improved Agent-to-Agent (A2A) streaming and content
|
||||
extraction.
|
||||
- **CLI & User Experience**: Introduced interactive shell autocompletion, added
|
||||
a new verbosity mode for cleaner error reporting, enabled parallel loading of
|
||||
extensions, and improved UI hints and shortcut handling.
|
||||
- **Billing and Security**: Implemented G1 AI credits overage flow with enhanced
|
||||
billing telemetry, updated the authentication handshake to specification, and
|
||||
added support for a policy engine in extensions.
|
||||
- **Stability and Bug Fixes**: Addressed numerous issues including 100% CPU
|
||||
consumption by orphaned processes, enhanced retry logic for Code Assist,
|
||||
reduced intrusive MCP errors, and merged duplicate imports across packages.
|
||||
|
||||
## What's Changed
|
||||
|
||||
- Use ranged reads and limited searches and fuzzy editing improvements by
|
||||
@gundermanc in
|
||||
[#19240](https://github.com/google-gemini/gemini-cli/pull/19240)
|
||||
- Fix bottom border color by @jacob314 in
|
||||
[#19266](https://github.com/google-gemini/gemini-cli/pull/19266)
|
||||
- Release note generator fix by @g-samroberts in
|
||||
[#19363](https://github.com/google-gemini/gemini-cli/pull/19363)
|
||||
- test(evals): add behavioral tests for tool output masking by @NTaylorMullen in
|
||||
[#19172](https://github.com/google-gemini/gemini-cli/pull/19172)
|
||||
- docs: clarify preflight instructions in GEMINI.md by @NTaylorMullen in
|
||||
[#19377](https://github.com/google-gemini/gemini-cli/pull/19377)
|
||||
- feat(cli): add gemini --resume hint on exit by @Mag1ck in
|
||||
[#16285](https://github.com/google-gemini/gemini-cli/pull/16285)
|
||||
- fix: optimize height calculations for ask_user dialog by @jackwotherspoon in
|
||||
[#19017](https://github.com/google-gemini/gemini-cli/pull/19017)
|
||||
- feat(cli): add Alt+D for forward word deletion by @scidomino in
|
||||
[#19300](https://github.com/google-gemini/gemini-cli/pull/19300)
|
||||
- Disable failing eval test by @chrstnb in
|
||||
[#19455](https://github.com/google-gemini/gemini-cli/pull/19455)
|
||||
- fix(cli): support legacy onConfirm callback in ToolActionsContext by
|
||||
- feat(plan): add integration tests for plan mode by @Adib234 in
|
||||
[#20214](https://github.com/google-gemini/gemini-cli/pull/20214)
|
||||
- fix(acp): update auth handshake to spec by @skeshive in
|
||||
[#19725](https://github.com/google-gemini/gemini-cli/pull/19725)
|
||||
- feat(core): implement robust A2A streaming reassembly and fix task continuity
|
||||
by @adamfweidman in
|
||||
[#20091](https://github.com/google-gemini/gemini-cli/pull/20091)
|
||||
- feat(cli): load extensions in parallel by @scidomino in
|
||||
[#20229](https://github.com/google-gemini/gemini-cli/pull/20229)
|
||||
- Plumb the maxAttempts setting through Config args by @kevinjwang1 in
|
||||
[#20239](https://github.com/google-gemini/gemini-cli/pull/20239)
|
||||
- fix(cli): skip 404 errors in setup-github file downloads by @h30s in
|
||||
[#20287](https://github.com/google-gemini/gemini-cli/pull/20287)
|
||||
- fix(cli): expose model.name setting in settings dialog for persistence by
|
||||
@achaljhawar in
|
||||
[#19605](https://github.com/google-gemini/gemini-cli/pull/19605)
|
||||
- docs: remove legacy cmd examples in favor of powershell by @scidomino in
|
||||
[#20323](https://github.com/google-gemini/gemini-cli/pull/20323)
|
||||
- feat(core): Enable model steering in workspace. by @joshualitt in
|
||||
[#20343](https://github.com/google-gemini/gemini-cli/pull/20343)
|
||||
- fix: remove trailing comma in issue triage workflow settings json by @Nixxx19
|
||||
in [#20265](https://github.com/google-gemini/gemini-cli/pull/20265)
|
||||
- feat(core): implement task tracker foundation and service by @anj-s in
|
||||
[#19464](https://github.com/google-gemini/gemini-cli/pull/19464)
|
||||
- test: support tests that include color information by @jacob314 in
|
||||
[#20220](https://github.com/google-gemini/gemini-cli/pull/20220)
|
||||
- feat(core): introduce Kind.Agent for sub-agent classification by @abhipatel12
|
||||
in [#20369](https://github.com/google-gemini/gemini-cli/pull/20369)
|
||||
- Changelog for v0.30.0 by @gemini-cli-robot in
|
||||
[#20252](https://github.com/google-gemini/gemini-cli/pull/20252)
|
||||
- Update changelog workflow to reject nightly builds by @g-samroberts in
|
||||
[#20248](https://github.com/google-gemini/gemini-cli/pull/20248)
|
||||
- Changelog for v0.31.0-preview.0 by @gemini-cli-robot in
|
||||
[#20249](https://github.com/google-gemini/gemini-cli/pull/20249)
|
||||
- feat(cli): hide workspace policy update dialog and auto-accept by default by
|
||||
@Abhijit-2592 in
|
||||
[#20351](https://github.com/google-gemini/gemini-cli/pull/20351)
|
||||
- feat(core): rename grep_search include parameter to include_pattern by
|
||||
@SandyTao520 in
|
||||
[#19369](https://github.com/google-gemini/gemini-cli/pull/19369)
|
||||
- chore(deps): bump tar from 7.5.7 to 7.5.8 by dependabot[bot] in
|
||||
[#19367](https://github.com/google-gemini/gemini-cli/pull/19367)
|
||||
- fix(plan): allow safe fallback when experiment setting for plan is not enabled
|
||||
but approval mode at startup is plan by @Adib234 in
|
||||
[#19439](https://github.com/google-gemini/gemini-cli/pull/19439)
|
||||
- Add explicit color-convert dependency by @chrstnb in
|
||||
[#19460](https://github.com/google-gemini/gemini-cli/pull/19460)
|
||||
- feat(devtools): migrate devtools package into monorepo by @SandyTao520 in
|
||||
[#18936](https://github.com/google-gemini/gemini-cli/pull/18936)
|
||||
- fix(core): clarify plan mode constraints and exit mechanism by @jerop in
|
||||
[#19438](https://github.com/google-gemini/gemini-cli/pull/19438)
|
||||
- feat(cli): add macOS run-event notifications (interactive only) by
|
||||
@LyalinDotCom in
|
||||
[#19056](https://github.com/google-gemini/gemini-cli/pull/19056)
|
||||
- Changelog for v0.29.0 by @gemini-cli-robot in
|
||||
[#19361](https://github.com/google-gemini/gemini-cli/pull/19361)
|
||||
- fix(ui): preventing empty history items from being added by @devr0306 in
|
||||
[#19014](https://github.com/google-gemini/gemini-cli/pull/19014)
|
||||
- Changelog for v0.30.0-preview.0 by @gemini-cli-robot in
|
||||
[#19364](https://github.com/google-gemini/gemini-cli/pull/19364)
|
||||
- feat(core): add support for MCP progress updates by @NTaylorMullen in
|
||||
[#19046](https://github.com/google-gemini/gemini-cli/pull/19046)
|
||||
- fix(core): ensure directory exists before writing conversation file by
|
||||
@godwiniheuwa in
|
||||
[#18429](https://github.com/google-gemini/gemini-cli/pull/18429)
|
||||
- fix(ui): move margin from top to bottom in ToolGroupMessage by @imadraude in
|
||||
[#17198](https://github.com/google-gemini/gemini-cli/pull/17198)
|
||||
- fix(cli): treat unknown slash commands as regular input instead of showing
|
||||
error by @skyvanguard in
|
||||
[#17393](https://github.com/google-gemini/gemini-cli/pull/17393)
|
||||
- feat(core): experimental in-progress steering hints (2 of 2) by @joshualitt in
|
||||
[#19307](https://github.com/google-gemini/gemini-cli/pull/19307)
|
||||
- docs(plan): add documentation for plan mode command by @Adib234 in
|
||||
[#19467](https://github.com/google-gemini/gemini-cli/pull/19467)
|
||||
- fix(core): ripgrep fails when pattern looks like ripgrep flag by @syvb in
|
||||
[#18858](https://github.com/google-gemini/gemini-cli/pull/18858)
|
||||
- fix(cli): disable auto-completion on Shift+Tab to preserve mode cycling by
|
||||
@NTaylorMullen in
|
||||
[#19451](https://github.com/google-gemini/gemini-cli/pull/19451)
|
||||
- use issuer instead of authorization_endpoint for oauth discovery by
|
||||
@garrettsparks in
|
||||
[#17332](https://github.com/google-gemini/gemini-cli/pull/17332)
|
||||
- feat(cli): include `/dir add` directories in @ autocomplete suggestions by
|
||||
@jasmeetsb in [#19246](https://github.com/google-gemini/gemini-cli/pull/19246)
|
||||
- feat(admin): Admin settings should only apply if adminControlsApplicable =
|
||||
true and fetch errors should be fatal by @skeshive in
|
||||
[#19453](https://github.com/google-gemini/gemini-cli/pull/19453)
|
||||
- Format strict-development-rules command by @g-samroberts in
|
||||
[#19484](https://github.com/google-gemini/gemini-cli/pull/19484)
|
||||
- feat(core): centralize compatibility checks and add TrueColor detection by
|
||||
[#20328](https://github.com/google-gemini/gemini-cli/pull/20328)
|
||||
- feat(plan): support opening and modifying plan in external editor by @Adib234
|
||||
in [#20348](https://github.com/google-gemini/gemini-cli/pull/20348)
|
||||
- feat(cli): implement interactive shell autocompletion by @mrpmohiburrahman in
|
||||
[#20082](https://github.com/google-gemini/gemini-cli/pull/20082)
|
||||
- fix(core): allow /memory add to work in plan mode by @Jefftree in
|
||||
[#20353](https://github.com/google-gemini/gemini-cli/pull/20353)
|
||||
- feat(core): add HTTP 499 to retryable errors and map to RetryableQuotaError by
|
||||
@bdmorgan in [#20432](https://github.com/google-gemini/gemini-cli/pull/20432)
|
||||
- feat(core): Enable generalist agent by @joshualitt in
|
||||
[#19665](https://github.com/google-gemini/gemini-cli/pull/19665)
|
||||
- Updated tests in TableRenderer.test.tsx to use SVG snapshots by @devr0306 in
|
||||
[#20450](https://github.com/google-gemini/gemini-cli/pull/20450)
|
||||
- Refactor Github Action per b/485167538 by @google-admin in
|
||||
[#19443](https://github.com/google-gemini/gemini-cli/pull/19443)
|
||||
- fix(github): resolve actionlint and yamllint regressions from #19443 by @jerop
|
||||
in [#20467](https://github.com/google-gemini/gemini-cli/pull/20467)
|
||||
- fix: action var usage by @galz10 in
|
||||
[#20492](https://github.com/google-gemini/gemini-cli/pull/20492)
|
||||
- feat(core): improve A2A content extraction by @adamfweidman in
|
||||
[#20487](https://github.com/google-gemini/gemini-cli/pull/20487)
|
||||
- fix(cli): support quota error fallbacks for all authentication types by
|
||||
@sehoon38 in [#20475](https://github.com/google-gemini/gemini-cli/pull/20475)
|
||||
- fix(core): flush transcript for pure tool-call responses to ensure BeforeTool
|
||||
hooks see complete state by @krishdef7 in
|
||||
[#20419](https://github.com/google-gemini/gemini-cli/pull/20419)
|
||||
- feat(plan): adapt planning workflow based on complexity of task by @jerop in
|
||||
[#20465](https://github.com/google-gemini/gemini-cli/pull/20465)
|
||||
- fix: prevent orphaned processes from consuming 100% CPU when terminal closes
|
||||
by @yuvrajangadsingh in
|
||||
[#16965](https://github.com/google-gemini/gemini-cli/pull/16965)
|
||||
- feat(core): increase fetch timeout and fix [object Object] error
|
||||
stringification by @bdmorgan in
|
||||
[#20441](https://github.com/google-gemini/gemini-cli/pull/20441)
|
||||
- [Gemma x Gemini CLI] Add an Experimental Gemma Router that uses a LiteRT-LM
|
||||
shim into the Composite Model Classifier Strategy by @sidwan02 in
|
||||
[#17231](https://github.com/google-gemini/gemini-cli/pull/17231)
|
||||
- docs(plan): update documentation regarding supporting editing of plan files
|
||||
during plan approval by @Adib234 in
|
||||
[#20452](https://github.com/google-gemini/gemini-cli/pull/20452)
|
||||
- test(cli): fix flaky ToolResultDisplay overflow test by @jwhelangoog in
|
||||
[#20518](https://github.com/google-gemini/gemini-cli/pull/20518)
|
||||
- ui(cli): reduce length of Ctrl+O hint by @jwhelangoog in
|
||||
[#20490](https://github.com/google-gemini/gemini-cli/pull/20490)
|
||||
- fix(ui): correct styled table width calculations by @devr0306 in
|
||||
[#20042](https://github.com/google-gemini/gemini-cli/pull/20042)
|
||||
- Avoid overaggressive unescaping by @scidomino in
|
||||
[#20520](https://github.com/google-gemini/gemini-cli/pull/20520)
|
||||
- feat(telemetry) Instrument traces with more attributes and make them available
|
||||
to OTEL users by @heaventourist in
|
||||
[#20237](https://github.com/google-gemini/gemini-cli/pull/20237)
|
||||
- Add support for policy engine in extensions by @chrstnb in
|
||||
[#20049](https://github.com/google-gemini/gemini-cli/pull/20049)
|
||||
- Docs: Update to Terms of Service & FAQ by @jkcinouye in
|
||||
[#20488](https://github.com/google-gemini/gemini-cli/pull/20488)
|
||||
- Fix bottom border rendering for search and add a regression test. by @jacob314
|
||||
in [#20517](https://github.com/google-gemini/gemini-cli/pull/20517)
|
||||
- fix(core): apply retry logic to CodeAssistServer for all users by @bdmorgan in
|
||||
[#20507](https://github.com/google-gemini/gemini-cli/pull/20507)
|
||||
- Fix extension MCP server env var loading by @chrstnb in
|
||||
[#20374](https://github.com/google-gemini/gemini-cli/pull/20374)
|
||||
- feat(ui): add 'ctrl+o' hint to truncated content message by @jerop in
|
||||
[#20529](https://github.com/google-gemini/gemini-cli/pull/20529)
|
||||
- Fix flicker showing message to press ctrl-O again to collapse. by @jacob314 in
|
||||
[#20414](https://github.com/google-gemini/gemini-cli/pull/20414)
|
||||
- fix(cli): hide shortcuts hint while model is thinking or the user has typed a
|
||||
prompt + add debounce to avoid flicker by @jacob314 in
|
||||
[#19389](https://github.com/google-gemini/gemini-cli/pull/19389)
|
||||
- feat(plan): update planning workflow to encourage multi-select with
|
||||
descriptions of options by @Adib234 in
|
||||
[#20491](https://github.com/google-gemini/gemini-cli/pull/20491)
|
||||
- refactor(core,cli): useAlternateBuffer read from config by @psinha40898 in
|
||||
[#20346](https://github.com/google-gemini/gemini-cli/pull/20346)
|
||||
- fix(cli): ensure dialogs stay scrolled to bottom in alternate buffer mode by
|
||||
@jacob314 in [#20527](https://github.com/google-gemini/gemini-cli/pull/20527)
|
||||
- fix(core): revert auto-save of policies to user space by @Abhijit-2592 in
|
||||
[#20531](https://github.com/google-gemini/gemini-cli/pull/20531)
|
||||
- Demote unreliable test. by @gundermanc in
|
||||
[#20571](https://github.com/google-gemini/gemini-cli/pull/20571)
|
||||
- fix(core): handle optional response fields from code assist API by @sehoon38
|
||||
in [#20345](https://github.com/google-gemini/gemini-cli/pull/20345)
|
||||
- fix(cli): keep thought summary when loading phrases are off by @LyalinDotCom
|
||||
in [#20497](https://github.com/google-gemini/gemini-cli/pull/20497)
|
||||
- feat(cli): add temporary flag to disable workspace policies by @Abhijit-2592
|
||||
in [#20523](https://github.com/google-gemini/gemini-cli/pull/20523)
|
||||
- Disable expensive and scheduled workflows on personal forks by @dewitt in
|
||||
[#20449](https://github.com/google-gemini/gemini-cli/pull/20449)
|
||||
- Moved markdown parsing logic to a separate util file by @devr0306 in
|
||||
[#20526](https://github.com/google-gemini/gemini-cli/pull/20526)
|
||||
- fix(plan): prevent agent from using ask_user for shell command confirmation by
|
||||
@Adib234 in [#20504](https://github.com/google-gemini/gemini-cli/pull/20504)
|
||||
- fix(core): disable retries for code assist streaming requests by @sehoon38 in
|
||||
[#20561](https://github.com/google-gemini/gemini-cli/pull/20561)
|
||||
- feat(billing): implement G1 AI credits overage flow with billing telemetry by
|
||||
@gsquared94 in
|
||||
[#18590](https://github.com/google-gemini/gemini-cli/pull/18590)
|
||||
- feat: better error messages by @gsquared94 in
|
||||
[#20577](https://github.com/google-gemini/gemini-cli/pull/20577)
|
||||
- fix(ui): persist expansion in AskUser dialog when navigating options by @jerop
|
||||
in [#20559](https://github.com/google-gemini/gemini-cli/pull/20559)
|
||||
- fix(cli): prevent sub-agent tool calls from leaking into UI by @abhipatel12 in
|
||||
[#20580](https://github.com/google-gemini/gemini-cli/pull/20580)
|
||||
- fix(cli): Shell autocomplete polish by @jacob314 in
|
||||
[#20411](https://github.com/google-gemini/gemini-cli/pull/20411)
|
||||
- Changelog for v0.31.0-preview.1 by @gemini-cli-robot in
|
||||
[#20590](https://github.com/google-gemini/gemini-cli/pull/20590)
|
||||
- Add slash command for promoting behavioral evals to CI blocking by @gundermanc
|
||||
in [#20575](https://github.com/google-gemini/gemini-cli/pull/20575)
|
||||
- Changelog for v0.30.1 by @gemini-cli-robot in
|
||||
[#20589](https://github.com/google-gemini/gemini-cli/pull/20589)
|
||||
- Add low/full CLI error verbosity mode for cleaner UI by @LyalinDotCom in
|
||||
[#20399](https://github.com/google-gemini/gemini-cli/pull/20399)
|
||||
- Disable Gemini PR reviews on draft PRs. by @gundermanc in
|
||||
[#20362](https://github.com/google-gemini/gemini-cli/pull/20362)
|
||||
- Docs: FAQ update by @jkcinouye in
|
||||
[#20585](https://github.com/google-gemini/gemini-cli/pull/20585)
|
||||
- fix(core): reduce intrusive MCP errors and deduplicate diagnostics by
|
||||
@spencer426 in
|
||||
[#19478](https://github.com/google-gemini/gemini-cli/pull/19478)
|
||||
- Remove unused files and update index and sidebar. by @g-samroberts in
|
||||
[#19479](https://github.com/google-gemini/gemini-cli/pull/19479)
|
||||
- Migrate core render util to use xterm.js as part of the rendering loop. by
|
||||
@jacob314 in [#19044](https://github.com/google-gemini/gemini-cli/pull/19044)
|
||||
- Changelog for v0.30.0-preview.1 by @gemini-cli-robot in
|
||||
[#19496](https://github.com/google-gemini/gemini-cli/pull/19496)
|
||||
- build: replace deprecated built-in punycode with userland package by @jacob314
|
||||
in [#19502](https://github.com/google-gemini/gemini-cli/pull/19502)
|
||||
- Speculative fixes to try to fix react error. by @jacob314 in
|
||||
[#19508](https://github.com/google-gemini/gemini-cli/pull/19508)
|
||||
- fix spacing by @jacob314 in
|
||||
[#19494](https://github.com/google-gemini/gemini-cli/pull/19494)
|
||||
- fix(core): ensure user rejections update tool outcome for telemetry by
|
||||
@abhiasap in [#18982](https://github.com/google-gemini/gemini-cli/pull/18982)
|
||||
- fix(acp): Initialize config (#18897) by @Mervap in
|
||||
[#18898](https://github.com/google-gemini/gemini-cli/pull/18898)
|
||||
- fix(core): add error logging for IDE fetch failures by @yuvrajangadsingh in
|
||||
[#17981](https://github.com/google-gemini/gemini-cli/pull/17981)
|
||||
- feat(acp): support set_mode interface (#18890) by @Mervap in
|
||||
[#18891](https://github.com/google-gemini/gemini-cli/pull/18891)
|
||||
- fix(core): robust workspace-based IDE connection discovery by @ehedlund in
|
||||
[#18443](https://github.com/google-gemini/gemini-cli/pull/18443)
|
||||
- Deflake windows tests. by @jacob314 in
|
||||
[#19511](https://github.com/google-gemini/gemini-cli/pull/19511)
|
||||
- Fix: Avoid tool confirmation timeout when no UI listeners are present by
|
||||
@pdHaku0 in [#17955](https://github.com/google-gemini/gemini-cli/pull/17955)
|
||||
- format md file by @scidomino in
|
||||
[#19474](https://github.com/google-gemini/gemini-cli/pull/19474)
|
||||
- feat(cli): add experimental.useOSC52Copy setting by @scidomino in
|
||||
[#19488](https://github.com/google-gemini/gemini-cli/pull/19488)
|
||||
- feat(cli): replace loading phrases boolean with enum setting by @LyalinDotCom
|
||||
in [#19347](https://github.com/google-gemini/gemini-cli/pull/19347)
|
||||
- Update skill to adjust for generated results. by @g-samroberts in
|
||||
[#19500](https://github.com/google-gemini/gemini-cli/pull/19500)
|
||||
- Fix message too large issue. by @gundermanc in
|
||||
[#19499](https://github.com/google-gemini/gemini-cli/pull/19499)
|
||||
- fix(core): prevent duplicate tool approval entries in auto-saved.toml by
|
||||
@Abhijit-2592 in
|
||||
[#19487](https://github.com/google-gemini/gemini-cli/pull/19487)
|
||||
- fix(core): resolve crash in ClearcutLogger when os.cpus() is empty by @Adib234
|
||||
in [#19555](https://github.com/google-gemini/gemini-cli/pull/19555)
|
||||
- chore(core): improve encapsulation and remove unused exports by @adamfweidman
|
||||
in [#19556](https://github.com/google-gemini/gemini-cli/pull/19556)
|
||||
- Revert "Add generic searchable list to back settings and extensions (… by
|
||||
@chrstnb in [#19434](https://github.com/google-gemini/gemini-cli/pull/19434)
|
||||
- fix(core): improve error type extraction for telemetry by @yunaseoul in
|
||||
[#19565](https://github.com/google-gemini/gemini-cli/pull/19565)
|
||||
- fix: remove extra padding in Composer by @jackwotherspoon in
|
||||
[#19529](https://github.com/google-gemini/gemini-cli/pull/19529)
|
||||
- feat(plan): support configuring custom plans storage directory by @jerop in
|
||||
[#19577](https://github.com/google-gemini/gemini-cli/pull/19577)
|
||||
- Migrate files to resource or references folder. by @g-samroberts in
|
||||
[#19503](https://github.com/google-gemini/gemini-cli/pull/19503)
|
||||
- feat(policy): implement project-level policy support by @Abhijit-2592 in
|
||||
[#18682](https://github.com/google-gemini/gemini-cli/pull/18682)
|
||||
- feat(core): Implement parallel FC for read only tools. by @joshualitt in
|
||||
[#18791](https://github.com/google-gemini/gemini-cli/pull/18791)
|
||||
- chore(skills): adds pr-address-comments skill to work on PR feedback by
|
||||
@mbleigh in [#19576](https://github.com/google-gemini/gemini-cli/pull/19576)
|
||||
- refactor(sdk): introduce session-based architecture by @mbleigh in
|
||||
[#19180](https://github.com/google-gemini/gemini-cli/pull/19180)
|
||||
- fix(ci): add fallback JSON extraction to issue triage workflow by @bdmorgan in
|
||||
[#19593](https://github.com/google-gemini/gemini-cli/pull/19593)
|
||||
- feat(core): refine Edit and WriteFile tool schemas for Gemini 3 by
|
||||
@SandyTao520 in
|
||||
[#19476](https://github.com/google-gemini/gemini-cli/pull/19476)
|
||||
- Changelog for v0.30.0-preview.3 by @gemini-cli-robot in
|
||||
[#19585](https://github.com/google-gemini/gemini-cli/pull/19585)
|
||||
- fix(plan): exclude EnterPlanMode tool from YOLO mode by @Adib234 in
|
||||
[#19570](https://github.com/google-gemini/gemini-cli/pull/19570)
|
||||
- chore: resolve build warnings and update dependencies by @mattKorwel in
|
||||
[#18880](https://github.com/google-gemini/gemini-cli/pull/18880)
|
||||
- feat(ui): add source indicators to slash commands by @ehedlund in
|
||||
[#18839](https://github.com/google-gemini/gemini-cli/pull/18839)
|
||||
- docs: refine Plan Mode documentation structure and workflow by @jerop in
|
||||
[#19644](https://github.com/google-gemini/gemini-cli/pull/19644)
|
||||
- Docs: Update release information regarding Gemini 3.1 by @jkcinouye in
|
||||
[#19568](https://github.com/google-gemini/gemini-cli/pull/19568)
|
||||
- fix(security): rate limit web_fetch tool to mitigate DDoS via prompt injection
|
||||
by @mattKorwel in
|
||||
[#19567](https://github.com/google-gemini/gemini-cli/pull/19567)
|
||||
- Add initial implementation of /extensions explore command by @chrstnb in
|
||||
[#19029](https://github.com/google-gemini/gemini-cli/pull/19029)
|
||||
- fix: use discoverOAuthFromWWWAuthenticate for reactive OAuth flow (#18760) by
|
||||
@maximus12793 in
|
||||
[#19038](https://github.com/google-gemini/gemini-cli/pull/19038)
|
||||
- Search updates by @alisa-alisa in
|
||||
[#19482](https://github.com/google-gemini/gemini-cli/pull/19482)
|
||||
- feat(cli): add support for numpad SS3 sequences by @scidomino in
|
||||
[#19659](https://github.com/google-gemini/gemini-cli/pull/19659)
|
||||
- feat(cli): enhance folder trust with configuration discovery and security
|
||||
warnings by @galz10 in
|
||||
[#19492](https://github.com/google-gemini/gemini-cli/pull/19492)
|
||||
- feat(ui): improve startup warnings UX with dismissal and show-count limits by
|
||||
@spencer426 in
|
||||
[#19584](https://github.com/google-gemini/gemini-cli/pull/19584)
|
||||
- feat(a2a): Add API key authentication provider by @adamfweidman in
|
||||
[#19548](https://github.com/google-gemini/gemini-cli/pull/19548)
|
||||
- Send accepted/removed lines with ACCEPT_FILE telemetry. by @gundermanc in
|
||||
[#19670](https://github.com/google-gemini/gemini-cli/pull/19670)
|
||||
- feat(models): support Gemini 3.1 Pro Preview and fixes by @sehoon38 in
|
||||
[#19676](https://github.com/google-gemini/gemini-cli/pull/19676)
|
||||
- feat(plan): enforce read-only constraints in Plan Mode by @mattKorwel in
|
||||
[#19433](https://github.com/google-gemini/gemini-cli/pull/19433)
|
||||
- fix(cli): allow perfect match @scripts/test-windows-paths.js completions to
|
||||
submit on Enter by @spencer426 in
|
||||
[#19562](https://github.com/google-gemini/gemini-cli/pull/19562)
|
||||
- fix(core): treat 503 Service Unavailable as retryable quota error by @sehoon38
|
||||
in [#19642](https://github.com/google-gemini/gemini-cli/pull/19642)
|
||||
- Update sidebar.json for to allow top nav tabs. by @g-samroberts in
|
||||
[#19595](https://github.com/google-gemini/gemini-cli/pull/19595)
|
||||
- security: strip deceptive Unicode characters from terminal output by @ehedlund
|
||||
in [#19026](https://github.com/google-gemini/gemini-cli/pull/19026)
|
||||
- Fixes 'input.on' is not a function error in Gemini CLI by @gundermanc in
|
||||
[#19691](https://github.com/google-gemini/gemini-cli/pull/19691)
|
||||
- Revert "feat(ui): add source indicators to slash commands" by @ehedlund in
|
||||
[#19695](https://github.com/google-gemini/gemini-cli/pull/19695)
|
||||
- security: implement deceptive URL detection and disclosure in tool
|
||||
confirmations by @ehedlund in
|
||||
[#19288](https://github.com/google-gemini/gemini-cli/pull/19288)
|
||||
- fix(core): restore auth consent in headless mode and add unit tests by
|
||||
@ehedlund in [#19689](https://github.com/google-gemini/gemini-cli/pull/19689)
|
||||
- Fix unsafe assertions in code_assist folder. by @gundermanc in
|
||||
[#19706](https://github.com/google-gemini/gemini-cli/pull/19706)
|
||||
- feat(cli): make JetBrains warning more specific by @jacob314 in
|
||||
[#19687](https://github.com/google-gemini/gemini-cli/pull/19687)
|
||||
- fix(cli): extensions dialog UX polish by @jacob314 in
|
||||
[#19685](https://github.com/google-gemini/gemini-cli/pull/19685)
|
||||
- fix(cli): use getDisplayString for manual model selection in dialog by
|
||||
@sehoon38 in [#19726](https://github.com/google-gemini/gemini-cli/pull/19726)
|
||||
- feat(policy): repurpose "Always Allow" persistence to workspace level by
|
||||
@Abhijit-2592 in
|
||||
[#19707](https://github.com/google-gemini/gemini-cli/pull/19707)
|
||||
- fix(cli): re-enable CLI banner by @sehoon38 in
|
||||
[#19741](https://github.com/google-gemini/gemini-cli/pull/19741)
|
||||
- Disallow and suppress unsafe assignment by @gundermanc in
|
||||
[#19736](https://github.com/google-gemini/gemini-cli/pull/19736)
|
||||
- feat(core): migrate read_file to 1-based start_line/end_line parameters by
|
||||
@adamfweidman in
|
||||
[#19526](https://github.com/google-gemini/gemini-cli/pull/19526)
|
||||
- feat(cli): improve CTRL+O experience for both standard and alternate screen
|
||||
buffer (ASB) modes by @jwhelangoog in
|
||||
[#19010](https://github.com/google-gemini/gemini-cli/pull/19010)
|
||||
- Utilize pipelining of grep_search -> read_file to eliminate turns by
|
||||
@gundermanc in
|
||||
[#19574](https://github.com/google-gemini/gemini-cli/pull/19574)
|
||||
- refactor(core): remove unsafe type assertions in error utils (Phase 1.1) by
|
||||
@mattKorwel in
|
||||
[#19750](https://github.com/google-gemini/gemini-cli/pull/19750)
|
||||
- Disallow unsafe returns. by @gundermanc in
|
||||
[#19767](https://github.com/google-gemini/gemini-cli/pull/19767)
|
||||
- fix(cli): filter subagent sessions from resume history by @abhipatel12 in
|
||||
[#19698](https://github.com/google-gemini/gemini-cli/pull/19698)
|
||||
- chore(lint): fix lint errors seen when running npm run lint by @abhipatel12 in
|
||||
[#19844](https://github.com/google-gemini/gemini-cli/pull/19844)
|
||||
- feat(core): remove unnecessary login verbiage from Code Assist auth by
|
||||
@NTaylorMullen in
|
||||
[#19861](https://github.com/google-gemini/gemini-cli/pull/19861)
|
||||
- fix(plan): time share by approval mode dashboard reporting negative time
|
||||
shares by @Adib234 in
|
||||
[#19847](https://github.com/google-gemini/gemini-cli/pull/19847)
|
||||
- fix(core): allow any preview model in quota access check by @bdmorgan in
|
||||
[#19867](https://github.com/google-gemini/gemini-cli/pull/19867)
|
||||
- fix(core): prevent omission placeholder deletions in replace/write_file by
|
||||
@nsalerni in [#19870](https://github.com/google-gemini/gemini-cli/pull/19870)
|
||||
- fix(core): add uniqueness guard to edit tool by @Shivangisharma4 in
|
||||
[#19890](https://github.com/google-gemini/gemini-cli/pull/19890)
|
||||
- refactor(config): remove enablePromptCompletion from settings by @sehoon38 in
|
||||
[#19974](https://github.com/google-gemini/gemini-cli/pull/19974)
|
||||
- refactor(core): move session conversion logic to core by @abhipatel12 in
|
||||
[#19972](https://github.com/google-gemini/gemini-cli/pull/19972)
|
||||
- Fix: Persist manual model selection on restart #19864 by @Nixxx19 in
|
||||
[#19891](https://github.com/google-gemini/gemini-cli/pull/19891)
|
||||
- fix(core): increase default retry attempts and add quota error backoff by
|
||||
@sehoon38 in [#19949](https://github.com/google-gemini/gemini-cli/pull/19949)
|
||||
- feat(core): add policy chain support for Gemini 3.1 by @sehoon38 in
|
||||
[#19991](https://github.com/google-gemini/gemini-cli/pull/19991)
|
||||
- Updates command reference and /stats command. by @g-samroberts in
|
||||
[#19794](https://github.com/google-gemini/gemini-cli/pull/19794)
|
||||
- Fix for silent failures in non-interactive mode by @owenofbrien in
|
||||
[#19905](https://github.com/google-gemini/gemini-cli/pull/19905)
|
||||
- fix(plan): allow plan mode writes on Windows and fix prompt paths by @Adib234
|
||||
in [#19658](https://github.com/google-gemini/gemini-cli/pull/19658)
|
||||
- fix(core): prevent OAuth server crash on unexpected requests by @reyyanxahmed
|
||||
in [#19668](https://github.com/google-gemini/gemini-cli/pull/19668)
|
||||
- feat: Map tool kinds to explicit ACP.ToolKind values and update test … by
|
||||
@sripasg in [#19547](https://github.com/google-gemini/gemini-cli/pull/19547)
|
||||
- chore: restrict gemini-automted-issue-triage to only allow echo by @galz10 in
|
||||
[#20047](https://github.com/google-gemini/gemini-cli/pull/20047)
|
||||
- Allow ask headers longer than 16 chars by @scidomino in
|
||||
[#20041](https://github.com/google-gemini/gemini-cli/pull/20041)
|
||||
- fix(core): prevent state corruption in McpClientManager during collis by @h30s
|
||||
in [#19782](https://github.com/google-gemini/gemini-cli/pull/19782)
|
||||
- fix(bundling): copy devtools package to bundle for runtime resolution by
|
||||
@SandyTao520 in
|
||||
[#19766](https://github.com/google-gemini/gemini-cli/pull/19766)
|
||||
- feat(policy): Support MCP Server Wildcards in Policy Engine by @jerop in
|
||||
[#20024](https://github.com/google-gemini/gemini-cli/pull/20024)
|
||||
- docs(CONTRIBUTING): update React DevTools version to 6 by @mmgok in
|
||||
[#20014](https://github.com/google-gemini/gemini-cli/pull/20014)
|
||||
- feat(core): optimize tool descriptions and schemas for Gemini 3 by
|
||||
@aishaneeshah in
|
||||
[#19643](https://github.com/google-gemini/gemini-cli/pull/19643)
|
||||
- feat(core): implement experimental direct web fetch by @mbleigh in
|
||||
[#19557](https://github.com/google-gemini/gemini-cli/pull/19557)
|
||||
- feat(core): replace expected_replacements with allow_multiple in replace tool
|
||||
by @SandyTao520 in
|
||||
[#20033](https://github.com/google-gemini/gemini-cli/pull/20033)
|
||||
- fix(sandbox): harden image packaging integrity checks by @aviralgarg05 in
|
||||
[#19552](https://github.com/google-gemini/gemini-cli/pull/19552)
|
||||
- fix(core): allow environment variable expansion and explicit overrides for MCP
|
||||
servers by @galz10 in
|
||||
[#18837](https://github.com/google-gemini/gemini-cli/pull/18837)
|
||||
- feat(policy): Implement Tool Annotation Matching in Policy Engine by @jerop in
|
||||
[#20029](https://github.com/google-gemini/gemini-cli/pull/20029)
|
||||
- fix(core): prevent utility calls from changing session active model by
|
||||
@adamfweidman in
|
||||
[#20035](https://github.com/google-gemini/gemini-cli/pull/20035)
|
||||
- fix(cli): skip workspace policy loading when in home directory by
|
||||
@Abhijit-2592 in
|
||||
[#20054](https://github.com/google-gemini/gemini-cli/pull/20054)
|
||||
- fix(scripts): Add Windows (win32/x64) support to lint.js by @ZafeerMahmood in
|
||||
[#16193](https://github.com/google-gemini/gemini-cli/pull/16193)
|
||||
- fix(a2a-server): Remove unsafe type assertions in agent by @Nixxx19 in
|
||||
[#19723](https://github.com/google-gemini/gemini-cli/pull/19723)
|
||||
- Fix: Handle corrupted token file gracefully when switching auth types (#19845)
|
||||
by @Nixxx19 in
|
||||
[#19850](https://github.com/google-gemini/gemini-cli/pull/19850)
|
||||
- fix critical dep vulnerability by @scidomino in
|
||||
[#20087](https://github.com/google-gemini/gemini-cli/pull/20087)
|
||||
- Add new setting to configure maxRetries by @kevinjwang1 in
|
||||
[#20064](https://github.com/google-gemini/gemini-cli/pull/20064)
|
||||
- Stabilize tests. by @gundermanc in
|
||||
[#20095](https://github.com/google-gemini/gemini-cli/pull/20095)
|
||||
- make windows tests mandatory by @scidomino in
|
||||
[#20096](https://github.com/google-gemini/gemini-cli/pull/20096)
|
||||
- Add 3.1 pro preview to behavioral evals. by @gundermanc in
|
||||
[#20088](https://github.com/google-gemini/gemini-cli/pull/20088)
|
||||
- feat:PR-rate-limit by @JagjeevanAK in
|
||||
[#19804](https://github.com/google-gemini/gemini-cli/pull/19804)
|
||||
- feat(cli): allow expanding full details of MCP tool on approval by @y-okt in
|
||||
[#19916](https://github.com/google-gemini/gemini-cli/pull/19916)
|
||||
- feat(security): Introduce Conseca framework by @shrishabh in
|
||||
[#13193](https://github.com/google-gemini/gemini-cli/pull/13193)
|
||||
- fix(cli): Remove unsafe type assertions in activityLogger #19713 by @Nixxx19
|
||||
in [#19745](https://github.com/google-gemini/gemini-cli/pull/19745)
|
||||
- feat: implement AfterTool tail tool calls by @googlestrobe in
|
||||
[#18486](https://github.com/google-gemini/gemini-cli/pull/18486)
|
||||
- ci(actions): fix PR rate limiter excluding maintainers by @scidomino in
|
||||
[#20117](https://github.com/google-gemini/gemini-cli/pull/20117)
|
||||
- Shortcuts: Move SectionHeader title below top line and refine styling by
|
||||
@keithguerin in
|
||||
[#18721](https://github.com/google-gemini/gemini-cli/pull/18721)
|
||||
- refactor(ui): Update and simplify use of gray colors in themes by @keithguerin
|
||||
in [#20141](https://github.com/google-gemini/gemini-cli/pull/20141)
|
||||
- fix punycode2 by @jacob314 in
|
||||
[#20154](https://github.com/google-gemini/gemini-cli/pull/20154)
|
||||
- feat(ide): add GEMINI_CLI_IDE_PID env var to override IDE process detection by
|
||||
@kiryltech in [#15842](https://github.com/google-gemini/gemini-cli/pull/15842)
|
||||
- feat(policy): Propagate Tool Annotations for MCP Servers by @jerop in
|
||||
[#20083](https://github.com/google-gemini/gemini-cli/pull/20083)
|
||||
- fix(a2a-server): pass allowedTools settings to core Config by @reyyanxahmed in
|
||||
[#19680](https://github.com/google-gemini/gemini-cli/pull/19680)
|
||||
- feat(mcp): add progress bar, throttling, and input validation for MCP tool
|
||||
progress by @jasmeetsb in
|
||||
[#19772](https://github.com/google-gemini/gemini-cli/pull/19772)
|
||||
- feat(policy): centralize plan mode tool visibility in policy engine by @jerop
|
||||
in [#20178](https://github.com/google-gemini/gemini-cli/pull/20178)
|
||||
- feat(browser): implement experimental browser agent by @gsquared94 in
|
||||
[#19284](https://github.com/google-gemini/gemini-cli/pull/19284)
|
||||
- feat(plan): summarize work after executing a plan by @jerop in
|
||||
[#19432](https://github.com/google-gemini/gemini-cli/pull/19432)
|
||||
- fix(core): create new McpClient on restart to apply updated config by @h30s in
|
||||
[#20126](https://github.com/google-gemini/gemini-cli/pull/20126)
|
||||
- Changelog for v0.30.0-preview.5 by @gemini-cli-robot in
|
||||
[#20107](https://github.com/google-gemini/gemini-cli/pull/20107)
|
||||
- Update packages. by @jacob314 in
|
||||
[#20152](https://github.com/google-gemini/gemini-cli/pull/20152)
|
||||
- Fix extension env dir loading issue by @chrstnb in
|
||||
[#20198](https://github.com/google-gemini/gemini-cli/pull/20198)
|
||||
- restrict /assign to help-wanted issues by @scidomino in
|
||||
[#20207](https://github.com/google-gemini/gemini-cli/pull/20207)
|
||||
- feat(plan): inject message when user manually exits Plan mode by @jerop in
|
||||
[#20203](https://github.com/google-gemini/gemini-cli/pull/20203)
|
||||
- feat(extensions): enforce folder trust for local extension install by @galz10
|
||||
in [#19703](https://github.com/google-gemini/gemini-cli/pull/19703)
|
||||
- feat(hooks): adds support for RuntimeHook functions. by @mbleigh in
|
||||
[#19598](https://github.com/google-gemini/gemini-cli/pull/19598)
|
||||
- Docs: Update UI links. by @jkcinouye in
|
||||
[#20224](https://github.com/google-gemini/gemini-cli/pull/20224)
|
||||
- feat: prompt users to run /terminal-setup with yes/no by @ishaanxgupta in
|
||||
[#16235](https://github.com/google-gemini/gemini-cli/pull/16235)
|
||||
- fix: additional high vulnerabilities (minimatch, cross-spawn) by @adamfweidman
|
||||
in [#20221](https://github.com/google-gemini/gemini-cli/pull/20221)
|
||||
- feat(telemetry): Add context breakdown to API response event by @SandyTao520
|
||||
in [#19699](https://github.com/google-gemini/gemini-cli/pull/19699)
|
||||
- Docs: Add nested sub-folders for related topics by @g-samroberts in
|
||||
[#20235](https://github.com/google-gemini/gemini-cli/pull/20235)
|
||||
- feat(plan): support automatic model switching for Plan Mode by @jerop in
|
||||
[#20240](https://github.com/google-gemini/gemini-cli/pull/20240)
|
||||
[#20232](https://github.com/google-gemini/gemini-cli/pull/20232)
|
||||
- docs: fix spelling typos in installation guide by @campox747 in
|
||||
[#20579](https://github.com/google-gemini/gemini-cli/pull/20579)
|
||||
- Promote stable tests to CI blocking. by @gundermanc in
|
||||
[#20581](https://github.com/google-gemini/gemini-cli/pull/20581)
|
||||
- feat(core): enable contiguous parallel admission for Kind.Agent tools by
|
||||
@abhipatel12 in
|
||||
[#20583](https://github.com/google-gemini/gemini-cli/pull/20583)
|
||||
- Enforce import/no-duplicates as error by @Nixxx19 in
|
||||
[#19797](https://github.com/google-gemini/gemini-cli/pull/19797)
|
||||
- fix: merge duplicate imports in sdk and test-utils packages (1/4) by @Nixxx19
|
||||
in [#19777](https://github.com/google-gemini/gemini-cli/pull/19777)
|
||||
- fix: merge duplicate imports in a2a-server package (2/4) by @Nixxx19 in
|
||||
[#19781](https://github.com/google-gemini/gemini-cli/pull/19781)
|
||||
|
||||
**Full Changelog**:
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.30.0-preview.6...v0.31.0-preview.0
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.31.0-preview.3...v0.32.0-preview.0
|
||||
|
||||
+12
-12
@@ -5,18 +5,18 @@ and parameters.
|
||||
|
||||
## CLI commands
|
||||
|
||||
| Command | Description | Example |
|
||||
| ---------------------------------- | ---------------------------------- | --------------------------------------------------- |
|
||||
| `gemini` | Start interactive REPL | `gemini` |
|
||||
| `gemini "query"` | Query non-interactively, then exit | `gemini "explain this project"` |
|
||||
| `cat file \| gemini` | Process piped content | `cat logs.txt \| gemini` |
|
||||
| `gemini -i "query"` | Execute and continue interactively | `gemini -i "What is the purpose of this project?"` |
|
||||
| `gemini -r "latest"` | Continue most recent session | `gemini -r "latest"` |
|
||||
| `gemini -r "latest" "query"` | Continue session with a new prompt | `gemini -r "latest" "Check for type errors"` |
|
||||
| `gemini -r "<session-id>" "query"` | Resume session by ID | `gemini -r "abc123" "Finish this PR"` |
|
||||
| `gemini update` | Update to latest version | `gemini update` |
|
||||
| `gemini extensions` | Manage extensions | See [Extensions Management](#extensions-management) |
|
||||
| `gemini mcp` | Configure MCP servers | See [MCP Server Management](#mcp-server-management) |
|
||||
| Command | Description | Example |
|
||||
| ---------------------------------- | ---------------------------------- | ------------------------------------------------------------ |
|
||||
| `gemini` | Start interactive REPL | `gemini` |
|
||||
| `gemini "query"` | Query non-interactively, then exit | `gemini "explain this project"` |
|
||||
| `cat file \| gemini` | Process piped content | `cat logs.txt \| gemini`<br>`Get-Content logs.txt \| gemini` |
|
||||
| `gemini -i "query"` | Execute and continue interactively | `gemini -i "What is the purpose of this project?"` |
|
||||
| `gemini -r "latest"` | Continue most recent session | `gemini -r "latest"` |
|
||||
| `gemini -r "latest" "query"` | Continue session with a new prompt | `gemini -r "latest" "Check for type errors"` |
|
||||
| `gemini -r "<session-id>" "query"` | Resume session by ID | `gemini -r "abc123" "Finish this PR"` |
|
||||
| `gemini update` | Update to latest version | `gemini update` |
|
||||
| `gemini extensions` | Manage extensions | See [Extensions Management](#extensions-management) |
|
||||
| `gemini mcp` | Configure MCP servers | See [MCP Server Management](#mcp-server-management) |
|
||||
|
||||
### Positional arguments
|
||||
|
||||
|
||||
@@ -278,11 +278,20 @@ Let's create a global command that asks the model to refactor a piece of code.
|
||||
First, ensure the user commands directory exists, then create a `refactor`
|
||||
subdirectory for organization and the final TOML file.
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.gemini/commands/refactor
|
||||
touch ~/.gemini/commands/refactor/pure.toml
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.gemini\commands\refactor"
|
||||
New-Item -ItemType File -Force -Path "$env:USERPROFILE\.gemini\commands\refactor\pure.toml"
|
||||
```
|
||||
|
||||
**2. Add the content to the file:**
|
||||
|
||||
Open `~/.gemini/commands/refactor/pure.toml` in your editor and add the
|
||||
|
||||
@@ -203,6 +203,15 @@ with the actual Gemini CLI process, which inherits the environment variable.
|
||||
This makes it significantly more difficult for a user to bypass the enforced
|
||||
settings.
|
||||
|
||||
**PowerShell Profile (Windows alternative):**
|
||||
|
||||
On Windows, administrators can achieve similar results by adding the environment
|
||||
variable to the system-wide or user-specific PowerShell profile:
|
||||
|
||||
```powershell
|
||||
Add-Content -Path $PROFILE -Value '$env:GEMINI_CLI_SYSTEM_SETTINGS_PATH="C:\ProgramData\gemini-cli\settings.json"'
|
||||
```
|
||||
|
||||
## User isolation in shared environments
|
||||
|
||||
In shared compute environments (like ML experiment runners or shared build
|
||||
@@ -214,12 +223,22 @@ use the `GEMINI_CLI_HOME` environment variable to point to a unique directory
|
||||
for a specific user or job. The CLI will create a `.gemini` folder inside the
|
||||
specified path.
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
# Isolate state for a specific job
|
||||
export GEMINI_CLI_HOME="/tmp/gemini-job-123"
|
||||
gemini
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
# Isolate state for a specific job
|
||||
$env:GEMINI_CLI_HOME="C:\temp\gemini-job-123"
|
||||
gemini
|
||||
```
|
||||
|
||||
## Restricting tool access
|
||||
|
||||
You can significantly enhance security by controlling which tools the Gemini
|
||||
|
||||
+5
-14
@@ -19,24 +19,15 @@ Use the following command in Gemini CLI:
|
||||
|
||||
Running this command will open a dialog with your options:
|
||||
|
||||
| Option | Description | Models |
|
||||
| ----------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| Auto (Gemini 3) | Let the system choose the best Gemini 3 model for your task. | gemini-3-pro-preview (if enabled), gemini-3-flash-preview (if enabled) |
|
||||
| Auto (Gemini 2.5) | Let the system choose the best Gemini 2.5 model for your task. | gemini-2.5-pro, gemini-2.5-flash |
|
||||
| Manual | Select a specific model. | Any available model. |
|
||||
| Option | Description | Models |
|
||||
| ----------------- | -------------------------------------------------------------- | -------------------------------------------- |
|
||||
| Auto (Gemini 3) | Let the system choose the best Gemini 3 model for your task. | gemini-3-pro-preview, gemini-3-flash-preview |
|
||||
| Auto (Gemini 2.5) | Let the system choose the best Gemini 2.5 model for your task. | gemini-2.5-pro, gemini-2.5-flash |
|
||||
| Manual | Select a specific model. | Any available model. |
|
||||
|
||||
We recommend selecting one of the above **Auto** options. However, you can
|
||||
select **Manual** to select a specific model from those available.
|
||||
|
||||
### Gemini 3 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 on Gemini CLI](../get-started/gemini-3.md).
|
||||
|
||||
To enable Gemini 3 Pro and Gemini 3 Flash (if available), enable
|
||||
[**Preview Features** by using the `settings` command](../cli/settings.md).
|
||||
|
||||
You can also use the `--model` flag to specify a particular Gemini model on
|
||||
startup. For more details, refer to the
|
||||
[configuration documentation](../reference/configuration.md).
|
||||
|
||||
+42
-2
@@ -55,12 +55,27 @@ from your organization's registry.
|
||||
```bash
|
||||
# Enable sandboxing with command flag
|
||||
gemini -s -p "analyze the code structure"
|
||||
```
|
||||
|
||||
# Use environment variable
|
||||
**Use environment variable**
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export GEMINI_SANDBOX=true
|
||||
gemini -p "run the test suite"
|
||||
```
|
||||
|
||||
# Configure in settings.json
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:GEMINI_SANDBOX="true"
|
||||
gemini -p "run the test suite"
|
||||
```
|
||||
|
||||
**Configure in settings.json**
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"sandbox": "docker"
|
||||
@@ -99,26 +114,51 @@ use cases.
|
||||
|
||||
To disable SELinux labeling for volume mounts, you can set the following:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export SANDBOX_FLAGS="--security-opt label=disable"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:SANDBOX_FLAGS="--security-opt label=disable"
|
||||
```
|
||||
|
||||
Multiple flags can be provided as a space-separated string:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export SANDBOX_FLAGS="--flag1 --flag2=value"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:SANDBOX_FLAGS="--flag1 --flag2=value"
|
||||
```
|
||||
|
||||
## Linux UID/GID handling
|
||||
|
||||
The sandbox automatically handles user permissions on Linux. Override these
|
||||
permissions with:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export SANDBOX_SET_UID_GID=true # Force host UID/GID
|
||||
export SANDBOX_SET_UID_GID=false # Disable UID/GID mapping
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:SANDBOX_SET_UID_GID="true" # Force host UID/GID
|
||||
$env:SANDBOX_SET_UID_GID="false" # Disable UID/GID mapping
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common issues
|
||||
|
||||
@@ -72,6 +72,7 @@ they appear in the UI.
|
||||
| Incremental Rendering | `ui.incrementalRendering` | Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled. | `true` |
|
||||
| Show Spinner | `ui.showSpinner` | Show the spinner during operations. | `true` |
|
||||
| Loading Phrases | `ui.loadingPhrases` | What to show while the model is working: tips, witty comments, both, or nothing. | `"tips"` |
|
||||
| Error Verbosity | `ui.errorVerbosity` | Controls whether recoverable errors are hidden (low) or fully shown (full). | `"low"` |
|
||||
| Screen Reader Mode | `ui.accessibility.screenReader` | Render output in plain-text to be more screen reader accessible | `false` |
|
||||
|
||||
### IDE
|
||||
@@ -80,6 +81,12 @@ they appear in the UI.
|
||||
| -------- | ------------- | ---------------------------- | ------- |
|
||||
| IDE Mode | `ide.enabled` | Enable IDE integration mode. | `false` |
|
||||
|
||||
### Billing
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ---------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| Overage Strategy | `billing.overageStrategy` | How to handle quota exhaustion when AI credits are available. 'ask' prompts each time, 'always' automatically uses credits, 'never' disables credit usage. | `"ask"` |
|
||||
|
||||
### Model
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
|
||||
@@ -103,23 +103,52 @@ Before using either method below, complete these steps:
|
||||
|
||||
1. Set your Google Cloud project ID:
|
||||
- For telemetry in a separate project from inference:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export OTLP_GOOGLE_CLOUD_PROJECT="your-telemetry-project-id"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:OTLP_GOOGLE_CLOUD_PROJECT="your-telemetry-project-id"
|
||||
```
|
||||
|
||||
- For telemetry in the same project as inference:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export GOOGLE_CLOUD_PROJECT="your-project-id"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:GOOGLE_CLOUD_PROJECT="your-project-id"
|
||||
```
|
||||
|
||||
2. Authenticate with Google Cloud:
|
||||
- If using a user account:
|
||||
```bash
|
||||
gcloud auth application-default login
|
||||
```
|
||||
- If using a service account:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/service-account.json"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:GOOGLE_APPLICATION_CREDENTIALS="C:\path\to\your\service-account.json"
|
||||
```
|
||||
|
||||
3. Make sure your account or service account has these IAM roles:
|
||||
- Cloud Trace Agent
|
||||
- Monitoring Metric Writer
|
||||
|
||||
@@ -37,10 +37,18 @@ output.
|
||||
|
||||
Pipe a file:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
cat error.log | gemini "Explain why this failed"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
Get-Content error.log | gemini "Explain why this failed"
|
||||
```
|
||||
|
||||
Pipe a command:
|
||||
|
||||
```bash
|
||||
@@ -57,7 +65,10 @@ results to a file.
|
||||
You have a folder of Python scripts and want to generate a `README.md` for each
|
||||
one.
|
||||
|
||||
1. Save the following code as `generate_docs.sh`:
|
||||
1. Save the following code as `generate_docs.sh` (or `generate_docs.ps1` for
|
||||
Windows):
|
||||
|
||||
**macOS/Linux (`generate_docs.sh`)**
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
@@ -72,13 +83,34 @@ one.
|
||||
done
|
||||
```
|
||||
|
||||
**Windows PowerShell (`generate_docs.ps1`)**
|
||||
|
||||
```powershell
|
||||
# Loop through all Python files
|
||||
Get-ChildItem -Filter *.py | ForEach-Object {
|
||||
Write-Host "Generating docs for $($_.Name)..."
|
||||
|
||||
$newName = $_.Name -replace '\.py$', '.md'
|
||||
# Ask Gemini CLI to generate the documentation and print it to stdout
|
||||
gemini "Generate a Markdown documentation summary for @$($_.Name). Print the result to standard output." | Out-File -FilePath $newName -Encoding utf8
|
||||
}
|
||||
```
|
||||
|
||||
2. Make the script executable and run it in your directory:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
chmod +x generate_docs.sh
|
||||
./generate_docs.sh
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
.\generate_docs.ps1
|
||||
```
|
||||
|
||||
This creates a corresponding Markdown file for every Python file in the
|
||||
folder.
|
||||
|
||||
@@ -90,7 +122,10 @@ like `jq`. To get pure JSON data from the model, combine the
|
||||
|
||||
### Scenario: Extract and return structured data
|
||||
|
||||
1. Save the following script as `generate_json.sh`:
|
||||
1. Save the following script as `generate_json.sh` (or `generate_json.ps1` for
|
||||
Windows):
|
||||
|
||||
**macOS/Linux (`generate_json.sh`)**
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
@@ -105,13 +140,35 @@ like `jq`. To get pure JSON data from the model, combine the
|
||||
gemini --output-format json "Return a raw JSON object with keys 'version' and 'deps' from @package.json" | jq -r '.response' > data.json
|
||||
```
|
||||
|
||||
2. Run `generate_json.sh`:
|
||||
**Windows PowerShell (`generate_json.ps1`)**
|
||||
|
||||
```powershell
|
||||
# Ensure we are in a project root
|
||||
if (-not (Test-Path "package.json")) {
|
||||
Write-Error "Error: package.json not found."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Extract data (requires jq installed, or you can use ConvertFrom-Json)
|
||||
$output = gemini --output-format json "Return a raw JSON object with keys 'version' and 'deps' from @package.json" | ConvertFrom-Json
|
||||
$output.response | Out-File -FilePath data.json -Encoding utf8
|
||||
```
|
||||
|
||||
2. Run the script:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
chmod +x generate_json.sh
|
||||
./generate_json.sh
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
.\generate_json.ps1
|
||||
```
|
||||
|
||||
3. Check `data.json`. The file should look like this:
|
||||
|
||||
```json
|
||||
@@ -129,8 +186,10 @@ Use headless mode to perform custom, automated AI tasks.
|
||||
|
||||
### Scenario: Create a "Smart Commit" alias
|
||||
|
||||
You can add a function to your shell configuration (like `.zshrc` or `.bashrc`)
|
||||
to create a `git commit` wrapper that writes the message for you.
|
||||
You can add a function to your shell configuration to create a `git commit`
|
||||
wrapper that writes the message for you.
|
||||
|
||||
**macOS/Linux (Bash/Zsh)**
|
||||
|
||||
1. Open your `.zshrc` file (or `.bashrc` if you use Bash) in your preferred
|
||||
text editor.
|
||||
@@ -170,6 +229,43 @@ to create a `git commit` wrapper that writes the message for you.
|
||||
source ~/.zshrc
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
1. Open your PowerShell profile in your preferred text editor.
|
||||
|
||||
```powershell
|
||||
notepad $PROFILE
|
||||
```
|
||||
|
||||
2. Scroll to the very bottom of the file and paste this code:
|
||||
|
||||
```powershell
|
||||
function gcommit {
|
||||
# Get the diff of staged changes
|
||||
$diff = git diff --staged
|
||||
|
||||
if (-not $diff) {
|
||||
Write-Host "No staged changes to commit."
|
||||
return
|
||||
}
|
||||
|
||||
# Ask Gemini to write the message
|
||||
Write-Host "Generating commit message..."
|
||||
$msg = $diff | gemini "Write a concise Conventional Commit message for this diff. Output ONLY the message."
|
||||
|
||||
# Commit with the generated message
|
||||
git commit -m "$msg"
|
||||
}
|
||||
```
|
||||
|
||||
Save your file and exit.
|
||||
|
||||
3. Run this command to make the function available immediately:
|
||||
|
||||
```powershell
|
||||
. $PROFILE
|
||||
```
|
||||
|
||||
4. Use your new command:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -20,10 +20,18 @@ Most MCP servers require authentication. For GitHub, you need a PAT.
|
||||
**Read/Write** access to **Issues** and **Pull Requests**.
|
||||
3. Store it in your environment:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export GITHUB_PERSONAL_ACCESS_TOKEN="github_pat_..."
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:GITHUB_PERSONAL_ACCESS_TOKEN="github_pat_..."
|
||||
```
|
||||
|
||||
## How to configure Gemini CLI
|
||||
|
||||
You tell Gemini about new servers by editing your `settings.json`.
|
||||
|
||||
@@ -14,10 +14,18 @@ responding correctly.
|
||||
|
||||
1. Run the following command to create the folders:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
mkdir -p .gemini/skills/api-auditor/scripts
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path ".gemini\skills\api-auditor\scripts"
|
||||
```
|
||||
|
||||
### Create the definition
|
||||
|
||||
1. Create a file at `.gemini/skills/api-auditor/SKILL.md`. This tells the agent
|
||||
|
||||
@@ -189,10 +189,18 @@ Custom commands create shortcuts for complex prompts.
|
||||
|
||||
1. Create a `commands` directory and a subdirectory for your command group:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
mkdir -p commands/fs
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path "commands\fs"
|
||||
```
|
||||
|
||||
2. Create a file named `commands/fs/grep-code.toml`:
|
||||
|
||||
```toml
|
||||
@@ -252,10 +260,18 @@ Skills are activated only when needed, which saves context tokens.
|
||||
|
||||
1. Create a `skills` directory and a subdirectory for your skill:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
mkdir -p skills/security-audit
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path "skills\security-audit"
|
||||
```
|
||||
|
||||
2. Create a `skills/security-audit/SKILL.md` file:
|
||||
|
||||
```markdown
|
||||
|
||||
@@ -78,11 +78,20 @@ To authenticate and use Gemini CLI with a Gemini API key:
|
||||
|
||||
2. Set the `GEMINI_API_KEY` environment variable to your key. For example:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
# Replace YOUR_GEMINI_API_KEY with the key from AI Studio
|
||||
export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
# Replace YOUR_GEMINI_API_KEY with the key from AI Studio
|
||||
$env:GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
|
||||
```
|
||||
|
||||
To make this setting persistent, see
|
||||
[Persisting Environment Variables](#persisting-vars).
|
||||
|
||||
@@ -114,12 +123,22 @@ or the location where you want to run your jobs.
|
||||
|
||||
For example:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
# Replace with your project ID and desired location (e.g., us-central1)
|
||||
export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
|
||||
export GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
# Replace with your project ID and desired location (e.g., us-central1)
|
||||
$env:GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
|
||||
$env:GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"
|
||||
```
|
||||
|
||||
To make any Vertex AI environment variable settings persistent, see
|
||||
[Persisting Environment Variables](#persisting-vars).
|
||||
|
||||
@@ -130,9 +149,17 @@ Consider this authentication method if you have Google Cloud CLI installed.
|
||||
> **Note:** If you have previously set `GOOGLE_API_KEY` or `GEMINI_API_KEY`, you
|
||||
> must unset them to use ADC:
|
||||
>
|
||||
> **macOS/Linux**
|
||||
>
|
||||
> ```bash
|
||||
> unset GOOGLE_API_KEY GEMINI_API_KEY
|
||||
> ```
|
||||
>
|
||||
> **Windows (PowerShell)**
|
||||
>
|
||||
> ```powershell
|
||||
> Remove-Item Env:\GOOGLE_API_KEY, Env:\GEMINI_API_KEY -ErrorAction Ignore
|
||||
> ```
|
||||
|
||||
1. Verify you have a Google Cloud project and Vertex AI API is enabled.
|
||||
|
||||
@@ -160,9 +187,17 @@ pipelines, or if your organization restricts user-based ADC or API key creation.
|
||||
> **Note:** If you have previously set `GOOGLE_API_KEY` or `GEMINI_API_KEY`, you
|
||||
> must unset them:
|
||||
>
|
||||
> **macOS/Linux**
|
||||
>
|
||||
> ```bash
|
||||
> unset GOOGLE_API_KEY GEMINI_API_KEY
|
||||
> ```
|
||||
>
|
||||
> **Windows (PowerShell)**
|
||||
>
|
||||
> ```powershell
|
||||
> Remove-Item Env:\GOOGLE_API_KEY, Env:\GEMINI_API_KEY -ErrorAction Ignore
|
||||
> ```
|
||||
|
||||
1. [Create a service account and key](https://cloud.google.com/iam/docs/keys-create-delete)
|
||||
and download the provided JSON file. Assign the "Vertex AI User" role to the
|
||||
@@ -171,11 +206,20 @@ pipelines, or if your organization restricts user-based ADC or API key creation.
|
||||
2. Set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable to the JSON
|
||||
file's absolute path. For example:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
# Replace /path/to/your/keyfile.json with the actual path
|
||||
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/keyfile.json"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
# Replace C:\path\to\your\keyfile.json with the actual path
|
||||
$env:GOOGLE_APPLICATION_CREDENTIALS="C:\path\to\your\keyfile.json"
|
||||
```
|
||||
|
||||
3. [Configure your Google Cloud Project](#set-gcp).
|
||||
|
||||
4. Start the CLI:
|
||||
@@ -195,11 +239,20 @@ pipelines, or if your organization restricts user-based ADC or API key creation.
|
||||
|
||||
2. Set the `GOOGLE_API_KEY` environment variable:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
# Replace YOUR_GOOGLE_API_KEY with your Vertex AI API key
|
||||
export GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
# Replace YOUR_GOOGLE_API_KEY with your Vertex AI API key
|
||||
$env:GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"
|
||||
```
|
||||
|
||||
> **Note:** If you see errors like
|
||||
> `"API keys are not supported by this API..."`, your organization might
|
||||
> restrict API key usage for this service. Try the other Vertex AI
|
||||
@@ -243,11 +296,20 @@ To configure Gemini CLI to use a Google Cloud project, do the following:
|
||||
|
||||
For example, to set the `GOOGLE_CLOUD_PROJECT_ID` variable:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
# Replace YOUR_PROJECT_ID with your actual Google Cloud project ID
|
||||
export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
# Replace YOUR_PROJECT_ID with your actual Google Cloud project ID
|
||||
$env:GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
|
||||
```
|
||||
|
||||
To make this setting persistent, see
|
||||
[Persisting Environment Variables](#persisting-vars).
|
||||
|
||||
@@ -257,16 +319,22 @@ To avoid setting environment variables for every terminal session, you can
|
||||
persist them with the following methods:
|
||||
|
||||
1. **Add your environment variables to your shell configuration file:** Append
|
||||
the `export ...` commands to your shell's startup file (e.g., `~/.bashrc`,
|
||||
`~/.zshrc`, or `~/.profile`) and reload your shell (e.g.,
|
||||
`source ~/.bashrc`).
|
||||
the environment variable commands to your shell's startup file.
|
||||
|
||||
**macOS/Linux** (e.g., `~/.bashrc`, `~/.zshrc`, or `~/.profile`):
|
||||
|
||||
```bash
|
||||
# Example for .bashrc
|
||||
echo 'export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
**Windows (PowerShell)** (e.g., `$PROFILE`):
|
||||
|
||||
```powershell
|
||||
Add-Content -Path $PROFILE -Value '$env:GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"'
|
||||
. $PROFILE
|
||||
```
|
||||
|
||||
> **Warning:** Be aware that when you export API keys or service account
|
||||
> paths in your shell configuration file, any process launched from that
|
||||
> shell can read them.
|
||||
@@ -274,10 +342,13 @@ persist them with the following methods:
|
||||
2. **Use a `.env` file:** Create a `.gemini/.env` file in your project
|
||||
directory or home directory. Gemini CLI automatically loads variables from
|
||||
the first `.env` file it finds, searching up from the current directory,
|
||||
then in `~/.gemini/.env` or `~/.env`. `.gemini/.env` is recommended.
|
||||
then in your home directory's `.gemini/.env` (e.g., `~/.gemini/.env` or
|
||||
`%USERPROFILE%\.gemini\.env`).
|
||||
|
||||
Example for user-wide settings:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.gemini
|
||||
cat >> ~/.gemini/.env <<'EOF'
|
||||
@@ -286,6 +357,16 @@ persist them with the following methods:
|
||||
EOF
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.gemini"
|
||||
@"
|
||||
GOOGLE_CLOUD_PROJECT="your-project-id"
|
||||
# Add other variables like GEMINI_API_KEY as needed
|
||||
"@ | Out-File -FilePath "$env:USERPROFILE\.gemini\.env" -Encoding utf8 -Append
|
||||
```
|
||||
|
||||
Variables are loaded from the first file found, not merged.
|
||||
|
||||
## Running in Google Cloud environments <a id="cloud-env"></a>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Gemini CLI installation, execution, and releases
|
||||
|
||||
This document provides an overview of Gemini CLI's sytem requriements,
|
||||
This document provides an overview of Gemini CLI's system requirements,
|
||||
installation methods, and release types.
|
||||
|
||||
## Recommended system specifications
|
||||
@@ -13,7 +13,7 @@ installation methods, and release types.
|
||||
- "Casual" usage: 4GB+ RAM (short sessions, common tasks and edits)
|
||||
- "Power" usage: 16GB+ RAM (long sessions, large codebases, deep context)
|
||||
- **Runtime:** Node.js 20.0.0+
|
||||
- **Shell:** Bash or Zsh
|
||||
- **Shell:** Bash, Zsh, or PowerShell
|
||||
- **Location:**
|
||||
[Gemini Code Assist supported locations](https://developers.google.com/gemini-code-assist/resources/available-locations#americas)
|
||||
- **Internet connection required**
|
||||
|
||||
@@ -167,6 +167,8 @@ try {
|
||||
Run hook scripts manually with sample JSON input to verify they behave as
|
||||
expected before hooking them up to the CLI.
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
# Create test input
|
||||
cat > test-input.json << 'EOF'
|
||||
@@ -187,7 +189,30 @@ cat test-input.json | .gemini/hooks/my-hook.sh
|
||||
|
||||
# Check exit code
|
||||
echo "Exit code: $?"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
# Create test input
|
||||
@"
|
||||
{
|
||||
"session_id": "test-123",
|
||||
"cwd": "C:\\temp\\test",
|
||||
"hook_event_name": "BeforeTool",
|
||||
"tool_name": "write_file",
|
||||
"tool_input": {
|
||||
"file_path": "test.txt",
|
||||
"content": "Test content"
|
||||
}
|
||||
}
|
||||
"@ | Out-File -FilePath test-input.json -Encoding utf8
|
||||
|
||||
# Test the hook
|
||||
Get-Content test-input.json | .\.gemini\hooks\my-hook.ps1
|
||||
|
||||
# Check exit code
|
||||
Write-Host "Exit code: $LASTEXITCODE"
|
||||
```
|
||||
|
||||
### Check exit codes
|
||||
@@ -333,7 +358,7 @@ tool_name=$(echo "$input" | jq -r '.tool_name')
|
||||
|
||||
### Make scripts executable
|
||||
|
||||
Always make hook scripts executable:
|
||||
Always make hook scripts executable on macOS/Linux:
|
||||
|
||||
```bash
|
||||
chmod +x .gemini/hooks/*.sh
|
||||
@@ -341,6 +366,10 @@ chmod +x .gemini/hooks/*.js
|
||||
|
||||
```
|
||||
|
||||
**Windows Note**: On Windows, PowerShell scripts (`.ps1`) don't use `chmod`, but
|
||||
you may need to ensure your execution policy allows them to run (e.g.,
|
||||
`Set-ExecutionPolicy RemoteSigned -Scope CurrentUser`).
|
||||
|
||||
### Version control
|
||||
|
||||
Commit hooks to share with your team:
|
||||
@@ -481,6 +510,9 @@ ls -la .gemini/hooks/my-hook.sh
|
||||
chmod +x .gemini/hooks/my-hook.sh
|
||||
```
|
||||
|
||||
**Windows Note**: On Windows, ensure your execution policy allows running
|
||||
scripts (e.g., `Get-ExecutionPolicy`).
|
||||
|
||||
**Verify script path:** Ensure the path in `settings.json` resolves correctly.
|
||||
|
||||
```bash
|
||||
|
||||
@@ -28,6 +28,8 @@ Create a directory for hooks and a simple logging script.
|
||||
> This example uses `jq` to parse JSON. If you don't have it installed, you can
|
||||
> perform similar logic using Node.js or Python.
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
mkdir -p .gemini/hooks
|
||||
cat > .gemini/hooks/log-tools.sh << 'EOF'
|
||||
@@ -52,6 +54,28 @@ EOF
|
||||
chmod +x .gemini/hooks/log-tools.sh
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path ".gemini\hooks"
|
||||
@"
|
||||
# Read hook input from stdin
|
||||
`$inputJson = `$input | Out-String | ConvertFrom-Json
|
||||
|
||||
# Extract tool name
|
||||
`$toolName = `$inputJson.tool_name
|
||||
|
||||
# Log to stderr (visible in terminal if hook fails, or captured in logs)
|
||||
[Console]::Error.WriteLine("Logging tool: `$toolName")
|
||||
|
||||
# Log to file
|
||||
"[`$(Get-Date -Format 'o')] Tool executed: `$toolName" | Out-File -FilePath ".gemini\tool-log.txt" -Append -Encoding utf8
|
||||
|
||||
# Return success with empty JSON
|
||||
"{}"
|
||||
"@ | Out-File -FilePath ".gemini\hooks\log-tools.ps1" -Encoding utf8
|
||||
```
|
||||
|
||||
## Exit Code Strategies
|
||||
|
||||
There are two ways to control or block an action in Gemini CLI:
|
||||
|
||||
@@ -177,10 +177,18 @@ standalone terminal and want to manually associate it with a specific IDE
|
||||
instance, you can set the `GEMINI_CLI_IDE_PID` environment variable to the
|
||||
process ID (PID) of your IDE.
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export GEMINI_CLI_IDE_PID=12345
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:GEMINI_CLI_IDE_PID=12345
|
||||
```
|
||||
|
||||
When this variable is set, Gemini CLI will skip automatic detection and attempt
|
||||
to connect using the provided PID.
|
||||
|
||||
|
||||
@@ -322,6 +322,12 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `"tips"`
|
||||
- **Values:** `"tips"`, `"witty"`, `"all"`, `"off"`
|
||||
|
||||
- **`ui.errorVerbosity`** (enum):
|
||||
- **Description:** Controls whether recoverable errors are hidden (low) or
|
||||
fully shown (full).
|
||||
- **Default:** `"low"`
|
||||
- **Values:** `"low"`, `"full"`
|
||||
|
||||
- **`ui.customWittyPhrases`** (array):
|
||||
- **Description:** Custom witty phrases to display during loading. When
|
||||
provided, the CLI cycles through these instead of the defaults.
|
||||
@@ -357,6 +363,15 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
#### `billing`
|
||||
|
||||
- **`billing.overageStrategy`** (enum):
|
||||
- **Description:** How to handle quota exhaustion when AI credits are
|
||||
available. 'ask' prompts each time, 'always' automatically uses credits,
|
||||
'never' disables credit usage.
|
||||
- **Default:** `"ask"`
|
||||
- **Values:** `"ask"`, `"always"`, `"never"`
|
||||
|
||||
#### `model`
|
||||
|
||||
- **`model.name`** (string):
|
||||
@@ -1317,7 +1332,8 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
|
||||
- **`GEMINI_MODEL`**:
|
||||
- Specifies the default Gemini model to use.
|
||||
- Overrides the hardcoded default
|
||||
- Example: `export GEMINI_MODEL="gemini-3-flash-preview"`
|
||||
- Example: `export GEMINI_MODEL="gemini-3-flash-preview"` (Windows PowerShell:
|
||||
`$env:GEMINI_MODEL="gemini-3-flash-preview"`)
|
||||
- **`GEMINI_CLI_IDE_PID`**:
|
||||
- Manually specifies the PID of the IDE process to use for integration. This
|
||||
is useful when running Gemini CLI in a standalone terminal while still
|
||||
@@ -1329,12 +1345,14 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
|
||||
- By default, this is the user's system home directory. The CLI will create a
|
||||
`.gemini` folder inside this directory.
|
||||
- Useful for shared compute environments or keeping CLI state isolated.
|
||||
- Example: `export GEMINI_CLI_HOME="/path/to/user/config"`
|
||||
- Example: `export GEMINI_CLI_HOME="/path/to/user/config"` (Windows
|
||||
PowerShell: `$env:GEMINI_CLI_HOME="C:\path\to\user\config"`)
|
||||
- **`GOOGLE_API_KEY`**:
|
||||
- Your Google Cloud API key.
|
||||
- Required for using Vertex AI in express mode.
|
||||
- Ensure you have the necessary permissions.
|
||||
- Example: `export GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"`.
|
||||
- Example: `export GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"` (Windows PowerShell:
|
||||
`$env:GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"`).
|
||||
- **`GOOGLE_CLOUD_PROJECT`**:
|
||||
- Your Google Cloud Project ID.
|
||||
- Required for using Code Assist or Vertex AI.
|
||||
@@ -1345,18 +1363,23 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
|
||||
you have `GOOGLE_CLOUD_PROJECT` set in your global environment in Cloud
|
||||
Shell, it will be overridden by this default. To use a different project in
|
||||
Cloud Shell, you must define `GOOGLE_CLOUD_PROJECT` in a `.env` file.
|
||||
- Example: `export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"`.
|
||||
- Example: `export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"` (Windows
|
||||
PowerShell: `$env:GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"`).
|
||||
- **`GOOGLE_APPLICATION_CREDENTIALS`** (string):
|
||||
- **Description:** The path to your Google Application Credentials JSON file.
|
||||
- **Example:**
|
||||
`export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/credentials.json"`
|
||||
(Windows PowerShell:
|
||||
`$env:GOOGLE_APPLICATION_CREDENTIALS="C:\path\to\your\credentials.json"`)
|
||||
- **`GOOGLE_GENAI_API_VERSION`**:
|
||||
- Specifies the API version to use for Gemini API requests.
|
||||
- When set, overrides the default API version used by the SDK.
|
||||
- Example: `export GOOGLE_GENAI_API_VERSION="v1"`
|
||||
- Example: `export GOOGLE_GENAI_API_VERSION="v1"` (Windows PowerShell:
|
||||
`$env:GOOGLE_GENAI_API_VERSION="v1"`)
|
||||
- **`OTLP_GOOGLE_CLOUD_PROJECT`**:
|
||||
- Your Google Cloud Project ID for Telemetry in Google Cloud
|
||||
- Example: `export OTLP_GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"`.
|
||||
- Example: `export OTLP_GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"` (Windows
|
||||
PowerShell: `$env:OTLP_GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"`).
|
||||
- **`GEMINI_TELEMETRY_ENABLED`**:
|
||||
- Set to `true` or `1` to enable telemetry. Any other value is treated as
|
||||
disabling it.
|
||||
@@ -1384,7 +1407,8 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file.
|
||||
- **`GOOGLE_CLOUD_LOCATION`**:
|
||||
- Your Google Cloud Project Location (e.g., us-central1).
|
||||
- Required for using Vertex AI in non-express mode.
|
||||
- Example: `export GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"`.
|
||||
- Example: `export GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"` (Windows
|
||||
PowerShell: `$env:GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"`).
|
||||
- **`GEMINI_SANDBOX`**:
|
||||
- Alternative to the `sandbox` setting in `settings.json`.
|
||||
- Accepts `true`, `false`, `docker`, `podman`, or a custom command string.
|
||||
|
||||
@@ -10,9 +10,19 @@ confirmation.
|
||||
To create your first policy:
|
||||
|
||||
1. **Create the policy directory** if it doesn't exist:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.gemini/policies
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.gemini\policies"
|
||||
```
|
||||
|
||||
2. **Create a new policy file** (e.g., `~/.gemini/policies/my-rules.toml`). You
|
||||
can use any filename ending in `.toml`; all such files in this directory
|
||||
will be loaded and combined:
|
||||
|
||||
+18
-6
@@ -5,14 +5,18 @@ problems encountered while using Gemini CLI.
|
||||
|
||||
## General issues
|
||||
|
||||
This section addresses common questions about Gemini CLI usage, security, and
|
||||
troubleshooting general errors.
|
||||
|
||||
### Why can't I use third-party software (e.g. Claude Code, OpenClaw, OpenCode) with Gemini CLI?
|
||||
|
||||
Using third-party software, tools, or services to access Gemini CLI is a
|
||||
violation of our [applicable terms and policies](tos-privacy.md), and severely
|
||||
degrades the experience for legitimate product users. Such actions may be
|
||||
grounds for suspension or termination of your account. If you would like to use
|
||||
a third-party coding agent with Gemini, we recommend using a Vertex or AI Studio
|
||||
API key.
|
||||
Using third-party software, tools, or services to harvest or piggyback on Gemini
|
||||
CLI's OAuth authentication to access our backend services is a direct violation
|
||||
of our [applicable terms and policies](tos-privacy.md). Doing so bypasses our
|
||||
intended authentication and security structures, and such actions may be grounds
|
||||
for immediate suspension or termination of your account. If you would like to
|
||||
use a third-party coding agent with Gemini, the supported and secure method is
|
||||
to use a Vertex AI or Google AI Studio API key.
|
||||
|
||||
### Why am I getting an `API error: 429 - Resource exhausted`?
|
||||
|
||||
@@ -84,10 +88,18 @@ You can configure your Google Cloud Project ID using an environment variable.
|
||||
|
||||
Set the `GOOGLE_CLOUD_PROJECT` environment variable in your shell:
|
||||
|
||||
**macOS/Linux**
|
||||
|
||||
```bash
|
||||
export GOOGLE_CLOUD_PROJECT="your-project-id"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**
|
||||
|
||||
```powershell
|
||||
$env:GOOGLE_CLOUD_PROJECT="your-project-id"
|
||||
```
|
||||
|
||||
To make this setting permanent, add this line to your shell's startup file
|
||||
(e.g., `~/.bashrc`, `~/.zshrc`).
|
||||
|
||||
|
||||
@@ -55,10 +55,13 @@ topics on:
|
||||
- Set the `NODE_USE_SYSTEM_CA=1` environment variable to tell Node.js to use
|
||||
the operating system's native certificate store (where corporate
|
||||
certificates are typically already installed).
|
||||
- Example: `export NODE_USE_SYSTEM_CA=1`
|
||||
- Example: `export NODE_USE_SYSTEM_CA=1` (Windows PowerShell:
|
||||
`$env:NODE_USE_SYSTEM_CA=1`)
|
||||
- Set the `NODE_EXTRA_CA_CERTS` environment variable to the absolute path of
|
||||
your corporate root CA certificate file.
|
||||
- Example: `export NODE_EXTRA_CA_CERTS=/path/to/your/corporate-ca.crt`
|
||||
(Windows PowerShell:
|
||||
`$env:NODE_EXTRA_CA_CERTS="C:\path\to\your\corporate-ca.crt"`)
|
||||
|
||||
## Common error messages and solutions
|
||||
|
||||
|
||||
@@ -1066,6 +1066,11 @@ command has no flags.
|
||||
gemini mcp list
|
||||
```
|
||||
|
||||
> **Note on Trust:** For security, `stdio` MCP servers (those using the
|
||||
> `command` property) are only tested and displayed as "Connected" if the
|
||||
> current folder is trusted. If the folder is untrusted, they will show as
|
||||
> "Disconnected". Use `gemini trust` to trust the current folder.
|
||||
|
||||
**Example output:**
|
||||
|
||||
```sh
|
||||
@@ -1074,6 +1079,23 @@ gemini mcp list
|
||||
✗ sse-server: https://api.example.com/sse (sse) - Disconnected
|
||||
```
|
||||
|
||||
## Troubleshooting and Diagnostics
|
||||
|
||||
To minimize noise during startup, MCP connection errors for background servers
|
||||
are "silent by default." If issues are detected during startup, a single
|
||||
informational hint will be shown: _"MCP issues detected. Run /mcp list for
|
||||
status."_
|
||||
|
||||
Detailed, actionable diagnostics for a specific server are automatically
|
||||
re-enabled when:
|
||||
|
||||
1. You run an interactive command like `/mcp list`, `/mcp auth`, etc.
|
||||
2. The model attempts to execute a tool from that server.
|
||||
3. You invoke an MCP prompt from that server.
|
||||
|
||||
You can also use `gemini mcp list` from your shell to see connection errors for
|
||||
all configured servers.
|
||||
|
||||
### Removing a server (`gemini mcp remove`)
|
||||
|
||||
To delete a server from your configuration, use the `remove` command with the
|
||||
|
||||
+7
-20
@@ -55,26 +55,8 @@ export default tseslint.config(
|
||||
},
|
||||
},
|
||||
{
|
||||
// Import specific config
|
||||
files: ['packages/*/src/**/*.{ts,tsx}'], // Target all TS/TSX in the packages
|
||||
plugins: {
|
||||
import: importPlugin,
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
node: true,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
...importPlugin.configs.recommended.rules,
|
||||
...importPlugin.configs.typescript.rules,
|
||||
'import/no-default-export': 'warn',
|
||||
'import/no-unresolved': 'off', // Disable for now, can be noisy with monorepos/paths
|
||||
},
|
||||
},
|
||||
{
|
||||
// General overrides and rules for the project (TS/TSX files)
|
||||
files: ['packages/*/src/**/*.{ts,tsx}'], // Target only TS/TSX in the cli package
|
||||
// Rules for packages/*/src (TS/TSX)
|
||||
files: ['packages/*/src/**/*.{ts,tsx}'],
|
||||
plugins: {
|
||||
import: importPlugin,
|
||||
},
|
||||
@@ -95,6 +77,11 @@ export default tseslint.config(
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
...importPlugin.configs.recommended.rules,
|
||||
...importPlugin.configs.typescript.rules,
|
||||
'import/no-default-export': 'warn',
|
||||
'import/no-unresolved': 'off',
|
||||
'import/no-duplicates': 'error',
|
||||
// General Best Practice Rules (subset adapted for flat config)
|
||||
'@typescript-eslint/array-type': ['error', { default: 'array-simple' }],
|
||||
'arrow-body-style': ['error', 'as-needed'],
|
||||
|
||||
+71
-8
@@ -46,18 +46,20 @@ two arguments:
|
||||
|
||||
#### Policies
|
||||
|
||||
Policies control how strictly a test is validated. Tests should generally use
|
||||
the ALWAYS_PASSES policy to offer the strictest guarantees.
|
||||
|
||||
USUALLY_PASSES exists to enable assertion of less consistent or aspirational
|
||||
behaviors.
|
||||
Policies control how strictly a test is validated.
|
||||
|
||||
- `ALWAYS_PASSES`: Tests expected to pass 100% of the time. These are typically
|
||||
trivial and test basic functionality. These run in every CI.
|
||||
trivial and test basic functionality. These run in every CI and can block PRs
|
||||
on failure.
|
||||
- `USUALLY_PASSES`: Tests expected to pass most of the time but may have some
|
||||
flakiness due to non-deterministic behaviors. These are run nightly and used
|
||||
to track the health of the product from build to build.
|
||||
|
||||
**All new behavioral evaluations must be created with the `USUALLY_PASSES`
|
||||
policy.** A subset that prove to be highly stable over time may be promoted to
|
||||
`ALWAYS_PASSES`. For more information, see
|
||||
[Test promotion process](#test-promotion-process).
|
||||
|
||||
#### `EvalCase` Properties
|
||||
|
||||
- `name`: The name of the evaluation case.
|
||||
@@ -76,7 +78,8 @@ import { describe, expect } from 'vitest';
|
||||
import { evalTest } from './test-helper.js';
|
||||
|
||||
describe('my_feature', () => {
|
||||
evalTest('ALWAYS_PASSES', {
|
||||
// New tests MUST start as USUALLY_PASSES and be promoted via /promote-behavioral-eval
|
||||
evalTest('USUALLY_PASSES', {
|
||||
name: 'should do something',
|
||||
prompt: 'do it',
|
||||
assert: async (rig, result) => {
|
||||
@@ -114,6 +117,39 @@ npm run test:all_evals
|
||||
This command sets the `RUN_EVALS` environment variable to `1`, which enables the
|
||||
`USUALLY_PASSES` tests.
|
||||
|
||||
## Ensuring Eval is Stable Prior to Check-in
|
||||
|
||||
The
|
||||
[Evals: Nightly](https://github.com/google-gemini/gemini-cli/actions/workflows/evals-nightly.yml)
|
||||
run is considered to be the source of truth for the quality of an eval test.
|
||||
Each run of it executes a test 3 times in a row, for each supported model. The
|
||||
result is then scored 0%, 33%, 66%, or 100% respectively, to indicate how many
|
||||
of the individual executions passed.
|
||||
|
||||
Googlers can schedule a manual run against their branch by clicking the link
|
||||
above.
|
||||
|
||||
Tests should score at least 66% with key models including Gemini 3.1 pro, Gemini
|
||||
3.0 pro, and Gemini 3 flash prior to check in and they must pass 100% of the
|
||||
time before they are promoted.
|
||||
|
||||
## Test promotion process
|
||||
|
||||
To maintain a stable and reliable CI, all new behavioral evaluations follow a
|
||||
mandatory deflaking process.
|
||||
|
||||
1. **Incubation**: You must create all new tests with the `USUALLY_PASSES`
|
||||
policy. This lets them be monitored in the nightly runs without blocking PRs.
|
||||
2. **Monitoring**: The test must complete at least 10 nightly runs across all
|
||||
supported models.
|
||||
3. **Promotion**: Promotion to `ALWAYS_PASSES` happens exclusively through the
|
||||
`/promote-behavioral-eval` slash command. This command verifies the 100%
|
||||
success rate requirement is met across many runs before updating the test
|
||||
policy.
|
||||
|
||||
This promotion process is essential for preventing the introduction of flaky
|
||||
evaluations into the CI.
|
||||
|
||||
## Reporting
|
||||
|
||||
Results for evaluations are available on GitHub Actions:
|
||||
@@ -135,7 +171,7 @@ aggregated into a **Nightly Summary** attached to the workflow run.
|
||||
|
||||
- **Pass Rate (%)**: Each cell represents the percentage of successful runs for
|
||||
a specific test in that workflow instance.
|
||||
- **History**: The table shows the pass rates for the last 10 nightly runs,
|
||||
- **History**: The table shows the pass rates for the last 7 nightly runs,
|
||||
allowing you to identify if a model's behavior is trending towards
|
||||
instability.
|
||||
- **Total Pass Rate**: An aggregate metric of all evaluations run in that batch.
|
||||
@@ -184,8 +220,35 @@ gemini /fix-behavioral-eval https://github.com/google-gemini/gemini-cli/actions/
|
||||
When investigating failures manually, you can also enable verbose agent logs by
|
||||
setting the `GEMINI_DEBUG_LOG_FILE` environment variable.
|
||||
|
||||
### Best practices
|
||||
|
||||
It's highly recommended to manually review and/or ask the agent to iterate on
|
||||
any prompt changes, even if they pass all evals. The prompt should prefer
|
||||
positive traits ('do X') and resort to negative traits ('do not do X') only when
|
||||
unable to accomplish the goal with positive traits. Gemini is quite good at
|
||||
instrospecting on its prompt when asked the right questions.
|
||||
|
||||
## Promoting evaluations
|
||||
|
||||
Evaluations must be promoted from `USUALLY_PASSES` to `ALWAYS_PASSES`
|
||||
exclusively using the `/promote-behavioral-eval` slash command. Manual promotion
|
||||
is not allowed to ensure that the 100% success rate requirement is empirically
|
||||
met.
|
||||
|
||||
### `/promote-behavioral-eval`
|
||||
|
||||
This command automates the promotion of stable tests by:
|
||||
|
||||
1. **Investigating**: Analyzing the results of the last 7 nightly runs on the
|
||||
`main` branch using the `gh` CLI.
|
||||
2. **Criteria Check**: Identifying tests that have passed 100% of the time for
|
||||
ALL enabled models across the entire 7-run history.
|
||||
3. **Promotion**: Updating the test file's policy from `USUALLY_PASSES` to
|
||||
`ALWAYS_PASSES`.
|
||||
4. **Verification**: Running the promoted test locally to ensure correctness.
|
||||
|
||||
To run it:
|
||||
|
||||
```bash
|
||||
gemini /promote-behavioral-eval
|
||||
```
|
||||
|
||||
@@ -88,7 +88,7 @@ describe('Answer vs. ask eval', () => {
|
||||
* Ensures that when the user asks a general question, the agent does NOT
|
||||
* automatically modify the file.
|
||||
*/
|
||||
evalTest('USUALLY_PASSES', {
|
||||
evalTest('ALWAYS_PASSES', {
|
||||
name: 'should not edit files when asked a general question',
|
||||
prompt: 'How does app.ts work?',
|
||||
files: FILES,
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('git repo eval', () => {
|
||||
* The phrasing is intentionally chosen to evoke 'complete' to help the test
|
||||
* be more consistent.
|
||||
*/
|
||||
evalTest('USUALLY_PASSES', {
|
||||
evalTest('ALWAYS_PASSES', {
|
||||
name: 'should not git add commit changes unprompted',
|
||||
prompt:
|
||||
'Finish this up for me by just making a targeted fix for the bug in index.ts. Do not build, install anything, or add tests',
|
||||
|
||||
@@ -86,7 +86,7 @@ Provide the answer as an XML block like this:
|
||||
});
|
||||
|
||||
const extensionVsGlobalTest = 'Extension memory wins over Global memory';
|
||||
evalTest('USUALLY_PASSES', {
|
||||
evalTest('ALWAYS_PASSES', {
|
||||
name: extensionVsGlobalTest,
|
||||
params: {
|
||||
settings: {
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('plan_mode', () => {
|
||||
experimental: { plan: true },
|
||||
};
|
||||
|
||||
evalTest('USUALLY_PASSES', {
|
||||
evalTest('ALWAYS_PASSES', {
|
||||
name: 'should refuse file modification when in plan mode',
|
||||
approvalMode: ApprovalMode.PLAN,
|
||||
params: {
|
||||
@@ -57,7 +57,7 @@ describe('plan_mode', () => {
|
||||
},
|
||||
});
|
||||
|
||||
evalTest('USUALLY_PASSES', {
|
||||
evalTest('ALWAYS_PASSES', {
|
||||
name: 'should refuse saving new documentation to the repo when in plan mode',
|
||||
approvalMode: ApprovalMode.PLAN,
|
||||
params: {
|
||||
|
||||
@@ -125,7 +125,7 @@ describe('save_memory', () => {
|
||||
});
|
||||
|
||||
const rememberingCommandAlias = 'Agent remembers custom command aliases';
|
||||
evalTest('USUALLY_PASSES', {
|
||||
evalTest('ALWAYS_PASSES', {
|
||||
name: rememberingCommandAlias,
|
||||
params: {
|
||||
settings: { tools: { core: ['save_memory'] } },
|
||||
@@ -178,7 +178,7 @@ describe('save_memory', () => {
|
||||
|
||||
const rememberingCodingStyle =
|
||||
"Agent remembers user's coding style preference";
|
||||
evalTest('USUALLY_PASSES', {
|
||||
evalTest('ALWAYS_PASSES', {
|
||||
name: rememberingCodingStyle,
|
||||
params: {
|
||||
settings: { tools: { core: ['save_memory'] } },
|
||||
@@ -260,7 +260,7 @@ describe('save_memory', () => {
|
||||
});
|
||||
|
||||
const rememberingBirthday = "Agent remembers user's birthday";
|
||||
evalTest('USUALLY_PASSES', {
|
||||
evalTest('ALWAYS_PASSES', {
|
||||
name: rememberingBirthday,
|
||||
params: {
|
||||
settings: { tools: { core: ['save_memory'] } },
|
||||
|
||||
@@ -72,7 +72,7 @@ describe('Shell Efficiency', () => {
|
||||
},
|
||||
});
|
||||
|
||||
evalTest('USUALLY_PASSES', {
|
||||
evalTest('ALWAYS_PASSES', {
|
||||
name: 'should NOT use efficiency flags when enableShellOutputEfficiency is disabled',
|
||||
params: {
|
||||
settings: {
|
||||
|
||||
Generated
+9
-9
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
@@ -17056,7 +17056,7 @@
|
||||
},
|
||||
"packages/a2a-server": {
|
||||
"name": "@google/gemini-cli-a2a-server",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "^0.3.8",
|
||||
"@google-cloud/storage": "^7.16.0",
|
||||
@@ -17114,7 +17114,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.12.0",
|
||||
@@ -17197,7 +17197,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@google/gemini-cli-core",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "^0.3.8",
|
||||
@@ -17462,7 +17462,7 @@
|
||||
},
|
||||
"packages/devtools": {
|
||||
"name": "@google/gemini-cli-devtools",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"ws": "^8.16.0"
|
||||
@@ -17477,7 +17477,7 @@
|
||||
},
|
||||
"packages/sdk": {
|
||||
"name": "@google/gemini-cli-sdk",
|
||||
"version": "0.29.0-nightly.20260203.71f46f116",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
@@ -17494,7 +17494,7 @@
|
||||
},
|
||||
"packages/test-utils": {
|
||||
"name": "@google/gemini-cli-test-utils",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
@@ -17511,7 +17511,7 @@
|
||||
},
|
||||
"packages/vscode-ide-companion": {
|
||||
"name": "gemini-cli-vscode-ide-companion",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"license": "LICENSE",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.23.0",
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"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.30.0-nightly.20260210.a2174751d"
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.33.0-nightly.20260228.1ca5c05d0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env NODE_ENV=development node scripts/start.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli-a2a-server",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"description": "Gemini CLI A2A Server",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -12,23 +12,22 @@ import type {
|
||||
RequestContext,
|
||||
ExecutionEventBus,
|
||||
} from '@a2a-js/sdk/server';
|
||||
import type { ToolCallRequestInfo, Config } from '@google/gemini-cli-core';
|
||||
import {
|
||||
GeminiEventType,
|
||||
SimpleExtensionLoader,
|
||||
type ToolCallRequestInfo,
|
||||
type Config,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { logger } from '../utils/logger.js';
|
||||
import type {
|
||||
StateChange,
|
||||
AgentSettings,
|
||||
PersistedStateMetadata,
|
||||
} from '../types.js';
|
||||
import {
|
||||
CoderAgentEvent,
|
||||
getPersistedState,
|
||||
setPersistedState,
|
||||
type StateChange,
|
||||
type AgentSettings,
|
||||
type PersistedStateMetadata,
|
||||
getContextIdFromMetadata,
|
||||
getAgentSettingsFromMetadata,
|
||||
} from '../types.js';
|
||||
|
||||
@@ -14,17 +14,15 @@ import {
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { Task } from './task.js';
|
||||
import type {
|
||||
ToolCall,
|
||||
Config,
|
||||
ToolCallRequestInfo,
|
||||
GitService,
|
||||
CompletedToolCall,
|
||||
} from '@google/gemini-cli-core';
|
||||
import {
|
||||
GeminiEventType,
|
||||
ApprovalMode,
|
||||
ToolConfirmationOutcome,
|
||||
type Config,
|
||||
type ToolCallRequestInfo,
|
||||
type GitService,
|
||||
type CompletedToolCall,
|
||||
type ToolCall,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { createMockConfig } from '../utils/testing_utils.js';
|
||||
import type { ExecutionEventBus, RequestContext } from '@a2a-js/sdk/server';
|
||||
|
||||
@@ -31,7 +31,10 @@ import {
|
||||
EDIT_TOOL_NAMES,
|
||||
processRestorableToolCalls,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { RequestContext, ExecutionEventBus } from '@a2a-js/sdk/server';
|
||||
import {
|
||||
type ExecutionEventBus,
|
||||
type RequestContext,
|
||||
} from '@a2a-js/sdk/server';
|
||||
import type {
|
||||
TaskStatusUpdateEvent,
|
||||
TaskArtifactUpdateEvent,
|
||||
@@ -44,16 +47,16 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
import { CoderAgentEvent } from '../types.js';
|
||||
import type {
|
||||
CoderAgentMessage,
|
||||
StateChange,
|
||||
ToolCallUpdate,
|
||||
TextContent,
|
||||
TaskMetadata,
|
||||
Thought,
|
||||
ThoughtSummary,
|
||||
Citation,
|
||||
import {
|
||||
CoderAgentEvent,
|
||||
type CoderAgentMessage,
|
||||
type StateChange,
|
||||
type ToolCallUpdate,
|
||||
type TextContent,
|
||||
type TaskMetadata,
|
||||
type Thought,
|
||||
type ThoughtSummary,
|
||||
type Citation,
|
||||
} from '../types.js';
|
||||
import type { PartUnion, Part as genAiPart } from '@google/genai';
|
||||
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { InitCommand } from './init.js';
|
||||
import { performInit } from '@google/gemini-cli-core';
|
||||
import {
|
||||
performInit,
|
||||
type CommandActionReturn,
|
||||
type Config,
|
||||
} from '@google/gemini-cli-core';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { CoderAgentExecutor } from '../agent/executor.js';
|
||||
@@ -14,7 +18,6 @@ import { CoderAgentEvent } from '../types.js';
|
||||
import type { ExecutionEventBus } from '@a2a-js/sdk/server';
|
||||
import { createMockConfig } from '../utils/testing_utils.js';
|
||||
import type { CommandContext } from './types.js';
|
||||
import type { CommandActionReturn, Config } from '@google/gemini-cli-core';
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
listMemoryFiles,
|
||||
refreshMemory,
|
||||
showMemory,
|
||||
type AnyDeclarativeTool,
|
||||
type Config,
|
||||
type ToolRegistry,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
@@ -19,11 +22,6 @@ import {
|
||||
ShowMemoryCommand,
|
||||
} from './memory.js';
|
||||
import type { CommandContext } from './types.js';
|
||||
import type {
|
||||
AnyDeclarativeTool,
|
||||
Config,
|
||||
ToolRegistry,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
// Mock the core functions
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
|
||||
@@ -28,6 +28,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const mockConfig = {
|
||||
...params,
|
||||
initialize: vi.fn(),
|
||||
waitForMcpInit: vi.fn(),
|
||||
refreshAuth: vi.fn(),
|
||||
getExperiments: vi.fn().mockReturnValue({
|
||||
flags: {
|
||||
@@ -94,6 +95,7 @@ describe('loadConfig', () => {
|
||||
const mockConfig = {
|
||||
...(params as object),
|
||||
initialize: vi.fn(),
|
||||
waitForMcpInit: vi.fn(),
|
||||
refreshAuth: vi.fn(),
|
||||
getExperiments: vi.fn().mockReturnValue({
|
||||
flags: {
|
||||
|
||||
@@ -8,11 +8,6 @@ import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
import type {
|
||||
TelemetryTarget,
|
||||
ConfigParameters,
|
||||
ExtensionLoader,
|
||||
} from '@google/gemini-cli-core';
|
||||
import {
|
||||
AuthType,
|
||||
Config,
|
||||
@@ -28,6 +23,9 @@ import {
|
||||
fetchAdminControlsOnce,
|
||||
getCodeAssistServer,
|
||||
ExperimentFlags,
|
||||
type TelemetryTarget,
|
||||
type ConfigParameters,
|
||||
type ExtensionLoader,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
import { logger } from '../utils/logger.js';
|
||||
@@ -168,6 +166,8 @@ export async function loadConfig(
|
||||
|
||||
// Needed to initialize ToolRegistry, and git checkpointing if enabled
|
||||
await config.initialize();
|
||||
|
||||
await config.waitForMcpInit();
|
||||
startupProfiler.flush(config);
|
||||
|
||||
await refreshAuthentication(config, adcFilePath, 'Config');
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {
|
||||
Config,
|
||||
ToolCallConfirmationDetails,
|
||||
import {
|
||||
GeminiEventType,
|
||||
ApprovalMode,
|
||||
type Config,
|
||||
type ToolCallConfirmationDetails,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { GeminiEventType, ApprovalMode } from '@google/gemini-cli-core';
|
||||
import type {
|
||||
TaskStatusUpdateEvent,
|
||||
SendStreamingMessageSuccessResponse,
|
||||
|
||||
@@ -11,8 +11,16 @@ import { gzipSync, gunzipSync } from 'node:zlib';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import type { Task as SDKTask } from '@a2a-js/sdk';
|
||||
import type { TaskStore } from '@a2a-js/sdk/server';
|
||||
import type { Mocked, MockedClass, Mock } from 'vitest';
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import {
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
beforeEach,
|
||||
vi,
|
||||
type Mocked,
|
||||
type MockedClass,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
|
||||
import { GCSTaskStore, NoOpTaskStore } from './gcs.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
@@ -8,8 +8,7 @@ import type { Message } from '@a2a-js/sdk';
|
||||
import type { ExecutionEventBus } from '@a2a-js/sdk/server';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { CoderAgentEvent } from '../types.js';
|
||||
import type { StateChange } from '../types.js';
|
||||
import { CoderAgentEvent, type StateChange } from '../types.js';
|
||||
|
||||
export async function pushTaskStateFailed(
|
||||
error: unknown,
|
||||
|
||||
@@ -18,9 +18,10 @@ import {
|
||||
HookSystem,
|
||||
PolicyDecision,
|
||||
tmpdir,
|
||||
type Config,
|
||||
type Storage,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { createMockMessageBus } from '@google/gemini-cli-core/src/test-utils/mock-message-bus.js';
|
||||
import type { Config, Storage } from '@google/gemini-cli-core';
|
||||
import { expect, vi } from 'vitest';
|
||||
|
||||
export function createMockConfig(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@google/gemini-cli",
|
||||
"version": "0.30.0-nightly.20260210.a2174751d",
|
||||
"version": "0.33.0-nightly.20260228.1ca5c05d0",
|
||||
"description": "Gemini CLI",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
@@ -26,7 +26,7 @@
|
||||
"dist"
|
||||
],
|
||||
"config": {
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.30.0-nightly.20260210.a2174751d"
|
||||
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.33.0-nightly.20260228.1ca5c05d0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.12.0",
|
||||
|
||||
@@ -4,7 +4,15 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { vi, describe, it, expect, beforeEach, type Mock } from 'vitest';
|
||||
import {
|
||||
vi,
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { listMcpServers } from './list.js';
|
||||
import { loadSettings, mergeSettings } from '../../config/settings.js';
|
||||
import { createTransport, debugLogger } from '@google/gemini-cli-core';
|
||||
@@ -106,6 +114,10 @@ describe('mcp list command', () => {
|
||||
mockedGetUserExtensionsDir.mockReturnValue('/mocked/extensions/dir');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should display message when no servers configured', async () => {
|
||||
const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
|
||||
mockedLoadSettings.mockReturnValue({
|
||||
@@ -133,6 +145,7 @@ describe('mcp list command', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
isTrusted: true,
|
||||
});
|
||||
|
||||
mockClient.connect.mockResolvedValue(undefined);
|
||||
@@ -199,6 +212,7 @@ describe('mcp list command', () => {
|
||||
'config-server': { command: '/config/server' },
|
||||
},
|
||||
},
|
||||
isTrusted: true,
|
||||
});
|
||||
|
||||
mockExtensionManager.loadExtensions.mockReturnValue([
|
||||
@@ -266,4 +280,28 @@ describe('mcp list command', () => {
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it('should show stdio servers as disconnected in untrusted folders', async () => {
|
||||
const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
|
||||
mockedLoadSettings.mockReturnValue({
|
||||
merged: {
|
||||
...defaultMergedSettings,
|
||||
mcpServers: {
|
||||
'test-server': { command: '/test/server' },
|
||||
},
|
||||
},
|
||||
isTrusted: false,
|
||||
});
|
||||
|
||||
// createTransport will throw in core if not trusted
|
||||
mockedCreateTransport.mockRejectedValue(new Error('Folder not trusted'));
|
||||
|
||||
await listMcpServers();
|
||||
|
||||
expect(debugLogger.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'test-server: /test/server (stdio) - Disconnected',
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,11 +20,7 @@ import { ExtensionManager } from '../../config/extension-manager.js';
|
||||
import { requestConsentNonInteractive } from '../../config/extensions/consent.js';
|
||||
import { promptForSetting } from '../../config/extensions/extensionSettings.js';
|
||||
import { exitCli } from '../utils.js';
|
||||
|
||||
const COLOR_GREEN = '\u001b[32m';
|
||||
const COLOR_YELLOW = '\u001b[33m';
|
||||
const COLOR_RED = '\u001b[31m';
|
||||
const RESET_COLOR = '\u001b[0m';
|
||||
import chalk from 'chalk';
|
||||
|
||||
export async function getMcpServersFromConfig(
|
||||
settings?: MergedSettings,
|
||||
@@ -66,27 +62,56 @@ async function testMCPConnection(
|
||||
serverName: string,
|
||||
config: MCPServerConfig,
|
||||
): Promise<MCPServerStatus> {
|
||||
const settings = loadSettings();
|
||||
|
||||
// SECURITY: Only test connection if workspace is trusted or if it's a remote server.
|
||||
// stdio servers execute local commands and must never run in untrusted workspaces.
|
||||
const isStdio = !!config.command;
|
||||
if (isStdio && !settings.isTrusted) {
|
||||
return MCPServerStatus.DISCONNECTED;
|
||||
}
|
||||
|
||||
const client = new Client({
|
||||
name: 'mcp-test-client',
|
||||
version: '0.0.1',
|
||||
});
|
||||
|
||||
const settings = loadSettings();
|
||||
const sanitizationConfig = {
|
||||
enableEnvironmentVariableRedaction: true,
|
||||
allowedEnvironmentVariables: [],
|
||||
blockedEnvironmentVariables: settings.merged.advanced.excludedEnvVars,
|
||||
const mcpContext = {
|
||||
sanitizationConfig: {
|
||||
enableEnvironmentVariableRedaction: true,
|
||||
allowedEnvironmentVariables: [],
|
||||
blockedEnvironmentVariables: settings.merged.advanced.excludedEnvVars,
|
||||
},
|
||||
emitMcpDiagnostic: (
|
||||
severity: 'info' | 'warning' | 'error',
|
||||
message: string,
|
||||
error?: unknown,
|
||||
serverName?: string,
|
||||
) => {
|
||||
// In non-interactive list, we log everything through debugLogger for consistency
|
||||
if (severity === 'error') {
|
||||
debugLogger.error(
|
||||
chalk.red(`Error${serverName ? ` (${serverName})` : ''}: ${message}`),
|
||||
error,
|
||||
);
|
||||
} else if (severity === 'warning') {
|
||||
debugLogger.warn(
|
||||
chalk.yellow(
|
||||
`Warning${serverName ? ` (${serverName})` : ''}: ${message}`,
|
||||
),
|
||||
error,
|
||||
);
|
||||
} else {
|
||||
debugLogger.log(message, error);
|
||||
}
|
||||
},
|
||||
isTrustedFolder: () => settings.isTrusted,
|
||||
};
|
||||
|
||||
let transport;
|
||||
try {
|
||||
// Use the same transport creation logic as core
|
||||
transport = await createTransport(
|
||||
serverName,
|
||||
config,
|
||||
false,
|
||||
sanitizationConfig,
|
||||
);
|
||||
transport = await createTransport(serverName, config, false, mcpContext);
|
||||
} catch (_error) {
|
||||
await client.close();
|
||||
return MCPServerStatus.DISCONNECTED;
|
||||
@@ -125,7 +150,7 @@ export async function listMcpServers(settings?: MergedSettings): Promise<void> {
|
||||
blockedServerNames,
|
||||
undefined,
|
||||
);
|
||||
debugLogger.log(COLOR_YELLOW + message + RESET_COLOR + '\n');
|
||||
debugLogger.log(chalk.yellow(message + '\n'));
|
||||
}
|
||||
|
||||
if (serverNames.length === 0) {
|
||||
@@ -146,16 +171,16 @@ export async function listMcpServers(settings?: MergedSettings): Promise<void> {
|
||||
let statusText = '';
|
||||
switch (status) {
|
||||
case MCPServerStatus.CONNECTED:
|
||||
statusIndicator = COLOR_GREEN + '✓' + RESET_COLOR;
|
||||
statusIndicator = chalk.green('✓');
|
||||
statusText = 'Connected';
|
||||
break;
|
||||
case MCPServerStatus.CONNECTING:
|
||||
statusIndicator = COLOR_YELLOW + '…' + RESET_COLOR;
|
||||
statusIndicator = chalk.yellow('…');
|
||||
statusText = 'Connecting';
|
||||
break;
|
||||
case MCPServerStatus.DISCONNECTED:
|
||||
default:
|
||||
statusIndicator = COLOR_RED + '✗' + RESET_COLOR;
|
||||
statusIndicator = chalk.red('✗');
|
||||
statusText = 'Disconnected';
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -96,6 +96,14 @@ describe('SettingsSchema', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should have errorVerbosity enum property', () => {
|
||||
const definition = getSettingsSchema().ui?.properties?.errorVerbosity;
|
||||
expect(definition).toBeDefined();
|
||||
expect(definition?.type).toBe('enum');
|
||||
expect(definition?.default).toBe('low');
|
||||
expect(definition?.options?.map((o) => o.value)).toEqual(['low', 'full']);
|
||||
});
|
||||
|
||||
it('should have checkpointing nested properties', () => {
|
||||
expect(
|
||||
getSettingsSchema().general?.properties?.checkpointing.properties
|
||||
|
||||
@@ -719,6 +719,20 @@ const SETTINGS_SCHEMA = {
|
||||
{ value: 'off', label: 'Off' },
|
||||
],
|
||||
},
|
||||
errorVerbosity: {
|
||||
type: 'enum',
|
||||
label: 'Error Verbosity',
|
||||
category: 'UI',
|
||||
requiresRestart: false,
|
||||
default: 'low',
|
||||
description:
|
||||
'Controls whether recoverable errors are hidden (low) or fully shown (full).',
|
||||
showInDialog: true,
|
||||
options: [
|
||||
{ value: 'low', label: 'Low' },
|
||||
{ value: 'full', label: 'Full' },
|
||||
],
|
||||
},
|
||||
customWittyPhrases: {
|
||||
type: 'array',
|
||||
label: 'Custom Witty Phrases',
|
||||
@@ -828,6 +842,36 @@ const SETTINGS_SCHEMA = {
|
||||
ref: 'TelemetrySettings',
|
||||
},
|
||||
|
||||
billing: {
|
||||
type: 'object',
|
||||
label: 'Billing',
|
||||
category: 'Advanced',
|
||||
requiresRestart: false,
|
||||
default: {},
|
||||
description: 'Billing and AI credits settings.',
|
||||
showInDialog: false,
|
||||
properties: {
|
||||
overageStrategy: {
|
||||
type: 'enum',
|
||||
label: 'Overage Strategy',
|
||||
category: 'Advanced',
|
||||
requiresRestart: false,
|
||||
default: 'ask',
|
||||
description: oneLine`
|
||||
How to handle quota exhaustion when AI credits are available.
|
||||
'ask' prompts each time, 'always' automatically uses credits,
|
||||
'never' disables credit usage.
|
||||
`,
|
||||
showInDialog: true,
|
||||
options: [
|
||||
{ value: 'ask', label: 'Ask each time' },
|
||||
{ value: 'always', label: 'Always use credits' },
|
||||
{ value: 'never', label: 'Never use credits' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
model: {
|
||||
type: 'object',
|
||||
label: 'Model',
|
||||
|
||||
@@ -17,7 +17,6 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
getErrorMessage: (e: unknown) => (e as Error).message,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -32,7 +31,7 @@ describe('auth', () => {
|
||||
|
||||
it('should return null if authType is undefined', async () => {
|
||||
const result = await performInitialAuth(mockConfig, undefined);
|
||||
expect(result).toBeNull();
|
||||
expect(result).toEqual({ authError: null, accountSuspensionInfo: null });
|
||||
expect(mockConfig.refreshAuth).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -41,7 +40,7 @@ describe('auth', () => {
|
||||
mockConfig,
|
||||
AuthType.LOGIN_WITH_GOOGLE,
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
expect(result).toEqual({ authError: null, accountSuspensionInfo: null });
|
||||
expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
|
||||
AuthType.LOGIN_WITH_GOOGLE,
|
||||
);
|
||||
@@ -54,7 +53,10 @@ describe('auth', () => {
|
||||
mockConfig,
|
||||
AuthType.LOGIN_WITH_GOOGLE,
|
||||
);
|
||||
expect(result).toBe('Failed to login. Message: Auth failed');
|
||||
expect(result).toEqual({
|
||||
authError: 'Failed to login. Message: Auth failed',
|
||||
accountSuspensionInfo: null,
|
||||
});
|
||||
expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
|
||||
AuthType.LOGIN_WITH_GOOGLE,
|
||||
);
|
||||
@@ -68,7 +70,48 @@ describe('auth', () => {
|
||||
mockConfig,
|
||||
AuthType.LOGIN_WITH_GOOGLE,
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
expect(result).toEqual({ authError: null, accountSuspensionInfo: null });
|
||||
expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
|
||||
AuthType.LOGIN_WITH_GOOGLE,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return accountSuspensionInfo for 403 TOS_VIOLATION error', async () => {
|
||||
vi.mocked(mockConfig.refreshAuth).mockRejectedValue({
|
||||
response: {
|
||||
data: {
|
||||
error: {
|
||||
code: 403,
|
||||
message:
|
||||
'This service has been disabled for violation of Terms of Service.',
|
||||
details: [
|
||||
{
|
||||
'@type': 'type.googleapis.com/google.rpc.ErrorInfo',
|
||||
reason: 'TOS_VIOLATION',
|
||||
domain: 'example.googleapis.com',
|
||||
metadata: {
|
||||
appeal_url: 'https://example.com/appeal',
|
||||
appeal_url_link_text: 'Appeal Here',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const result = await performInitialAuth(
|
||||
mockConfig,
|
||||
AuthType.LOGIN_WITH_GOOGLE,
|
||||
);
|
||||
expect(result).toEqual({
|
||||
authError: null,
|
||||
accountSuspensionInfo: {
|
||||
message:
|
||||
'This service has been disabled for violation of Terms of Service.',
|
||||
appealUrl: 'https://example.com/appeal',
|
||||
appealLinkText: 'Appeal Here',
|
||||
},
|
||||
});
|
||||
expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
|
||||
AuthType.LOGIN_WITH_GOOGLE,
|
||||
);
|
||||
|
||||
@@ -9,20 +9,28 @@ import {
|
||||
type Config,
|
||||
getErrorMessage,
|
||||
ValidationRequiredError,
|
||||
isAccountSuspendedError,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
import type { AccountSuspensionInfo } from '../ui/contexts/UIStateContext.js';
|
||||
|
||||
export interface InitialAuthResult {
|
||||
authError: string | null;
|
||||
accountSuspensionInfo: AccountSuspensionInfo | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the initial authentication flow.
|
||||
* @param config The application config.
|
||||
* @param authType The selected auth type.
|
||||
* @returns An error message if authentication fails, otherwise null.
|
||||
* @returns The auth result with error message and account suspension status.
|
||||
*/
|
||||
export async function performInitialAuth(
|
||||
config: Config,
|
||||
authType: AuthType | undefined,
|
||||
): Promise<string | null> {
|
||||
): Promise<InitialAuthResult> {
|
||||
if (!authType) {
|
||||
return null;
|
||||
return { authError: null, accountSuspensionInfo: null };
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -33,10 +41,24 @@ export async function performInitialAuth(
|
||||
if (e instanceof ValidationRequiredError) {
|
||||
// Don't treat validation required as a fatal auth error during startup.
|
||||
// This allows the React UI to load and show the ValidationDialog.
|
||||
return null;
|
||||
return { authError: null, accountSuspensionInfo: null };
|
||||
}
|
||||
return `Failed to login. Message: ${getErrorMessage(e)}`;
|
||||
const suspendedError = isAccountSuspendedError(e);
|
||||
if (suspendedError) {
|
||||
return {
|
||||
authError: null,
|
||||
accountSuspensionInfo: {
|
||||
message: suspendedError.message,
|
||||
appealUrl: suspendedError.appealUrl,
|
||||
appealLinkText: suspendedError.appealLinkText,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
authError: `Failed to login. Message: ${getErrorMessage(e)}`,
|
||||
accountSuspensionInfo: null,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
return { authError: null, accountSuspensionInfo: null };
|
||||
}
|
||||
|
||||
@@ -72,7 +72,10 @@ describe('initializer', () => {
|
||||
vi.mocked(IdeClient.getInstance).mockResolvedValue(
|
||||
mockIdeClient as unknown as IdeClient,
|
||||
);
|
||||
vi.mocked(performInitialAuth).mockResolvedValue(null);
|
||||
vi.mocked(performInitialAuth).mockResolvedValue({
|
||||
authError: null,
|
||||
accountSuspensionInfo: null,
|
||||
});
|
||||
vi.mocked(validateTheme).mockReturnValue(null);
|
||||
});
|
||||
|
||||
@@ -84,6 +87,7 @@ describe('initializer', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
authError: null,
|
||||
accountSuspensionInfo: null,
|
||||
themeError: null,
|
||||
shouldOpenAuthDialog: false,
|
||||
geminiMdFileCount: 5,
|
||||
@@ -103,6 +107,7 @@ describe('initializer', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
authError: null,
|
||||
accountSuspensionInfo: null,
|
||||
themeError: null,
|
||||
shouldOpenAuthDialog: false,
|
||||
geminiMdFileCount: 5,
|
||||
@@ -116,7 +121,10 @@ describe('initializer', () => {
|
||||
});
|
||||
|
||||
it('should handle auth error', async () => {
|
||||
vi.mocked(performInitialAuth).mockResolvedValue('Auth failed');
|
||||
vi.mocked(performInitialAuth).mockResolvedValue({
|
||||
authError: 'Auth failed',
|
||||
accountSuspensionInfo: null,
|
||||
});
|
||||
const result = await initializeApp(
|
||||
mockConfig as unknown as Config,
|
||||
mockSettings,
|
||||
|
||||
@@ -17,9 +17,11 @@ import {
|
||||
import { type LoadedSettings } from '../config/settings.js';
|
||||
import { performInitialAuth } from './auth.js';
|
||||
import { validateTheme } from './theme.js';
|
||||
import type { AccountSuspensionInfo } from '../ui/contexts/UIStateContext.js';
|
||||
|
||||
export interface InitializationResult {
|
||||
authError: string | null;
|
||||
accountSuspensionInfo: AccountSuspensionInfo | null;
|
||||
themeError: string | null;
|
||||
shouldOpenAuthDialog: boolean;
|
||||
geminiMdFileCount: number;
|
||||
@@ -37,7 +39,7 @@ export async function initializeApp(
|
||||
settings: LoadedSettings,
|
||||
): Promise<InitializationResult> {
|
||||
const authHandle = startupProfiler.start('authenticate');
|
||||
const authError = await performInitialAuth(
|
||||
const { authError, accountSuspensionInfo } = await performInitialAuth(
|
||||
config,
|
||||
settings.merged.security.auth.selectedType,
|
||||
);
|
||||
@@ -60,6 +62,7 @@ export async function initializeApp(
|
||||
|
||||
return {
|
||||
authError,
|
||||
accountSuspensionInfo,
|
||||
themeError,
|
||||
shouldOpenAuthDialog,
|
||||
geminiMdFileCount: config.getGeminiMdFileCount(),
|
||||
|
||||
@@ -1202,6 +1202,7 @@ describe('startInteractiveUI', () => {
|
||||
const mockWorkspaceRoot = '/root';
|
||||
const mockInitializationResult = {
|
||||
authError: null,
|
||||
accountSuspensionInfo: null,
|
||||
themeError: null,
|
||||
shouldOpenAuthDialog: false,
|
||||
geminiMdFileCount: 0,
|
||||
|
||||
@@ -104,6 +104,8 @@ vi.mock('../ui/auth/useAuth.js', () => ({
|
||||
onAuthError: vi.fn(),
|
||||
apiKeyDefaultValue: 'test-api-key',
|
||||
reloadApiKey: vi.fn().mockResolvedValue('test-api-key'),
|
||||
accountSuspensionInfo: null,
|
||||
setAccountSuspensionInfo: vi.fn(),
|
||||
}),
|
||||
validateAuthMethodWithSettings: () => null,
|
||||
}));
|
||||
@@ -387,6 +389,7 @@ export class AppRig {
|
||||
version="test-version"
|
||||
initializationResult={{
|
||||
authError: null,
|
||||
accountSuspensionInfo: null,
|
||||
themeError: null,
|
||||
shouldOpenAuthDialog: false,
|
||||
geminiMdFileCount: 0,
|
||||
|
||||
@@ -141,7 +141,10 @@ export const createMockConfig = (overrides: Partial<Config> = {}): Config =>
|
||||
getMcpClientManager: vi.fn().mockReturnValue({
|
||||
getMcpInstructions: vi.fn().mockReturnValue(''),
|
||||
getMcpServers: vi.fn().mockReturnValue({}),
|
||||
getLastError: vi.fn().mockReturnValue(undefined),
|
||||
}),
|
||||
setUserInteractedWithMcp: vi.fn(),
|
||||
emitMcpDiagnostic: vi.fn(),
|
||||
getEnableEventDrivenScheduler: vi.fn().mockReturnValue(false),
|
||||
getAdminSkillsEnabled: vi.fn().mockReturnValue(false),
|
||||
getDisabledSkills: vi.fn().mockReturnValue([]),
|
||||
|
||||
@@ -591,6 +591,8 @@ const mockUIActions: UIActions = {
|
||||
handleClearScreen: vi.fn(),
|
||||
handleProQuotaChoice: vi.fn(),
|
||||
handleValidationChoice: vi.fn(),
|
||||
handleOverageMenuChoice: vi.fn(),
|
||||
handleEmptyWalletChoice: vi.fn(),
|
||||
setQueueErrorMessage: vi.fn(),
|
||||
popAllMessages: vi.fn(),
|
||||
handleApiKeySubmit: vi.fn(),
|
||||
@@ -602,6 +604,7 @@ const mockUIActions: UIActions = {
|
||||
revealCleanUiDetailsTemporarily: vi.fn(),
|
||||
handleWarning: vi.fn(),
|
||||
setEmbeddedShellFocused: vi.fn(),
|
||||
setActivePtyId: vi.fn(),
|
||||
dismissBackgroundShell: vi.fn(),
|
||||
setActiveBackgroundShellPid: vi.fn(),
|
||||
setIsBackgroundShellListOpen: vi.fn(),
|
||||
@@ -613,6 +616,7 @@ const mockUIActions: UIActions = {
|
||||
handleRestart: vi.fn(),
|
||||
handleNewAgentsSelect: vi.fn(),
|
||||
getPreferredEditor: vi.fn(),
|
||||
clearAccountSuspension: vi.fn(),
|
||||
};
|
||||
|
||||
let capturedOverflowState: OverflowState | undefined;
|
||||
|
||||
@@ -2544,6 +2544,136 @@ describe('AppContainer State Management', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Expansion Persistence', () => {
|
||||
let rerender: () => void;
|
||||
let unmount: () => void;
|
||||
let stdin: ReturnType<typeof render>['stdin'];
|
||||
|
||||
const setupExpansionPersistenceTest = async (
|
||||
HighPriorityChild?: React.FC,
|
||||
) => {
|
||||
const getTree = () => (
|
||||
<SettingsContext.Provider value={mockSettings}>
|
||||
<KeypressProvider config={mockConfig}>
|
||||
<OverflowProvider>
|
||||
<AppContainer
|
||||
config={mockConfig}
|
||||
version="1.0.0"
|
||||
initializationResult={mockInitResult}
|
||||
/>
|
||||
{HighPriorityChild && <HighPriorityChild />}
|
||||
</OverflowProvider>
|
||||
</KeypressProvider>
|
||||
</SettingsContext.Provider>
|
||||
);
|
||||
|
||||
const renderResult = render(getTree());
|
||||
stdin = renderResult.stdin;
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(100);
|
||||
});
|
||||
rerender = () => renderResult.rerender(getTree());
|
||||
unmount = () => renderResult.unmount();
|
||||
};
|
||||
|
||||
const writeStdin = async (sequence: string) => {
|
||||
await act(async () => {
|
||||
stdin.write(sequence);
|
||||
// Advance timers to allow escape sequence parsing and broadcasting
|
||||
vi.advanceTimersByTime(100);
|
||||
});
|
||||
rerender();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should reset expansion when a key is NOT handled by anyone', async () => {
|
||||
await setupExpansionPersistenceTest();
|
||||
|
||||
// Expand first
|
||||
act(() => capturedUIActions.setConstrainHeight(false));
|
||||
rerender();
|
||||
expect(capturedUIState.constrainHeight).toBe(false);
|
||||
|
||||
// Press a random key that no one handles (hits Low priority fallback)
|
||||
await writeStdin('x');
|
||||
|
||||
// Should be reset to true (collapsed)
|
||||
expect(capturedUIState.constrainHeight).toBe(true);
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should toggle expansion when Ctrl+O is pressed', async () => {
|
||||
await setupExpansionPersistenceTest();
|
||||
|
||||
// Initial state is collapsed
|
||||
expect(capturedUIState.constrainHeight).toBe(true);
|
||||
|
||||
// Press Ctrl+O to expand (Ctrl+O is sequence \x0f)
|
||||
await writeStdin('\x0f');
|
||||
expect(capturedUIState.constrainHeight).toBe(false);
|
||||
|
||||
// Press Ctrl+O again to collapse
|
||||
await writeStdin('\x0f');
|
||||
expect(capturedUIState.constrainHeight).toBe(true);
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should NOT collapse when a high-priority component handles the key (e.g., up/down arrows)', async () => {
|
||||
const NavigationHandler = () => {
|
||||
// use real useKeypress
|
||||
useKeypress(
|
||||
(key: Key) => {
|
||||
if (key.name === 'up' || key.name === 'down') {
|
||||
return true; // Handle navigation
|
||||
}
|
||||
return false;
|
||||
},
|
||||
{ isActive: true, priority: true }, // High priority
|
||||
);
|
||||
return null;
|
||||
};
|
||||
|
||||
await setupExpansionPersistenceTest(NavigationHandler);
|
||||
|
||||
// Expand first
|
||||
act(() => capturedUIActions.setConstrainHeight(false));
|
||||
rerender();
|
||||
expect(capturedUIState.constrainHeight).toBe(false);
|
||||
|
||||
// 1. Simulate Up arrow (handled by high priority child)
|
||||
// CSI A is Up arrow
|
||||
await writeStdin('\u001b[A');
|
||||
|
||||
// Should STILL be expanded
|
||||
expect(capturedUIState.constrainHeight).toBe(false);
|
||||
|
||||
// 2. Simulate Down arrow (handled by high priority child)
|
||||
// CSI B is Down arrow
|
||||
await writeStdin('\u001b[B');
|
||||
|
||||
// Should STILL be expanded
|
||||
expect(capturedUIState.constrainHeight).toBe(false);
|
||||
|
||||
// 3. Sanity check: press an unhandled key
|
||||
await writeStdin('x');
|
||||
|
||||
// Should finally collapse
|
||||
expect(capturedUIState.constrainHeight).toBe(true);
|
||||
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Shortcuts Help Visibility', () => {
|
||||
let handleGlobalKeypress: (key: Key) => boolean;
|
||||
let mockedUseKeypress: Mock;
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
type IdeInfo,
|
||||
type IdeContext,
|
||||
type UserTierId,
|
||||
type GeminiUserTier,
|
||||
type UserFeedbackPayload,
|
||||
type AgentDefinition,
|
||||
type ApprovalMode,
|
||||
@@ -82,6 +83,8 @@ import {
|
||||
CoreToolCallStatus,
|
||||
generateSteeringAckMessage,
|
||||
buildUserSteeringHintPrompt,
|
||||
logBillingEvent,
|
||||
ApiKeyUpdatedEvent,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { validateAuthMethod } from '../config/auth.js';
|
||||
import process from 'node:process';
|
||||
@@ -391,6 +394,9 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
? { remaining, limit, resetTime }
|
||||
: undefined;
|
||||
});
|
||||
const [paidTier, setPaidTier] = useState<GeminiUserTier | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const [isConfigInitialized, setConfigInitialized] = useState(false);
|
||||
|
||||
@@ -669,7 +675,14 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
onAuthError,
|
||||
apiKeyDefaultValue,
|
||||
reloadApiKey,
|
||||
} = useAuthCommand(settings, config, initializationResult.authError);
|
||||
accountSuspensionInfo,
|
||||
setAccountSuspensionInfo,
|
||||
} = useAuthCommand(
|
||||
settings,
|
||||
config,
|
||||
initializationResult.authError,
|
||||
initializationResult.accountSuspensionInfo,
|
||||
);
|
||||
const [authContext, setAuthContext] = useState<{ requiresRestart?: boolean }>(
|
||||
{},
|
||||
);
|
||||
@@ -686,12 +699,20 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
handleProQuotaChoice,
|
||||
validationRequest,
|
||||
handleValidationChoice,
|
||||
// G1 AI Credits
|
||||
overageMenuRequest,
|
||||
handleOverageMenuChoice,
|
||||
emptyWalletRequest,
|
||||
handleEmptyWalletChoice,
|
||||
} = useQuotaAndFallback({
|
||||
config,
|
||||
historyManager,
|
||||
userTier,
|
||||
paidTier,
|
||||
settings,
|
||||
setModelSwitchedFromQuotaError,
|
||||
onShowAuthSelection: () => setAuthState(AuthState.Updating),
|
||||
errorVerbosity: settings.merged.ui.errorVerbosity,
|
||||
});
|
||||
|
||||
// Derive auth state variables for backward compatibility with UIStateContext
|
||||
@@ -729,6 +750,8 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
const handleAuthSelect = useCallback(
|
||||
async (authType: AuthType | undefined, scope: LoadableSettingScope) => {
|
||||
if (authType) {
|
||||
const previousAuthType =
|
||||
config.getContentGeneratorConfig()?.authType ?? 'unknown';
|
||||
if (authType === AuthType.LOGIN_WITH_GOOGLE) {
|
||||
setAuthContext({ requiresRestart: true });
|
||||
} else {
|
||||
@@ -741,6 +764,10 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
config.setRemoteAdminSettings(undefined);
|
||||
await config.refreshAuth(authType);
|
||||
setAuthState(AuthState.Authenticated);
|
||||
logBillingEvent(
|
||||
config,
|
||||
new ApiKeyUpdatedEvent(previousAuthType, authType),
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof ChangeAuthRequestedError) {
|
||||
return;
|
||||
@@ -803,6 +830,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
// Only sync when not currently authenticating
|
||||
if (authState === AuthState.Authenticated) {
|
||||
setUserTier(config.getUserTier());
|
||||
setPaidTier(config.getUserPaidTier());
|
||||
}
|
||||
}, [config, authState]);
|
||||
|
||||
@@ -1081,6 +1109,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
backgroundShells,
|
||||
dismissBackgroundShell,
|
||||
retryStatus,
|
||||
setActivePtyId,
|
||||
} = useGeminiStream(
|
||||
config.getGeminiClient(),
|
||||
historyManager.history,
|
||||
@@ -1661,6 +1690,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
retryStatus,
|
||||
loadingPhrasesMode: settings.merged.ui.loadingPhrases,
|
||||
customWittyPhrases: settings.merged.ui.customWittyPhrases,
|
||||
errorVerbosity: settings.merged.ui.errorVerbosity,
|
||||
});
|
||||
|
||||
const handleGlobalKeypress = useCallback(
|
||||
@@ -1867,7 +1897,10 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
],
|
||||
);
|
||||
|
||||
useKeypress(handleGlobalKeypress, { isActive: true, priority: true });
|
||||
useKeypress(handleGlobalKeypress, {
|
||||
isActive: true,
|
||||
priority: KeypressPriority.Low,
|
||||
});
|
||||
|
||||
useKeypress(
|
||||
() => {
|
||||
@@ -2006,6 +2039,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
showIdeRestartPrompt ||
|
||||
!!proQuotaRequest ||
|
||||
!!validationRequest ||
|
||||
!!overageMenuRequest ||
|
||||
!!emptyWalletRequest ||
|
||||
isSessionBrowserOpen ||
|
||||
authState === AuthState.AwaitingApiKeyInput ||
|
||||
!!newAgents;
|
||||
@@ -2033,6 +2068,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
hasLoopDetectionConfirmationRequest ||
|
||||
!!proQuotaRequest ||
|
||||
!!validationRequest ||
|
||||
!!overageMenuRequest ||
|
||||
!!emptyWalletRequest ||
|
||||
!!customDialog;
|
||||
|
||||
const allowPlanMode =
|
||||
@@ -2173,6 +2210,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
isAuthenticating,
|
||||
isConfigInitialized,
|
||||
authError,
|
||||
accountSuspensionInfo,
|
||||
isAuthDialogOpen,
|
||||
isAwaitingApiKeyInput: authState === AuthState.AwaitingApiKeyInput,
|
||||
apiKeyDefaultValue,
|
||||
@@ -2243,6 +2281,9 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
stats: quotaStats,
|
||||
proQuotaRequest,
|
||||
validationRequest,
|
||||
// G1 AI Credits dialog state
|
||||
overageMenuRequest,
|
||||
emptyWalletRequest,
|
||||
},
|
||||
contextFileNames,
|
||||
errorCount,
|
||||
@@ -2301,6 +2342,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
isAuthenticating,
|
||||
isConfigInitialized,
|
||||
authError,
|
||||
accountSuspensionInfo,
|
||||
isAuthDialogOpen,
|
||||
editorError,
|
||||
isEditorDialogOpen,
|
||||
@@ -2367,6 +2409,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
quotaStats,
|
||||
proQuotaRequest,
|
||||
validationRequest,
|
||||
overageMenuRequest,
|
||||
emptyWalletRequest,
|
||||
contextFileNames,
|
||||
errorCount,
|
||||
availableTerminalHeight,
|
||||
@@ -2448,6 +2492,9 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
handleClearScreen,
|
||||
handleProQuotaChoice,
|
||||
handleValidationChoice,
|
||||
// G1 AI Credits handlers
|
||||
handleOverageMenuChoice,
|
||||
handleEmptyWalletChoice,
|
||||
openSessionBrowser,
|
||||
closeSessionBrowser,
|
||||
handleResumeSession,
|
||||
@@ -2463,6 +2510,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
revealCleanUiDetailsTemporarily,
|
||||
handleWarning,
|
||||
setEmbeddedShellFocused,
|
||||
setActivePtyId,
|
||||
dismissBackgroundShell,
|
||||
setActiveBackgroundShellPid,
|
||||
setIsBackgroundShellListOpen,
|
||||
@@ -2505,6 +2553,10 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
setNewAgents(null);
|
||||
},
|
||||
getPreferredEditor,
|
||||
clearAccountSuspension: () => {
|
||||
setAccountSuspensionInfo(null);
|
||||
setAuthState(AuthState.Updating);
|
||||
},
|
||||
}),
|
||||
[
|
||||
handleThemeSelect,
|
||||
@@ -2534,6 +2586,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
handleClearScreen,
|
||||
handleProQuotaChoice,
|
||||
handleValidationChoice,
|
||||
handleOverageMenuChoice,
|
||||
handleEmptyWalletChoice,
|
||||
openSessionBrowser,
|
||||
closeSessionBrowser,
|
||||
handleResumeSession,
|
||||
@@ -2549,10 +2603,12 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
revealCleanUiDetailsTemporarily,
|
||||
handleWarning,
|
||||
setEmbeddedShellFocused,
|
||||
setActivePtyId,
|
||||
dismissBackgroundShell,
|
||||
setActiveBackgroundShellPid,
|
||||
setIsBackgroundShellListOpen,
|
||||
setAuthContext,
|
||||
setAccountSuspensionInfo,
|
||||
newAgents,
|
||||
config,
|
||||
historyManager,
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { waitFor } from '../../test-utils/async.js';
|
||||
import { BannedAccountDialog } from './BannedAccountDialog.js';
|
||||
import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import {
|
||||
openBrowserSecurely,
|
||||
shouldLaunchBrowser,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { Text } from 'ink';
|
||||
import { runExitCleanup } from '../../utils/cleanup.js';
|
||||
import type { AccountSuspensionInfo } from '../contexts/UIStateContext.js';
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
openBrowserSecurely: vi.fn(),
|
||||
shouldLaunchBrowser: vi.fn().mockReturnValue(true),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../utils/cleanup.js', () => ({
|
||||
runExitCleanup: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
vi.mock('../hooks/useKeypress.js', () => ({
|
||||
useKeypress: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../components/shared/RadioButtonSelect.js', () => ({
|
||||
RadioButtonSelect: vi.fn(({ items }) => (
|
||||
<>
|
||||
{items.map((item: { value: string; label: string }) => (
|
||||
<Text key={item.value}>{item.label}</Text>
|
||||
))}
|
||||
</>
|
||||
)),
|
||||
}));
|
||||
|
||||
const mockedRadioButtonSelect = RadioButtonSelect as Mock;
|
||||
const mockedUseKeypress = useKeypress as Mock;
|
||||
const mockedOpenBrowser = openBrowserSecurely as Mock;
|
||||
const mockedShouldLaunchBrowser = shouldLaunchBrowser as Mock;
|
||||
const mockedRunExitCleanup = runExitCleanup as Mock;
|
||||
|
||||
const DEFAULT_SUSPENSION_INFO: AccountSuspensionInfo = {
|
||||
message:
|
||||
'This service has been disabled in this account for violation of Terms of Service. Please submit an appeal to continue using this product.',
|
||||
appealUrl: 'https://example.com/appeal',
|
||||
appealLinkText: 'Appeal Here',
|
||||
};
|
||||
|
||||
describe('BannedAccountDialog', () => {
|
||||
let onExit: Mock;
|
||||
let onChangeAuth: Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
mockedShouldLaunchBrowser.mockReturnValue(true);
|
||||
mockedOpenBrowser.mockResolvedValue(undefined);
|
||||
mockedRunExitCleanup.mockResolvedValue(undefined);
|
||||
onExit = vi.fn();
|
||||
onChangeAuth = vi.fn();
|
||||
});
|
||||
|
||||
it('renders the suspension message from accountSuspensionInfo', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={DEFAULT_SUSPENSION_INFO}
|
||||
onExit={onExit}
|
||||
onChangeAuth={onChangeAuth}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const frame = lastFrame();
|
||||
expect(frame).toContain('Account Suspended');
|
||||
expect(frame).toContain('violation of Terms of Service');
|
||||
expect(frame).toContain('Escape to exit');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders menu options with appeal link text from response', async () => {
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={DEFAULT_SUSPENSION_INFO}
|
||||
onExit={onExit}
|
||||
onChangeAuth={onChangeAuth}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const items = mockedRadioButtonSelect.mock.calls[0][0].items;
|
||||
expect(items).toHaveLength(3);
|
||||
expect(items[0].label).toBe('Appeal Here');
|
||||
expect(items[1].label).toBe('Change authentication');
|
||||
expect(items[2].label).toBe('Exit');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('hides form option when no appealUrl is provided', async () => {
|
||||
const infoWithoutUrl: AccountSuspensionInfo = {
|
||||
message: 'Account suspended.',
|
||||
};
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={infoWithoutUrl}
|
||||
onExit={onExit}
|
||||
onChangeAuth={onChangeAuth}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const items = mockedRadioButtonSelect.mock.calls[0][0].items;
|
||||
expect(items).toHaveLength(2);
|
||||
expect(items[0].label).toBe('Change authentication');
|
||||
expect(items[1].label).toBe('Exit');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('uses default label when appealLinkText is not provided', async () => {
|
||||
const infoWithoutLinkText: AccountSuspensionInfo = {
|
||||
message: 'Account suspended.',
|
||||
appealUrl: 'https://example.com/appeal',
|
||||
};
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={infoWithoutLinkText}
|
||||
onExit={onExit}
|
||||
onChangeAuth={onChangeAuth}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const items = mockedRadioButtonSelect.mock.calls[0][0].items;
|
||||
expect(items[0].label).toBe('Open the Google Form');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('opens browser when appeal option is selected', async () => {
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={DEFAULT_SUSPENSION_INFO}
|
||||
onExit={onExit}
|
||||
onChangeAuth={onChangeAuth}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
|
||||
await onSelect('open_form');
|
||||
expect(mockedOpenBrowser).toHaveBeenCalledWith(
|
||||
'https://example.com/appeal',
|
||||
);
|
||||
expect(onExit).not.toHaveBeenCalled();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('shows URL when browser cannot be launched', async () => {
|
||||
mockedShouldLaunchBrowser.mockReturnValue(false);
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={DEFAULT_SUSPENSION_INFO}
|
||||
onExit={onExit}
|
||||
onChangeAuth={onChangeAuth}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
|
||||
onSelect('open_form');
|
||||
await waitFor(() => {
|
||||
expect(lastFrame()).toContain('Please open this URL in a browser');
|
||||
});
|
||||
expect(mockedOpenBrowser).not.toHaveBeenCalled();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('calls onExit when "Exit" is selected', async () => {
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={DEFAULT_SUSPENSION_INFO}
|
||||
onExit={onExit}
|
||||
onChangeAuth={onChangeAuth}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
|
||||
await onSelect('exit');
|
||||
expect(mockedRunExitCleanup).toHaveBeenCalled();
|
||||
expect(onExit).toHaveBeenCalled();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('calls onChangeAuth when "Change authentication" is selected', async () => {
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={DEFAULT_SUSPENSION_INFO}
|
||||
onExit={onExit}
|
||||
onChangeAuth={onChangeAuth}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
|
||||
onSelect('change_auth');
|
||||
expect(onChangeAuth).toHaveBeenCalled();
|
||||
expect(onExit).not.toHaveBeenCalled();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('exits on escape key', async () => {
|
||||
const { waitUntilReady, unmount } = renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={DEFAULT_SUSPENSION_INFO}
|
||||
onExit={onExit}
|
||||
onChangeAuth={onChangeAuth}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
|
||||
const result = keypressHandler({ name: 'escape' });
|
||||
expect(result).toBe(true);
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders snapshot correctly', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={DEFAULT_SUSPENSION_INFO}
|
||||
onExit={onExit}
|
||||
onChangeAuth={onChangeAuth}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js';
|
||||
import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import {
|
||||
openBrowserSecurely,
|
||||
shouldLaunchBrowser,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { runExitCleanup } from '../../utils/cleanup.js';
|
||||
import type { AccountSuspensionInfo } from '../contexts/UIStateContext.js';
|
||||
|
||||
interface BannedAccountDialogProps {
|
||||
accountSuspensionInfo: AccountSuspensionInfo;
|
||||
onExit: () => void;
|
||||
onChangeAuth: () => void;
|
||||
}
|
||||
|
||||
export function BannedAccountDialog({
|
||||
accountSuspensionInfo,
|
||||
onExit,
|
||||
onChangeAuth,
|
||||
}: BannedAccountDialogProps): React.JSX.Element {
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
const appealUrl = accountSuspensionInfo.appealUrl;
|
||||
const appealLinkText =
|
||||
accountSuspensionInfo.appealLinkText ?? 'Open the Google Form';
|
||||
|
||||
const items = useMemo(() => {
|
||||
const menuItems = [];
|
||||
if (appealUrl) {
|
||||
menuItems.push({
|
||||
label: appealLinkText,
|
||||
value: 'open_form' as const,
|
||||
key: 'open_form',
|
||||
});
|
||||
}
|
||||
menuItems.push(
|
||||
{
|
||||
label: 'Change authentication',
|
||||
value: 'change_auth' as const,
|
||||
key: 'change_auth',
|
||||
},
|
||||
{
|
||||
label: 'Exit',
|
||||
value: 'exit' as const,
|
||||
key: 'exit',
|
||||
},
|
||||
);
|
||||
return menuItems;
|
||||
}, [appealUrl, appealLinkText]);
|
||||
|
||||
useKeypress(
|
||||
(key) => {
|
||||
if (key.name === 'escape') {
|
||||
void handleExit();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
{ isActive: true },
|
||||
);
|
||||
|
||||
const handleExit = useCallback(async () => {
|
||||
await runExitCleanup();
|
||||
onExit();
|
||||
}, [onExit]);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
async (choice: string) => {
|
||||
if (choice === 'open_form' && appealUrl) {
|
||||
if (!shouldLaunchBrowser()) {
|
||||
setErrorMessage(`Please open this URL in a browser: ${appealUrl}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await openBrowserSecurely(appealUrl);
|
||||
} catch {
|
||||
setErrorMessage(`Failed to open browser. Please visit: ${appealUrl}`);
|
||||
}
|
||||
} else if (choice === 'change_auth') {
|
||||
onChangeAuth();
|
||||
} else {
|
||||
await handleExit();
|
||||
}
|
||||
},
|
||||
[handleExit, onChangeAuth, appealUrl],
|
||||
);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" padding={1}>
|
||||
<Text bold color={theme.status.error}>
|
||||
Error: Account Suspended
|
||||
</Text>
|
||||
|
||||
<Box marginTop={1}>
|
||||
<Text>{accountSuspensionInfo.message}</Text>
|
||||
</Box>
|
||||
|
||||
{appealUrl && (
|
||||
<>
|
||||
<Box marginTop={1}>
|
||||
<Text>Appeal URL:</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text color={theme.text.link}>[{appealUrl}]</Text>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{errorMessage && (
|
||||
<Box marginTop={1}>
|
||||
<Text color={theme.status.error}>{errorMessage}</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box marginTop={1}>
|
||||
<RadioButtonSelect
|
||||
items={items}
|
||||
onSelect={(choice) => void handleSelect(choice)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box marginTop={1}>
|
||||
<Text dimColor>Escape to exit</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`BannedAccountDialog > renders snapshot correctly 1`] = `
|
||||
"
|
||||
Error: Account Suspended
|
||||
|
||||
This service has been disabled in this account for violation of Terms of Service. Please submit an
|
||||
appeal to continue using this product.
|
||||
|
||||
Appeal URL:
|
||||
[https://example.com/appeal]
|
||||
|
||||
Appeal HereChange authenticationExit
|
||||
|
||||
Escape to exit
|
||||
"
|
||||
`;
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
type Config,
|
||||
loadApiKey,
|
||||
debugLogger,
|
||||
isAccountSuspendedError,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { getErrorMessage } from '@google/gemini-cli-core';
|
||||
import { AuthState } from '../types.js';
|
||||
@@ -34,16 +35,21 @@ export function validateAuthMethodWithSettings(
|
||||
return validateAuthMethod(authType);
|
||||
}
|
||||
|
||||
import type { AccountSuspensionInfo } from '../contexts/UIStateContext.js';
|
||||
|
||||
export const useAuthCommand = (
|
||||
settings: LoadedSettings,
|
||||
config: Config,
|
||||
initialAuthError: string | null = null,
|
||||
initialAccountSuspensionInfo: AccountSuspensionInfo | null = null,
|
||||
) => {
|
||||
const [authState, setAuthState] = useState<AuthState>(
|
||||
initialAuthError ? AuthState.Updating : AuthState.Unauthenticated,
|
||||
);
|
||||
|
||||
const [authError, setAuthError] = useState<string | null>(initialAuthError);
|
||||
const [accountSuspensionInfo, setAccountSuspensionInfo] =
|
||||
useState<AccountSuspensionInfo | null>(initialAccountSuspensionInfo);
|
||||
const [apiKeyDefaultValue, setApiKeyDefaultValue] = useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
@@ -130,7 +136,16 @@ export const useAuthCommand = (
|
||||
setAuthError(null);
|
||||
setAuthState(AuthState.Authenticated);
|
||||
} catch (e) {
|
||||
onAuthError(`Failed to login. Message: ${getErrorMessage(e)}`);
|
||||
const suspendedError = isAccountSuspendedError(e);
|
||||
if (suspendedError) {
|
||||
setAccountSuspensionInfo({
|
||||
message: suspendedError.message,
|
||||
appealUrl: suspendedError.appealUrl,
|
||||
appealLinkText: suspendedError.appealLinkText,
|
||||
});
|
||||
} else {
|
||||
onAuthError(`Failed to login. Message: ${getErrorMessage(e)}`);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [
|
||||
@@ -150,5 +165,7 @@ export const useAuthCommand = (
|
||||
onAuthError,
|
||||
apiKeyDefaultValue,
|
||||
reloadApiKey,
|
||||
accountSuspensionInfo,
|
||||
setAccountSuspensionInfo,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mcpCommand } from './mcpCommand.js';
|
||||
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
||||
import {
|
||||
@@ -77,6 +77,8 @@ describe('mcpCommand', () => {
|
||||
getGeminiClient: ReturnType<typeof vi.fn>;
|
||||
getMcpClientManager: ReturnType<typeof vi.fn>;
|
||||
getResourceRegistry: ReturnType<typeof vi.fn>;
|
||||
setUserInteractedWithMcp: ReturnType<typeof vi.fn>;
|
||||
getLastMcpError: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -104,12 +106,15 @@ describe('mcpCommand', () => {
|
||||
}),
|
||||
getGeminiClient: vi.fn(),
|
||||
getMcpClientManager: vi.fn().mockImplementation(() => ({
|
||||
getBlockedMcpServers: vi.fn(),
|
||||
getMcpServers: vi.fn(),
|
||||
getBlockedMcpServers: vi.fn().mockReturnValue([]),
|
||||
getMcpServers: vi.fn().mockReturnValue({}),
|
||||
getLastError: vi.fn().mockReturnValue(undefined),
|
||||
})),
|
||||
getResourceRegistry: vi.fn().mockReturnValue({
|
||||
getAllResources: vi.fn().mockReturnValue([]),
|
||||
}),
|
||||
setUserInteractedWithMcp: vi.fn(),
|
||||
getLastMcpError: vi.fn().mockReturnValue(undefined),
|
||||
};
|
||||
|
||||
mockContext = createMockCommandContext({
|
||||
@@ -119,6 +124,10 @@ describe('mcpCommand', () => {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('basic functionality', () => {
|
||||
it('should show an error if config is not available', async () => {
|
||||
const contextWithoutConfig = createMockCommandContext({
|
||||
@@ -161,6 +170,7 @@ describe('mcpCommand', () => {
|
||||
mockConfig.getMcpClientManager = vi.fn().mockReturnValue({
|
||||
getMcpServers: vi.fn().mockReturnValue(mockMcpServers),
|
||||
getBlockedMcpServers: vi.fn().mockReturnValue([]),
|
||||
getLastError: vi.fn().mockReturnValue(undefined),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@ const authCommand: SlashCommand = {
|
||||
};
|
||||
}
|
||||
|
||||
config.setUserInteractedWithMcp();
|
||||
|
||||
const mcpServers = config.getMcpClientManager()?.getMcpServers() ?? {};
|
||||
|
||||
if (!serverName) {
|
||||
@@ -184,6 +186,8 @@ const listAction = async (
|
||||
};
|
||||
}
|
||||
|
||||
config.setUserInteractedWithMcp();
|
||||
|
||||
const toolRegistry = config.getToolRegistry();
|
||||
if (!toolRegistry) {
|
||||
return {
|
||||
@@ -250,6 +254,13 @@ const listAction = async (
|
||||
enablementState[serverName] =
|
||||
await enablementManager.getDisplayState(serverName);
|
||||
}
|
||||
const errors: Record<string, string> = {};
|
||||
for (const serverName of serverNames) {
|
||||
const error = config.getMcpClientManager()?.getLastError(serverName);
|
||||
if (error) {
|
||||
errors[serverName] = error;
|
||||
}
|
||||
}
|
||||
|
||||
const mcpStatusItem: HistoryItemMcpStatus = {
|
||||
type: MessageType.MCP_STATUS,
|
||||
@@ -274,16 +285,19 @@ const listAction = async (
|
||||
})),
|
||||
authStatus,
|
||||
enablementState,
|
||||
blockedServers: blockedMcpServers,
|
||||
errors,
|
||||
blockedServers: blockedMcpServers.map((s) => ({
|
||||
name: s.name,
|
||||
extensionName: s.extensionName,
|
||||
})),
|
||||
discoveryInProgress,
|
||||
connectingServers,
|
||||
showDescriptions,
|
||||
showSchema,
|
||||
showDescriptions: Boolean(showDescriptions),
|
||||
showSchema: Boolean(showSchema),
|
||||
};
|
||||
|
||||
context.ui.addItem(mcpStatusItem);
|
||||
};
|
||||
|
||||
const listCommand: SlashCommand = {
|
||||
name: 'list',
|
||||
altNames: ['ls', 'nodesc', 'nodescription'],
|
||||
@@ -372,6 +386,8 @@ async function handleEnableDisable(
|
||||
};
|
||||
}
|
||||
|
||||
config.setUserInteractedWithMcp();
|
||||
|
||||
const parts = args.trim().split(/\s+/);
|
||||
const isSession = parts.includes('--session');
|
||||
const serverName = parts.filter((p) => p !== '--session')[0];
|
||||
|
||||
@@ -39,11 +39,18 @@ describe('statsCommand', () => {
|
||||
mockContext.session.stats.sessionStartTime = startTime;
|
||||
});
|
||||
|
||||
it('should display general session stats when run with no subcommand', () => {
|
||||
it('should display general session stats when run with no subcommand', async () => {
|
||||
if (!statsCommand.action) throw new Error('Command has no action');
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
statsCommand.action(mockContext, '');
|
||||
mockContext.services.config = {
|
||||
refreshUserQuota: vi.fn(),
|
||||
refreshAvailableCredits: vi.fn(),
|
||||
getUserTierName: vi.fn(),
|
||||
getUserPaidTier: vi.fn(),
|
||||
getModel: vi.fn(),
|
||||
} as unknown as Config;
|
||||
|
||||
await statsCommand.action(mockContext, '');
|
||||
|
||||
const expectedDuration = formatDuration(
|
||||
endTime.getTime() - startTime.getTime(),
|
||||
@@ -55,6 +62,7 @@ describe('statsCommand', () => {
|
||||
tier: undefined,
|
||||
userEmail: 'mock@example.com',
|
||||
currentModel: undefined,
|
||||
creditBalance: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -78,6 +86,8 @@ describe('statsCommand', () => {
|
||||
getQuotaRemaining: mockGetQuotaRemaining,
|
||||
getQuotaLimit: mockGetQuotaLimit,
|
||||
getQuotaResetTime: mockGetQuotaResetTime,
|
||||
getUserPaidTier: vi.fn(),
|
||||
refreshAvailableCredits: vi.fn(),
|
||||
} as unknown as Config;
|
||||
|
||||
await statsCommand.action(mockContext, '');
|
||||
|
||||
@@ -11,7 +11,10 @@ import type {
|
||||
} from '../types.js';
|
||||
import { MessageType } from '../types.js';
|
||||
import { formatDuration } from '../utils/formatters.js';
|
||||
import { UserAccountManager } from '@google/gemini-cli-core';
|
||||
import {
|
||||
UserAccountManager,
|
||||
getG1CreditBalance,
|
||||
} from '@google/gemini-cli-core';
|
||||
import {
|
||||
type CommandContext,
|
||||
type SlashCommand,
|
||||
@@ -27,8 +30,10 @@ function getUserIdentity(context: CommandContext) {
|
||||
const userEmail = cachedAccount ?? undefined;
|
||||
|
||||
const tier = context.services.config?.getUserTierName();
|
||||
const paidTier = context.services.config?.getUserPaidTier();
|
||||
const creditBalance = getG1CreditBalance(paidTier) ?? undefined;
|
||||
|
||||
return { selectedAuthType, userEmail, tier };
|
||||
return { selectedAuthType, userEmail, tier, creditBalance };
|
||||
}
|
||||
|
||||
async function defaultSessionView(context: CommandContext) {
|
||||
@@ -43,7 +48,8 @@ async function defaultSessionView(context: CommandContext) {
|
||||
}
|
||||
const wallDuration = now.getTime() - sessionStartTime.getTime();
|
||||
|
||||
const { selectedAuthType, userEmail, tier } = getUserIdentity(context);
|
||||
const { selectedAuthType, userEmail, tier, creditBalance } =
|
||||
getUserIdentity(context);
|
||||
const currentModel = context.services.config?.getModel();
|
||||
|
||||
const statsItem: HistoryItemStats = {
|
||||
@@ -53,10 +59,14 @@ async function defaultSessionView(context: CommandContext) {
|
||||
userEmail,
|
||||
tier,
|
||||
currentModel,
|
||||
creditBalance,
|
||||
};
|
||||
|
||||
if (context.services.config) {
|
||||
const quota = await context.services.config.refreshUserQuota();
|
||||
const [quota] = await Promise.all([
|
||||
context.services.config.refreshUserQuota(),
|
||||
context.services.config.refreshAvailableCredits(),
|
||||
]);
|
||||
if (quota) {
|
||||
statsItem.quotas = quota;
|
||||
statsItem.pooledRemaining = context.services.config.getQuotaRemaining();
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { render } from '../../test-utils/render.js';
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { DetailedMessagesDisplay } from './DetailedMessagesDisplay.js';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import type { ConsoleMessageItem } from '../types.js';
|
||||
import { Box } from 'ink';
|
||||
import type React from 'react';
|
||||
import { createMockSettings } from '../../test-utils/settings.js';
|
||||
|
||||
vi.mock('./shared/ScrollableList.js', () => ({
|
||||
ScrollableList: ({
|
||||
@@ -29,13 +30,18 @@ vi.mock('./shared/ScrollableList.js', () => ({
|
||||
|
||||
describe('DetailedMessagesDisplay', () => {
|
||||
it('renders nothing when messages are empty', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = render(
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<DetailedMessagesDisplay
|
||||
messages={[]}
|
||||
maxHeight={10}
|
||||
width={80}
|
||||
hasFocus={false}
|
||||
/>,
|
||||
{
|
||||
settings: createMockSettings({
|
||||
merged: { ui: { errorVerbosity: 'full' } },
|
||||
}),
|
||||
},
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame({ allowEmpty: true })).toBe('');
|
||||
@@ -50,13 +56,18 @@ describe('DetailedMessagesDisplay', () => {
|
||||
{ type: 'debug', content: 'Debug message', count: 1 },
|
||||
];
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = render(
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<DetailedMessagesDisplay
|
||||
messages={messages}
|
||||
maxHeight={20}
|
||||
width={80}
|
||||
hasFocus={true}
|
||||
/>,
|
||||
{
|
||||
settings: createMockSettings({
|
||||
merged: { ui: { errorVerbosity: 'full' } },
|
||||
}),
|
||||
},
|
||||
);
|
||||
await waitUntilReady();
|
||||
const output = lastFrame();
|
||||
@@ -65,18 +76,69 @@ describe('DetailedMessagesDisplay', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('hides the F12 hint in low error verbosity mode', async () => {
|
||||
const messages: ConsoleMessageItem[] = [
|
||||
{ type: 'error', content: 'Error message', count: 1 },
|
||||
];
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<DetailedMessagesDisplay
|
||||
messages={messages}
|
||||
maxHeight={20}
|
||||
width={80}
|
||||
hasFocus={true}
|
||||
/>,
|
||||
{
|
||||
settings: createMockSettings({
|
||||
merged: { ui: { errorVerbosity: 'low' } },
|
||||
}),
|
||||
},
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).not.toContain('(F12 to close)');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('shows the F12 hint in full error verbosity mode', async () => {
|
||||
const messages: ConsoleMessageItem[] = [
|
||||
{ type: 'error', content: 'Error message', count: 1 },
|
||||
];
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<DetailedMessagesDisplay
|
||||
messages={messages}
|
||||
maxHeight={20}
|
||||
width={80}
|
||||
hasFocus={true}
|
||||
/>,
|
||||
{
|
||||
settings: createMockSettings({
|
||||
merged: { ui: { errorVerbosity: 'full' } },
|
||||
}),
|
||||
},
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toContain('(F12 to close)');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders message counts', async () => {
|
||||
const messages: ConsoleMessageItem[] = [
|
||||
{ type: 'log', content: 'Repeated message', count: 5 },
|
||||
];
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = render(
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<DetailedMessagesDisplay
|
||||
messages={messages}
|
||||
maxHeight={10}
|
||||
width={80}
|
||||
hasFocus={false}
|
||||
/>,
|
||||
{
|
||||
settings: createMockSettings({
|
||||
merged: { ui: { errorVerbosity: 'full' } },
|
||||
}),
|
||||
},
|
||||
);
|
||||
await waitUntilReady();
|
||||
const output = lastFrame();
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
ScrollableList,
|
||||
type ScrollableListRef,
|
||||
} from './shared/ScrollableList.js';
|
||||
import { useConfig } from '../contexts/ConfigContext.js';
|
||||
import { useSettings } from '../contexts/SettingsContext.js';
|
||||
|
||||
interface DetailedMessagesDisplayProps {
|
||||
messages: ConsoleMessageItem[];
|
||||
@@ -27,6 +29,10 @@ export const DetailedMessagesDisplay: React.FC<
|
||||
DetailedMessagesDisplayProps
|
||||
> = ({ messages, maxHeight, width, hasFocus }) => {
|
||||
const scrollableListRef = useRef<ScrollableListRef<ConsoleMessageItem>>(null);
|
||||
const config = useConfig();
|
||||
const settings = useSettings();
|
||||
const showHotkeyHint =
|
||||
settings.merged.ui.errorVerbosity === 'full' || config.getDebugMode();
|
||||
|
||||
const borderAndPadding = 3;
|
||||
|
||||
@@ -65,7 +71,10 @@ export const DetailedMessagesDisplay: React.FC<
|
||||
>
|
||||
<Box marginBottom={1}>
|
||||
<Text bold color={theme.text.primary}>
|
||||
Debug Console <Text color={theme.text.secondary}>(F12 to close)</Text>
|
||||
Debug Console{' '}
|
||||
{showHotkeyHint && (
|
||||
<Text color={theme.text.secondary}>(F12 to close)</Text>
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box height={maxHeight} width={width - borderAndPadding}>
|
||||
|
||||
@@ -80,6 +80,8 @@ describe('DialogManager', () => {
|
||||
stats: undefined,
|
||||
proQuotaRequest: null,
|
||||
validationRequest: null,
|
||||
overageMenuRequest: null,
|
||||
emptyWalletRequest: null,
|
||||
},
|
||||
shouldShowIdePrompt: false,
|
||||
isFolderTrustDialogOpen: false,
|
||||
@@ -132,6 +134,8 @@ describe('DialogManager', () => {
|
||||
resolve: vi.fn(),
|
||||
},
|
||||
validationRequest: null,
|
||||
overageMenuRequest: null,
|
||||
emptyWalletRequest: null,
|
||||
},
|
||||
},
|
||||
'ProQuotaDialog',
|
||||
|
||||
@@ -13,11 +13,14 @@ import { ThemeDialog } from './ThemeDialog.js';
|
||||
import { SettingsDialog } from './SettingsDialog.js';
|
||||
import { AuthInProgress } from '../auth/AuthInProgress.js';
|
||||
import { AuthDialog } from '../auth/AuthDialog.js';
|
||||
import { BannedAccountDialog } from '../auth/BannedAccountDialog.js';
|
||||
import { ApiAuthDialog } from '../auth/ApiAuthDialog.js';
|
||||
import { EditorSettingsDialog } from './EditorSettingsDialog.js';
|
||||
import { PrivacyNotice } from '../privacy/PrivacyNotice.js';
|
||||
import { ProQuotaDialog } from './ProQuotaDialog.js';
|
||||
import { ValidationDialog } from './ValidationDialog.js';
|
||||
import { OverageMenuDialog } from './OverageMenuDialog.js';
|
||||
import { EmptyWalletDialog } from './EmptyWalletDialog.js';
|
||||
import { runExitCleanup } from '../../utils/cleanup.js';
|
||||
import { RELAUNCH_EXIT_CODE } from '../../utils/processUtils.js';
|
||||
import { SessionBrowser } from './SessionBrowser.js';
|
||||
@@ -152,6 +155,28 @@ export const DialogManager = ({
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (uiState.quota.overageMenuRequest) {
|
||||
return (
|
||||
<OverageMenuDialog
|
||||
failedModel={uiState.quota.overageMenuRequest.failedModel}
|
||||
fallbackModel={uiState.quota.overageMenuRequest.fallbackModel}
|
||||
resetTime={uiState.quota.overageMenuRequest.resetTime}
|
||||
creditBalance={uiState.quota.overageMenuRequest.creditBalance}
|
||||
onChoice={uiActions.handleOverageMenuChoice}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (uiState.quota.emptyWalletRequest) {
|
||||
return (
|
||||
<EmptyWalletDialog
|
||||
failedModel={uiState.quota.emptyWalletRequest.failedModel}
|
||||
fallbackModel={uiState.quota.emptyWalletRequest.fallbackModel}
|
||||
resetTime={uiState.quota.emptyWalletRequest.resetTime}
|
||||
onGetCredits={uiState.quota.emptyWalletRequest.onGetCredits}
|
||||
onChoice={uiActions.handleEmptyWalletChoice}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (uiState.shouldShowIdePrompt) {
|
||||
return (
|
||||
<IdeIntegrationNudge
|
||||
@@ -296,6 +321,21 @@ export const DialogManager = ({
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
if (uiState.accountSuspensionInfo) {
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<BannedAccountDialog
|
||||
accountSuspensionInfo={uiState.accountSuspensionInfo}
|
||||
onExit={() => {
|
||||
process.exit(1);
|
||||
}}
|
||||
onChangeAuth={() => {
|
||||
uiActions.clearAccountSuspension();
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
if (uiState.isAuthenticating) {
|
||||
return (
|
||||
<AuthInProgress
|
||||
@@ -318,6 +358,7 @@ export const DialogManager = ({
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (uiState.isAuthDialogOpen) {
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { waitFor } from '../../test-utils/async.js';
|
||||
import { act } from 'react';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { EmptyWalletDialog } from './EmptyWalletDialog.js';
|
||||
|
||||
const writeKey = (stdin: { write: (data: string) => void }, key: string) => {
|
||||
act(() => {
|
||||
stdin.write(key);
|
||||
});
|
||||
};
|
||||
|
||||
describe('EmptyWalletDialog', () => {
|
||||
const mockOnChoice = vi.fn();
|
||||
const mockOnGetCredits = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
it('should match snapshot with fallback available', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
fallbackModel="gemini-3-flash-preview"
|
||||
resetTime="2:00 PM"
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should match snapshot without fallback', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should display the model name and usage limit message', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
const output = lastFrame() ?? '';
|
||||
expect(output).toContain('gemini-2.5-pro');
|
||||
expect(output).toContain('Usage limit reached');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should display purchase prompt and credits update notice', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
const output = lastFrame() ?? '';
|
||||
expect(output).toContain('purchase more AI Credits');
|
||||
expect(output).toContain(
|
||||
'Newly purchased AI credits may take a few minutes to update',
|
||||
);
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should display reset time when provided', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
resetTime="3:45 PM"
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
const output = lastFrame() ?? '';
|
||||
expect(output).toContain('3:45 PM');
|
||||
expect(output).toContain('Access resets at');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should not display reset time when not provided', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
const output = lastFrame() ?? '';
|
||||
expect(output).not.toContain('Access resets at');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should display slash command hints', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
const output = lastFrame() ?? '';
|
||||
expect(output).toContain('/stats');
|
||||
expect(output).toContain('/model');
|
||||
expect(output).toContain('/auth');
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onChoice handling', () => {
|
||||
it('should call onGetCredits and onChoice when get_credits is selected', async () => {
|
||||
// get_credits is the first item, so just press Enter
|
||||
const { unmount, stdin, waitUntilReady } = renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
onChoice={mockOnChoice}
|
||||
onGetCredits={mockOnGetCredits}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
writeKey(stdin, '\r');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnGetCredits).toHaveBeenCalled();
|
||||
expect(mockOnChoice).toHaveBeenCalledWith('get_credits');
|
||||
});
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should call onChoice without onGetCredits when onGetCredits is not provided', async () => {
|
||||
const { unmount, stdin, waitUntilReady } = renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
writeKey(stdin, '\r');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnChoice).toHaveBeenCalledWith('get_credits');
|
||||
});
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should call onChoice with use_fallback when selected', async () => {
|
||||
// With fallback: items are [get_credits, use_fallback, stop]
|
||||
// use_fallback is the second item: Down + Enter
|
||||
const { unmount, stdin, waitUntilReady } = renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
fallbackModel="gemini-3-flash-preview"
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
writeKey(stdin, '\x1b[B'); // Down arrow
|
||||
writeKey(stdin, '\r');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnChoice).toHaveBeenCalledWith('use_fallback');
|
||||
});
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should call onChoice with stop when selected', async () => {
|
||||
// Without fallback: items are [get_credits, stop]
|
||||
// stop is the second item: Down + Enter
|
||||
const { unmount, stdin, waitUntilReady } = renderWithProviders(
|
||||
<EmptyWalletDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
writeKey(stdin, '\x1b[B'); // Down arrow
|
||||
writeKey(stdin, '\r');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnChoice).toHaveBeenCalledWith('stop');
|
||||
});
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
||||
/** Available choices in the empty wallet dialog */
|
||||
export type EmptyWalletChoice = 'get_credits' | 'use_fallback' | 'stop';
|
||||
|
||||
interface EmptyWalletDialogProps {
|
||||
/** The model that hit the quota limit */
|
||||
failedModel: string;
|
||||
/** The fallback model to offer (omit if none available) */
|
||||
fallbackModel?: string;
|
||||
/** Time when access resets (human-readable) */
|
||||
resetTime?: string;
|
||||
/** Callback to log click and open the browser for purchasing credits */
|
||||
onGetCredits?: () => void;
|
||||
/** Callback when user makes a selection */
|
||||
onChoice: (choice: EmptyWalletChoice) => void;
|
||||
}
|
||||
|
||||
export function EmptyWalletDialog({
|
||||
failedModel,
|
||||
fallbackModel,
|
||||
resetTime,
|
||||
onGetCredits,
|
||||
onChoice,
|
||||
}: EmptyWalletDialogProps): React.JSX.Element {
|
||||
const items: Array<{
|
||||
label: string;
|
||||
value: EmptyWalletChoice;
|
||||
key: string;
|
||||
}> = [
|
||||
{
|
||||
label: 'Get AI Credits - Open browser to purchase credits',
|
||||
value: 'get_credits',
|
||||
key: 'get_credits',
|
||||
},
|
||||
];
|
||||
|
||||
if (fallbackModel) {
|
||||
items.push({
|
||||
label: `Switch to ${fallbackModel}`,
|
||||
value: 'use_fallback',
|
||||
key: 'use_fallback',
|
||||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
label: 'Stop - Abort request',
|
||||
value: 'stop',
|
||||
key: 'stop',
|
||||
});
|
||||
|
||||
const handleSelect = (choice: EmptyWalletChoice) => {
|
||||
if (choice === 'get_credits') {
|
||||
onGetCredits?.();
|
||||
}
|
||||
onChoice(choice);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box borderStyle="round" flexDirection="column" padding={1}>
|
||||
<Box marginBottom={1} flexDirection="column">
|
||||
<Text color={theme.status.warning}>
|
||||
Usage limit reached for {failedModel}.
|
||||
</Text>
|
||||
{resetTime && <Text>Access resets at {resetTime}.</Text>}
|
||||
<Text>
|
||||
<Text bold color={theme.text.accent}>
|
||||
/stats
|
||||
</Text>{' '}
|
||||
model for usage details
|
||||
</Text>
|
||||
<Text>
|
||||
<Text bold color={theme.text.accent}>
|
||||
/model
|
||||
</Text>{' '}
|
||||
to switch models.
|
||||
</Text>
|
||||
<Text>
|
||||
<Text bold color={theme.text.accent}>
|
||||
/auth
|
||||
</Text>{' '}
|
||||
to switch to API key.
|
||||
</Text>
|
||||
</Box>
|
||||
<Box marginBottom={1}>
|
||||
<Text>To continue using this model now, purchase more AI Credits.</Text>
|
||||
</Box>
|
||||
<Box marginBottom={1}>
|
||||
<Text dimColor>
|
||||
Newly purchased AI credits may take a few minutes to update.
|
||||
</Text>
|
||||
</Box>
|
||||
<Box marginBottom={1}>
|
||||
<Text>How would you like to proceed?</Text>
|
||||
</Box>
|
||||
<Box marginTop={1} marginBottom={1}>
|
||||
<RadioButtonSelect items={items} onSelect={handleSelect} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -8,7 +8,11 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { createMockSettings } from '../../test-utils/settings.js';
|
||||
import { Footer } from './Footer.js';
|
||||
import { tildeifyPath, ToolCallDecision } from '@google/gemini-cli-core';
|
||||
import {
|
||||
makeFakeConfig,
|
||||
tildeifyPath,
|
||||
ToolCallDecision,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { SessionStatsState } from '../contexts/SessionContext.js';
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
@@ -177,6 +181,8 @@ describe('<Footer />', () => {
|
||||
},
|
||||
proQuotaRequest: null,
|
||||
validationRequest: null,
|
||||
overageMenuRequest: null,
|
||||
emptyWalletRequest: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -203,6 +209,8 @@ describe('<Footer />', () => {
|
||||
},
|
||||
proQuotaRequest: null,
|
||||
validationRequest: null,
|
||||
overageMenuRequest: null,
|
||||
emptyWalletRequest: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -229,6 +237,8 @@ describe('<Footer />', () => {
|
||||
},
|
||||
proQuotaRequest: null,
|
||||
validationRequest: null,
|
||||
overageMenuRequest: null,
|
||||
emptyWalletRequest: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -497,6 +507,75 @@ describe('<Footer />', () => {
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe('error summary visibility', () => {
|
||||
it('hides error summary in low verbosity mode', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<Footer />,
|
||||
{
|
||||
width: 120,
|
||||
uiState: {
|
||||
sessionStats: mockSessionStats,
|
||||
errorCount: 2,
|
||||
showErrorDetails: false,
|
||||
},
|
||||
settings: createMockSettings({
|
||||
merged: { ui: { errorVerbosity: 'low' } },
|
||||
}),
|
||||
},
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).not.toContain('F12 for details');
|
||||
expect(lastFrame()).not.toContain('2 errors');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('shows error summary in full verbosity mode', async () => {
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<Footer />,
|
||||
{
|
||||
width: 120,
|
||||
uiState: {
|
||||
sessionStats: mockSessionStats,
|
||||
errorCount: 2,
|
||||
showErrorDetails: false,
|
||||
},
|
||||
settings: createMockSettings({
|
||||
merged: { ui: { errorVerbosity: 'full' } },
|
||||
}),
|
||||
},
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toContain('F12 for details');
|
||||
expect(lastFrame()).toContain('2 errors');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('shows error summary in debug mode even when verbosity is low', async () => {
|
||||
const debugConfig = makeFakeConfig();
|
||||
vi.spyOn(debugConfig, 'getDebugMode').mockReturnValue(true);
|
||||
|
||||
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
|
||||
<Footer />,
|
||||
{
|
||||
width: 120,
|
||||
config: debugConfig,
|
||||
uiState: {
|
||||
sessionStats: mockSessionStats,
|
||||
errorCount: 1,
|
||||
showErrorDetails: false,
|
||||
},
|
||||
settings: createMockSettings({
|
||||
merged: { ui: { errorVerbosity: 'low' } },
|
||||
}),
|
||||
},
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toContain('F12 for details');
|
||||
expect(lastFrame()).toContain('1 error');
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fallback mode display', () => {
|
||||
|
||||
@@ -60,6 +60,9 @@ export const Footer: React.FC = () => {
|
||||
|
||||
const showMemoryUsage =
|
||||
config.getDebugMode() || settings.merged.ui.showMemoryUsage;
|
||||
const isFullErrorVerbosity = settings.merged.ui.errorVerbosity === 'full';
|
||||
const showErrorSummary =
|
||||
!showErrorDetails && errorCount > 0 && (isFullErrorVerbosity || debugMode);
|
||||
const hideCWD = settings.merged.ui.footer.hideCWD;
|
||||
const hideSandboxStatus = settings.merged.ui.footer.hideSandboxStatus;
|
||||
const hideModelInfo = settings.merged.ui.footer.hideModelInfo;
|
||||
@@ -180,7 +183,7 @@ export const Footer: React.FC = () => {
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
{!showErrorDetails && errorCount > 0 && (
|
||||
{showErrorSummary && (
|
||||
<Box paddingLeft={1} flexDirection="row">
|
||||
<Text color={theme.ui.comment}>| </Text>
|
||||
<ConsoleSummaryDisplay errorCount={errorCount} />
|
||||
|
||||
@@ -146,6 +146,7 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
creditBalance={itemForDisplay.creditBalance}
|
||||
/>
|
||||
)}
|
||||
{itemForDisplay.type === 'model_stats' && (
|
||||
|
||||
@@ -895,7 +895,9 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
completion.isPerfectMatch &&
|
||||
keyMatchers[Command.SUBMIT](key) &&
|
||||
recentUnsafePasteTime === null &&
|
||||
(!completion.showSuggestions || completion.activeSuggestionIndex <= 0)
|
||||
(!completion.showSuggestions ||
|
||||
(completion.activeSuggestionIndex <= 0 &&
|
||||
!hasUserNavigatedSuggestions.current))
|
||||
) {
|
||||
handleSubmit(buffer.text);
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { waitFor } from '../../test-utils/async.js';
|
||||
import { act } from 'react';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { OverageMenuDialog } from './OverageMenuDialog.js';
|
||||
|
||||
const writeKey = (stdin: { write: (data: string) => void }, key: string) => {
|
||||
act(() => {
|
||||
stdin.write(key);
|
||||
});
|
||||
};
|
||||
|
||||
describe('OverageMenuDialog', () => {
|
||||
const mockOnChoice = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
it('should match snapshot with fallback available', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<OverageMenuDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
fallbackModel="gemini-3-flash-preview"
|
||||
resetTime="2:00 PM"
|
||||
creditBalance={500}
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should match snapshot without fallback', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<OverageMenuDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
creditBalance={500}
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should display the credit balance', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<OverageMenuDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
creditBalance={200}
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
const output = lastFrame() ?? '';
|
||||
expect(output).toContain('200');
|
||||
expect(output).toContain('AI Credits available');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should display the model name', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<OverageMenuDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
creditBalance={100}
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
const output = lastFrame() ?? '';
|
||||
expect(output).toContain('gemini-2.5-pro');
|
||||
expect(output).toContain('Usage limit reached');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should display reset time when provided', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<OverageMenuDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
resetTime="3:45 PM"
|
||||
creditBalance={100}
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
const output = lastFrame() ?? '';
|
||||
expect(output).toContain('3:45 PM');
|
||||
expect(output).toContain('Access resets at');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should not display reset time when not provided', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<OverageMenuDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
creditBalance={100}
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
const output = lastFrame() ?? '';
|
||||
expect(output).not.toContain('Access resets at');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should display slash command hints', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<OverageMenuDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
creditBalance={100}
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
const output = lastFrame() ?? '';
|
||||
expect(output).toContain('/stats');
|
||||
expect(output).toContain('/model');
|
||||
expect(output).toContain('/auth');
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onChoice handling', () => {
|
||||
it('should call onChoice with use_credits when selected', async () => {
|
||||
// use_credits is the first item, so just press Enter
|
||||
const { unmount, stdin, waitUntilReady } = renderWithProviders(
|
||||
<OverageMenuDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
creditBalance={100}
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
writeKey(stdin, '\r');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnChoice).toHaveBeenCalledWith('use_credits');
|
||||
});
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should call onChoice with manage when selected', async () => {
|
||||
// manage is the second item: Down + Enter
|
||||
const { unmount, stdin, waitUntilReady } = renderWithProviders(
|
||||
<OverageMenuDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
creditBalance={100}
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
writeKey(stdin, '\x1b[B'); // Down arrow
|
||||
writeKey(stdin, '\r');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnChoice).toHaveBeenCalledWith('manage');
|
||||
});
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should call onChoice with use_fallback when selected', async () => {
|
||||
// With fallback: items are [use_credits, manage, use_fallback, stop]
|
||||
// use_fallback is the third item: Down x2 + Enter
|
||||
const { unmount, stdin, waitUntilReady } = renderWithProviders(
|
||||
<OverageMenuDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
fallbackModel="gemini-3-flash-preview"
|
||||
creditBalance={100}
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
writeKey(stdin, '\x1b[B'); // Down arrow
|
||||
writeKey(stdin, '\x1b[B'); // Down arrow
|
||||
writeKey(stdin, '\r');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnChoice).toHaveBeenCalledWith('use_fallback');
|
||||
});
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should call onChoice with stop when selected', async () => {
|
||||
// Without fallback: items are [use_credits, manage, stop]
|
||||
// stop is the third item: Down x2 + Enter
|
||||
const { unmount, stdin, waitUntilReady } = renderWithProviders(
|
||||
<OverageMenuDialog
|
||||
failedModel="gemini-2.5-pro"
|
||||
creditBalance={100}
|
||||
onChoice={mockOnChoice}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
writeKey(stdin, '\x1b[B'); // Down arrow
|
||||
writeKey(stdin, '\x1b[B'); // Down arrow
|
||||
writeKey(stdin, '\r');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnChoice).toHaveBeenCalledWith('stop');
|
||||
});
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
||||
/** Available choices in the overage menu dialog */
|
||||
export type OverageMenuChoice =
|
||||
| 'use_credits'
|
||||
| 'use_fallback'
|
||||
| 'manage'
|
||||
| 'stop';
|
||||
|
||||
interface OverageMenuDialogProps {
|
||||
/** The model that hit the quota limit */
|
||||
failedModel: string;
|
||||
/** The fallback model to offer (omit if none available) */
|
||||
fallbackModel?: string;
|
||||
/** Time when access resets (human-readable) */
|
||||
resetTime?: string;
|
||||
/** Available G1 AI credit balance */
|
||||
creditBalance: number;
|
||||
/** Callback when user makes a selection */
|
||||
onChoice: (choice: OverageMenuChoice) => void;
|
||||
}
|
||||
|
||||
export function OverageMenuDialog({
|
||||
failedModel,
|
||||
fallbackModel,
|
||||
resetTime,
|
||||
creditBalance,
|
||||
onChoice,
|
||||
}: OverageMenuDialogProps): React.JSX.Element {
|
||||
const items: Array<{
|
||||
label: string;
|
||||
value: OverageMenuChoice;
|
||||
key: string;
|
||||
}> = [
|
||||
{
|
||||
label: 'Use AI Credits - Continue this request (Overage)',
|
||||
value: 'use_credits',
|
||||
key: 'use_credits',
|
||||
},
|
||||
{
|
||||
label: 'Manage - View balance and purchase more credits',
|
||||
value: 'manage',
|
||||
key: 'manage',
|
||||
},
|
||||
];
|
||||
|
||||
if (fallbackModel) {
|
||||
items.push({
|
||||
label: `Switch to ${fallbackModel}`,
|
||||
value: 'use_fallback',
|
||||
key: 'use_fallback',
|
||||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
label: 'Stop - Abort request',
|
||||
value: 'stop',
|
||||
key: 'stop',
|
||||
});
|
||||
|
||||
return (
|
||||
<Box borderStyle="round" flexDirection="column" padding={1}>
|
||||
<Box marginBottom={1} flexDirection="column">
|
||||
<Text color={theme.status.warning}>
|
||||
Usage limit reached for {failedModel}.
|
||||
</Text>
|
||||
{resetTime && <Text>Access resets at {resetTime}.</Text>}
|
||||
<Text>
|
||||
<Text bold color={theme.text.accent}>
|
||||
/stats
|
||||
</Text>{' '}
|
||||
model for usage details
|
||||
</Text>
|
||||
<Text>
|
||||
<Text bold color={theme.text.accent}>
|
||||
/model
|
||||
</Text>{' '}
|
||||
to switch models.
|
||||
</Text>
|
||||
<Text>
|
||||
<Text bold color={theme.text.accent}>
|
||||
/auth
|
||||
</Text>{' '}
|
||||
to switch to API key.
|
||||
</Text>
|
||||
</Box>
|
||||
<Box marginBottom={1}>
|
||||
<Text>
|
||||
You have{' '}
|
||||
<Text bold color={theme.status.success}>
|
||||
{creditBalance}
|
||||
</Text>{' '}
|
||||
AI Credits available.
|
||||
</Text>
|
||||
</Box>
|
||||
<Box marginBottom={1}>
|
||||
<Text>How would you like to proceed?</Text>
|
||||
</Box>
|
||||
<Box marginTop={1} marginBottom={1}>
|
||||
<RadioButtonSelect items={items} onSelect={onChoice} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -395,6 +395,7 @@ interface StatsDisplayProps {
|
||||
tier?: string;
|
||||
currentModel?: string;
|
||||
quotaStats?: QuotaStats;
|
||||
creditBalance?: number;
|
||||
}
|
||||
|
||||
export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
||||
@@ -407,6 +408,7 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
||||
tier,
|
||||
currentModel,
|
||||
quotaStats,
|
||||
creditBalance,
|
||||
}) => {
|
||||
const { stats } = useSessionStats();
|
||||
const { metrics } = stats;
|
||||
@@ -488,6 +490,17 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
||||
<Text color={theme.text.primary}>{tier}</Text>
|
||||
</StatRow>
|
||||
)}
|
||||
{showUserIdentity && creditBalance != null && creditBalance >= 0 && (
|
||||
<StatRow title="Google AI Credits:">
|
||||
<Text
|
||||
color={
|
||||
creditBalance > 0 ? theme.text.primary : theme.text.secondary
|
||||
}
|
||||
>
|
||||
{creditBalance.toLocaleString()}
|
||||
</Text>
|
||||
</StatRow>
|
||||
)}
|
||||
<StatRow title="Tool Calls:">
|
||||
<Text color={theme.text.primary}>
|
||||
{tools.totalCalls} ({' '}
|
||||
|
||||
@@ -26,7 +26,6 @@ export const StickyHeader: React.FC<StickyHeaderProps> = ({
|
||||
containerRef,
|
||||
}) => (
|
||||
<Box
|
||||
ref={containerRef}
|
||||
sticky
|
||||
minHeight={1}
|
||||
flexShrink={0}
|
||||
@@ -58,6 +57,7 @@ export const StickyHeader: React.FC<StickyHeaderProps> = ({
|
||||
}
|
||||
>
|
||||
<Box
|
||||
ref={containerRef}
|
||||
borderStyle="round"
|
||||
width={width}
|
||||
borderColor={borderColor}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`EmptyWalletDialog > rendering > should match snapshot with fallback available 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ Usage limit reached for gemini-2.5-pro. │
|
||||
│ Access resets at 2:00 PM. │
|
||||
│ /stats model for usage details │
|
||||
│ /model to switch models. │
|
||||
│ /auth to switch to API key. │
|
||||
│ │
|
||||
│ To continue using this model now, purchase more AI Credits. │
|
||||
│ │
|
||||
│ Newly purchased AI credits may take a few minutes to update. │
|
||||
│ │
|
||||
│ How would you like to proceed? │
|
||||
│ │
|
||||
│ │
|
||||
│ ● 1. Get AI Credits - Open browser to purchase credits │
|
||||
│ 2. Switch to gemini-3-flash-preview │
|
||||
│ 3. Stop - Abort request │
|
||||
│ │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`EmptyWalletDialog > rendering > should match snapshot without fallback 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ Usage limit reached for gemini-2.5-pro. │
|
||||
│ /stats model for usage details │
|
||||
│ /model to switch models. │
|
||||
│ /auth to switch to API key. │
|
||||
│ │
|
||||
│ To continue using this model now, purchase more AI Credits. │
|
||||
│ │
|
||||
│ Newly purchased AI credits may take a few minutes to update. │
|
||||
│ │
|
||||
│ How would you like to proceed? │
|
||||
│ │
|
||||
│ │
|
||||
│ ● 1. Get AI Credits - Open browser to purchase credits │
|
||||
│ 2. Stop - Abort request │
|
||||
│ │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
@@ -0,0 +1,47 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`OverageMenuDialog > rendering > should match snapshot with fallback available 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ Usage limit reached for gemini-2.5-pro. │
|
||||
│ Access resets at 2:00 PM. │
|
||||
│ /stats model for usage details │
|
||||
│ /model to switch models. │
|
||||
│ /auth to switch to API key. │
|
||||
│ │
|
||||
│ You have 500 AI Credits available. │
|
||||
│ │
|
||||
│ How would you like to proceed? │
|
||||
│ │
|
||||
│ │
|
||||
│ ● 1. Use AI Credits - Continue this request (Overage) │
|
||||
│ 2. Manage - View balance and purchase more credits │
|
||||
│ 3. Switch to gemini-3-flash-preview │
|
||||
│ 4. Stop - Abort request │
|
||||
│ │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`OverageMenuDialog > rendering > should match snapshot without fallback 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ Usage limit reached for gemini-2.5-pro. │
|
||||
│ /stats model for usage details │
|
||||
│ /model to switch models. │
|
||||
│ /auth to switch to API key. │
|
||||
│ │
|
||||
│ You have 500 AI Credits available. │
|
||||
│ │
|
||||
│ How would you like to proceed? │
|
||||
│ │
|
||||
│ │
|
||||
│ ● 1. Use AI Credits - Continue this request (Overage) │
|
||||
│ 2. Manage - View balance and purchase more credits │
|
||||
│ 3. Stop - Abort request │
|
||||
│ │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
@@ -78,7 +78,7 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
|
||||
embeddedShellFocused,
|
||||
);
|
||||
|
||||
const { setEmbeddedShellFocused } = useUIActions();
|
||||
const { setEmbeddedShellFocused, setActivePtyId } = useUIActions();
|
||||
const wasFocusedRef = React.useRef(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -102,13 +102,14 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
|
||||
|
||||
const handleFocus = () => {
|
||||
if (isThisShellFocusable) {
|
||||
setActivePtyId(ptyId ?? null);
|
||||
setEmbeddedShellFocused(true);
|
||||
}
|
||||
};
|
||||
|
||||
useMouseClick(headerRef, handleFocus, { isActive: !!isThisShellFocusable });
|
||||
useMouseClick(headerRef, handleFocus, { isActive: true });
|
||||
|
||||
useMouseClick(contentRef, handleFocus, { isActive: !!isThisShellFocusable });
|
||||
useMouseClick(contentRef, handleFocus, { isActive: true });
|
||||
|
||||
const { shouldShowFocusHint } = useFocusHint(
|
||||
isThisShellFocusable,
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
GLOB_DISPLAY_NAME,
|
||||
} from '@google/gemini-cli-core';
|
||||
import os from 'node:os';
|
||||
import { createMockSettings } from '../../../test-utils/settings.js';
|
||||
|
||||
describe('<ToolGroupMessage />', () => {
|
||||
afterEach(() => {
|
||||
@@ -64,6 +65,11 @@ describe('<ToolGroupMessage />', () => {
|
||||
ideMode: false,
|
||||
enableInteractiveShell: true,
|
||||
});
|
||||
const fullVerbositySettings = createMockSettings({
|
||||
merged: {
|
||||
ui: { errorVerbosity: 'full' },
|
||||
},
|
||||
});
|
||||
|
||||
describe('Golden Snapshots', () => {
|
||||
it('renders single successful tool call', async () => {
|
||||
@@ -73,6 +79,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
uiState: {
|
||||
pendingHistoryItems: [
|
||||
{
|
||||
@@ -104,7 +111,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||
{ config: baseMockConfig },
|
||||
{ config: baseMockConfig, settings: fullVerbositySettings },
|
||||
);
|
||||
|
||||
// Should render nothing because all tools in the group are confirming
|
||||
@@ -140,6 +147,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
uiState: {
|
||||
pendingHistoryItems: [
|
||||
{
|
||||
@@ -160,6 +168,76 @@ describe('<ToolGroupMessage />', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('hides errored tool calls in low error verbosity mode', async () => {
|
||||
const toolCalls = [
|
||||
createToolCall({
|
||||
callId: 'tool-1',
|
||||
name: 'successful-tool',
|
||||
status: CoreToolCallStatus.Success,
|
||||
}),
|
||||
createToolCall({
|
||||
callId: 'tool-2',
|
||||
name: 'error-tool',
|
||||
status: CoreToolCallStatus.Error,
|
||||
resultDisplay: 'Tool failed',
|
||||
}),
|
||||
];
|
||||
const item = createItem(toolCalls);
|
||||
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
uiState: {
|
||||
pendingHistoryItems: [
|
||||
{
|
||||
type: 'tool_group',
|
||||
tools: toolCalls,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
||||
await waitUntilReady();
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('successful-tool');
|
||||
expect(output).not.toContain('error-tool');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('keeps client-initiated errored tool calls visible in low error verbosity mode', async () => {
|
||||
const toolCalls = [
|
||||
createToolCall({
|
||||
callId: 'tool-1',
|
||||
name: 'client-error-tool',
|
||||
status: CoreToolCallStatus.Error,
|
||||
isClientInitiated: true,
|
||||
resultDisplay: 'Client tool failed',
|
||||
}),
|
||||
];
|
||||
const item = createItem(toolCalls);
|
||||
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
uiState: {
|
||||
pendingHistoryItems: [
|
||||
{
|
||||
type: 'tool_group',
|
||||
tools: toolCalls,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await waitUntilReady();
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('client-error-tool');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders mixed tool calls including shell command', async () => {
|
||||
const toolCalls = [
|
||||
createToolCall({
|
||||
@@ -187,6 +265,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
uiState: {
|
||||
pendingHistoryItems: [
|
||||
{
|
||||
@@ -233,6 +312,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
/>,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
uiState: {
|
||||
pendingHistoryItems: [
|
||||
{
|
||||
@@ -266,6 +346,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
/>,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
uiState: {
|
||||
pendingHistoryItems: [
|
||||
{
|
||||
@@ -288,6 +369,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
uiState: {
|
||||
pendingHistoryItems: [
|
||||
{
|
||||
@@ -326,6 +408,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
</Scrollable>,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
uiState: {
|
||||
pendingHistoryItems: [
|
||||
{
|
||||
@@ -356,6 +439,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
uiState: {
|
||||
pendingHistoryItems: [
|
||||
{
|
||||
@@ -406,6 +490,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
</Scrollable>,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
uiState: {
|
||||
pendingHistoryItems: [
|
||||
{
|
||||
@@ -439,6 +524,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
uiState: {
|
||||
pendingHistoryItems: [
|
||||
{
|
||||
@@ -468,6 +554,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
uiState: {
|
||||
pendingHistoryItems: [
|
||||
{
|
||||
@@ -510,6 +597,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
/>,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
uiState: {
|
||||
pendingHistoryItems: [
|
||||
{
|
||||
@@ -569,7 +657,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||
{ config: baseMockConfig },
|
||||
{ config: baseMockConfig, settings: fullVerbositySettings },
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
@@ -599,7 +687,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||
{ config: baseMockConfig },
|
||||
{ config: baseMockConfig, settings: fullVerbositySettings },
|
||||
);
|
||||
|
||||
await waitUntilReady();
|
||||
@@ -627,7 +715,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
toolCalls={toolCalls}
|
||||
borderBottom={false}
|
||||
/>,
|
||||
{ config: baseMockConfig },
|
||||
{ config: baseMockConfig, settings: fullVerbositySettings },
|
||||
);
|
||||
// AskUser tools in progress are rendered by AskUserDialog, so we expect nothing.
|
||||
await waitUntilReady();
|
||||
@@ -665,7 +753,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
|
||||
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
|
||||
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||
{ config: baseMockConfig },
|
||||
{ config: baseMockConfig, settings: fullVerbositySettings },
|
||||
);
|
||||
|
||||
await waitUntilReady();
|
||||
@@ -697,6 +785,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
/>,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
useAlternateBuffer: true,
|
||||
uiState: {
|
||||
constrainHeight: true,
|
||||
@@ -728,6 +817,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
/>,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
useAlternateBuffer: true,
|
||||
uiState: {
|
||||
constrainHeight: true,
|
||||
@@ -760,6 +850,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
/>,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
useAlternateBuffer: true,
|
||||
uiState: {
|
||||
constrainHeight: true,
|
||||
@@ -791,6 +882,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
/>,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
useAlternateBuffer: true,
|
||||
uiState: {
|
||||
constrainHeight: true,
|
||||
@@ -818,6 +910,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
/>,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
useAlternateBuffer: true,
|
||||
uiState: {
|
||||
constrainHeight: false,
|
||||
@@ -850,6 +943,7 @@ describe('<ToolGroupMessage />', () => {
|
||||
/>,
|
||||
{
|
||||
config: baseMockConfig,
|
||||
settings: fullVerbositySettings,
|
||||
useAlternateBuffer: true,
|
||||
uiState: {
|
||||
constrainHeight: true,
|
||||
|
||||
@@ -18,7 +18,10 @@ import { ShellToolMessage } from './ShellToolMessage.js';
|
||||
import { theme } from '../../semantic-colors.js';
|
||||
import { useConfig } from '../../contexts/ConfigContext.js';
|
||||
import { isShellTool, isThisShellFocused } from './ToolShared.js';
|
||||
import { shouldHideToolCall } from '@google/gemini-cli-core';
|
||||
import {
|
||||
shouldHideToolCall,
|
||||
CoreToolCallStatus,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { ShowMoreLines } from '../ShowMoreLines.js';
|
||||
import { useUIState } from '../../contexts/UIStateContext.js';
|
||||
import { useAlternateBuffer } from '../../hooks/useAlternateBuffer.js';
|
||||
@@ -27,6 +30,7 @@ import {
|
||||
calculateToolContentMaxLines,
|
||||
} from '../../utils/toolLayoutUtils.js';
|
||||
import { getToolGroupBorderAppearance } from '../../utils/borderStyles.js';
|
||||
import { useSettings } from '../../contexts/SettingsContext.js';
|
||||
|
||||
interface ToolGroupMessageProps {
|
||||
item: HistoryItem | HistoryItemWithoutId;
|
||||
@@ -51,19 +55,29 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
|
||||
borderBottom: borderBottomOverride,
|
||||
isExpandable,
|
||||
}) => {
|
||||
const settings = useSettings();
|
||||
const isLowErrorVerbosity = settings.merged.ui?.errorVerbosity !== 'full';
|
||||
|
||||
// Filter out tool calls that should be hidden (e.g. in-progress Ask User, or Plan Mode operations).
|
||||
const toolCalls = useMemo(
|
||||
() =>
|
||||
allToolCalls.filter(
|
||||
(t) =>
|
||||
!shouldHideToolCall({
|
||||
displayName: t.name,
|
||||
status: t.status,
|
||||
approvalMode: t.approvalMode,
|
||||
hasResultDisplay: !!t.resultDisplay,
|
||||
}),
|
||||
),
|
||||
[allToolCalls],
|
||||
allToolCalls.filter((t) => {
|
||||
if (
|
||||
isLowErrorVerbosity &&
|
||||
t.status === CoreToolCallStatus.Error &&
|
||||
!t.isClientInitiated
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !shouldHideToolCall({
|
||||
displayName: t.name,
|
||||
status: t.status,
|
||||
approvalMode: t.approvalMode,
|
||||
hasResultDisplay: !!t.resultDisplay,
|
||||
});
|
||||
}),
|
||||
[allToolCalls, isLowErrorVerbosity],
|
||||
);
|
||||
|
||||
const config = useConfig();
|
||||
|
||||
@@ -50,10 +50,14 @@ describe('ToolResultDisplay Overflow', () => {
|
||||
|
||||
await waitUntilReady();
|
||||
|
||||
// ResizeObserver might take a tick, though ToolGroupMessage calculates overflow synchronously
|
||||
// In ASB mode the overflow hint can render before the scroll position
|
||||
// settles. Wait for both the hint and the tail of the content so this
|
||||
// snapshot is deterministic across slower CI runners.
|
||||
await waitFor(() => {
|
||||
const frame = lastFrame();
|
||||
expect(frame.toLowerCase()).toContain('press ctrl+o to show more lines');
|
||||
expect(frame).toBeDefined();
|
||||
expect(frame?.toLowerCase()).toContain('press ctrl+o to show more lines');
|
||||
expect(frame).toContain('line 50');
|
||||
});
|
||||
|
||||
const frame = lastFrame();
|
||||
|
||||
@@ -16,12 +16,11 @@ import {
|
||||
} from '../../constants.js';
|
||||
import { theme } from '../../semantic-colors.js';
|
||||
import {
|
||||
type Config,
|
||||
SHELL_TOOL_NAME,
|
||||
isCompletedAskUserTool,
|
||||
type ToolResultDisplay,
|
||||
CoreToolCallStatus,
|
||||
isCompletedAskUserTool
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { Config, ToolResultDisplay ,
|
||||
CoreToolCallStatus} from '@google/gemini-cli-core';
|
||||
import { useInactivityTimer } from '../../hooks/useInactivityTimer.js';
|
||||
import { formatCommand } from '../../utils/keybindingUtils.js';
|
||||
import { Command } from '../../../config/keyBindings.js';
|
||||
@@ -44,14 +43,10 @@ export function isShellTool(name: string): boolean {
|
||||
*/
|
||||
export function isThisShellFocusable(
|
||||
name: string,
|
||||
status: CoreToolCallStatus,
|
||||
config?: Config,
|
||||
_status: CoreToolCallStatus,
|
||||
_config?: Config,
|
||||
): boolean {
|
||||
return !!(
|
||||
isShellTool(name) &&
|
||||
status === CoreToolCallStatus.Executing &&
|
||||
config?.getEnableInteractiveShell()
|
||||
);
|
||||
return isShellTool(name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,14 +54,13 @@ export function isThisShellFocusable(
|
||||
*/
|
||||
export function isThisShellFocused(
|
||||
name: string,
|
||||
status: CoreToolCallStatus,
|
||||
_status: CoreToolCallStatus,
|
||||
ptyId?: number,
|
||||
activeShellPtyId?: number | null,
|
||||
embeddedShellFocused?: boolean,
|
||||
): boolean {
|
||||
return !!(
|
||||
isShellTool(name) &&
|
||||
status === CoreToolCallStatus.Executing &&
|
||||
ptyId === activeShellPtyId &&
|
||||
embeddedShellFocused
|
||||
);
|
||||
|
||||
@@ -5,12 +5,20 @@
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { useState, useRef, useCallback, useMemo, useLayoutEffect } from 'react';
|
||||
import { Box, ResizeObserver, type DOMElement } from 'ink';
|
||||
import {
|
||||
useState,
|
||||
useRef,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useLayoutEffect,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import { Box, ResizeObserver, getBoundingBox, type DOMElement } from 'ink';
|
||||
import { useKeypress, type Key } from '../../hooks/useKeypress.js';
|
||||
import { useScrollable } from '../../contexts/ScrollProvider.js';
|
||||
import { useAnimatedScrollbar } from '../../hooks/useAnimatedScrollbar.js';
|
||||
import { useBatchedScroll } from '../../hooks/useBatchedScroll.js';
|
||||
import { useMouse, type MouseEvent } from '../../hooks/useMouse.js';
|
||||
import { keyMatchers, Command } from '../../keyMatchers.js';
|
||||
|
||||
interface ScrollableProps {
|
||||
@@ -115,6 +123,30 @@ export const Scrollable: React.FC<ScrollableProps> = ({
|
||||
[scrollToBottom],
|
||||
);
|
||||
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
useMouse(
|
||||
(event: MouseEvent) => {
|
||||
if (event.name === 'move' && viewportRef.current) {
|
||||
const boundingBox = getBoundingBox(viewportRef.current);
|
||||
if (boundingBox) {
|
||||
const { x, y, width, height } = boundingBox;
|
||||
const inside =
|
||||
event.col >= x &&
|
||||
event.col < x + width + 1 &&
|
||||
event.row >= y &&
|
||||
event.row < y + height;
|
||||
|
||||
if (inside !== isHovered) {
|
||||
setIsHovered(inside);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
{ isActive: true },
|
||||
);
|
||||
|
||||
const { getScrollTop, setPendingScrollTop } = useBatchedScroll(scrollTop);
|
||||
|
||||
const scrollBy = useCallback(
|
||||
@@ -135,8 +167,20 @@ export const Scrollable: React.FC<ScrollableProps> = ({
|
||||
const { scrollbarColor, flashScrollbar, scrollByWithAnimation } =
|
||||
useAnimatedScrollbar(hasFocus, scrollBy);
|
||||
|
||||
// Flash scrollbar on hover for discoverability.
|
||||
const wasHovered = useRef(isHovered);
|
||||
useEffect(() => {
|
||||
if (isHovered && !wasHovered.current && !hasFocus) {
|
||||
flashScrollbar();
|
||||
}
|
||||
wasHovered.current = isHovered;
|
||||
}, [isHovered, hasFocus, flashScrollbar]);
|
||||
|
||||
useKeypress(
|
||||
(key: Key) => {
|
||||
if (!hasFocus) {
|
||||
return false;
|
||||
}
|
||||
const { scrollHeight, innerHeight } = sizeRef.current;
|
||||
const scrollTop = getScrollTop();
|
||||
const maxScroll = Math.max(0, scrollHeight - innerHeight);
|
||||
@@ -175,13 +219,20 @@ export const Scrollable: React.FC<ScrollableProps> = ({
|
||||
);
|
||||
|
||||
const getScrollState = useCallback(() => {
|
||||
if (!hasFocus && !isHovered) {
|
||||
return {
|
||||
scrollTop: 0,
|
||||
scrollHeight: 0,
|
||||
innerHeight: 0,
|
||||
};
|
||||
}
|
||||
const maxScroll = Math.max(0, size.scrollHeight - size.innerHeight);
|
||||
return {
|
||||
scrollTop: Math.min(getScrollTop(), maxScroll),
|
||||
scrollHeight: size.scrollHeight,
|
||||
innerHeight: size.innerHeight,
|
||||
};
|
||||
}, [getScrollTop, size.scrollHeight, size.innerHeight]);
|
||||
}, [hasFocus, isHovered, getScrollTop, size.scrollHeight, size.innerHeight]);
|
||||
|
||||
const hasFocusCallback = useCallback(() => hasFocus, [hasFocus]);
|
||||
|
||||
@@ -206,11 +257,11 @@ export const Scrollable: React.FC<ScrollableProps> = ({
|
||||
width={width ?? maxWidth}
|
||||
height={height}
|
||||
flexDirection="column"
|
||||
overflowY="scroll"
|
||||
overflowY={hasFocus || isHovered ? 'scroll' : 'hidden'}
|
||||
overflowX="hidden"
|
||||
scrollTop={scrollTop}
|
||||
flexGrow={flexGrow}
|
||||
scrollbarThumbColor={scrollbarColor}
|
||||
scrollbarThumbColor={hasFocus || isHovered ? scrollbarColor : undefined}
|
||||
>
|
||||
{/*
|
||||
This inner box is necessary to prevent the parent from shrinking
|
||||
|
||||
@@ -47,6 +47,7 @@ describe('McpStatus', () => {
|
||||
isPersistentDisabled: false,
|
||||
},
|
||||
},
|
||||
errors: {},
|
||||
discoveryInProgress: false,
|
||||
connectingServers: [],
|
||||
showDescriptions: true,
|
||||
@@ -208,6 +209,18 @@ describe('McpStatus', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders correctly with a server error', async () => {
|
||||
const { lastFrame, unmount, waitUntilReady } = render(
|
||||
<McpStatus
|
||||
{...baseProps}
|
||||
errors={{ 'server-1': 'Failed to connect to server' }}
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('truncates resources when exceeding limit', async () => {
|
||||
const manyResources = Array.from({ length: 25 }, (_, i) => ({
|
||||
serverName: 'server-1',
|
||||
|
||||
@@ -26,6 +26,7 @@ interface McpStatusProps {
|
||||
serverStatus: (serverName: string) => MCPServerStatus;
|
||||
authStatus: HistoryItemMcpStatus['authStatus'];
|
||||
enablementState: HistoryItemMcpStatus['enablementState'];
|
||||
errors: Record<string, string>;
|
||||
discoveryInProgress: boolean;
|
||||
connectingServers: string[];
|
||||
showDescriptions: boolean;
|
||||
@@ -41,6 +42,7 @@ export const McpStatus: React.FC<McpStatusProps> = ({
|
||||
serverStatus,
|
||||
authStatus,
|
||||
enablementState,
|
||||
errors,
|
||||
discoveryInProgress,
|
||||
connectingServers,
|
||||
showDescriptions,
|
||||
@@ -195,6 +197,14 @@ export const McpStatus: React.FC<McpStatusProps> = ({
|
||||
<Text> ({toolCount} tools cached)</Text>
|
||||
)}
|
||||
|
||||
{errors[serverName] && (
|
||||
<Box marginLeft={2}>
|
||||
<Text color={theme.status.error}>
|
||||
Error: {errors[serverName]}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{showDescriptions && server?.description && (
|
||||
<Text color={theme.text.secondary}>
|
||||
{server.description.trim()}
|
||||
|
||||
@@ -60,6 +60,18 @@ A test server
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`McpStatus > renders correctly with a server error 1`] = `
|
||||
"Configured MCP servers:
|
||||
|
||||
🟢 server-1 - Ready (1 tool)
|
||||
Error: Failed to connect to server
|
||||
A test server
|
||||
Tools:
|
||||
- tool-1
|
||||
A test tool
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`McpStatus > renders correctly with authenticated OAuth status 1`] = `
|
||||
"Configured MCP servers:
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user