Compare commits

...

12 Commits

Author SHA1 Message Date
gemini-cli-robot 54c90d73f1 chore(release): v0.19.4 2025-12-05 18:08:09 +00:00
gemini-cli-robot 9351148754 fix(patch): cherry-pick fcb85e6 to release/v0.19.3-pr-14092 to patch version v0.19.3 and create version 0.19.4 (#14588)
Co-authored-by: Brian de Alwis <bsd@acm.org>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-12-05 17:45:59 +00:00
gemini-cli-robot de13a2ccb4 chore(release): v0.19.3 2025-12-05 07:34:15 +00:00
gemini-cli-robot ee6b01f9c7 fix(patch): cherry-pick 934b309 to release/v0.19.2-pr-14566 to patch version v0.19.2 and create version 0.19.3 (#14571)
Co-authored-by: Jacob Richman <jacob314@gmail.com>
2025-12-05 07:03:08 +00:00
gemini-cli-robot a35d001fde chore(release): v0.19.2 2025-12-05 04:51:01 +00:00
gemini-cli-robot 95f9032bf4 fix(patch): cherry-pick d284fa6 to release/v0.19.1-pr-14545 [CONFLICTS] (#14558)
Co-authored-by: Jacob Richman <jacob314@gmail.com>
2025-12-04 20:33:55 -08:00
gemini-cli-robot 578c497417 chore(release): v0.19.1 2025-12-03 02:10:47 +00:00
gemini-cli-robot 6169ef04ba fix(patch): cherry-pick bde8b78 to release/v0.19.0-pr-14301 to patch version v0.19.0 and create version 0.19.1 (#14418)
Co-authored-by: Jack Wotherspoon <jackwoth@google.com>
Co-authored-by: jacob314 <jacob314@gmail.com>
2025-12-02 17:54:15 -08:00
gemini-cli-robot 933e0dc8ec chore(release): v0.19.0 2025-12-02 23:18:39 +00:00
gemini-cli-robot 90a5dc3da5 chore(release): v0.19.0-preview.1 2025-12-02 22:26:26 +00:00
gemini-cli-robot 403d29c63c fix(patch): cherry-pick 576fda1 to release/v0.19.0-preview.0-pr-14099 [CONFLICTS] (#14402)
Co-authored-by: Jack Wotherspoon <jackwoth@google.com>
2025-12-02 13:41:09 -08:00
gemini-cli-robot e1ea24806e chore(release): v0.19.0-preview.0 2025-11-26 02:44:48 +00:00
18 changed files with 594 additions and 128 deletions
+54
View File
@@ -0,0 +1,54 @@
description="Injects context of all relevant cli files"
prompt = """
The source code contains the content of absolutely every source code file in
packages/cli. In addition to the source code, the following test files are
included as they are test files that conform to the project's testing standards:
`packages/cli/src/ui/components/InputPrompt.test.tsx` and `packages/cli/src/ui/App.test.tsx`.
You should very rarely need to read any other files from packages/cli to resolve
prompts.
!{find packages/cli -path packages/cli/dist -prune -o -type f \\( -name "*.ts" -o -name "*.tsx" \\) ! -name "*.test.ts" ! -name "*.test.tsx" ! -name "*.d.ts" -exec echo "--- {} ---" \\; -exec cat {} \\; && echo "--- packages/cli/src/ui/components/InputPrompt.test.tsx ---" && cat packages/cli/src/ui/components/InputPrompt.test.tsx && echo "--- packages/cli/src/ui/App.test.tsx ---" && cat packages/cli/src/ui/App.test.tsx}
**Pay extremely close attention to these files.** They define the project's
core architecture, component patterns, and testing standards.
In addition to the code context, you MUST strictly adhere to the following frontend-specific development guidelines while adding code to packages/cli.
## Testing Standards
* **Async Testing**: ALWAYS use `waitFor` from `packages/cli/src/test-utils/async.ts` instead of `vi.waitFor` to prevent flakiness and `act` warnings. NEVER use fixed waits (e.g., `await delay(100)`).
* **State Changes**: Wrap all blocks that change component state in `act`.
* **Snapshots**: Use `toMatchSnapshot` to verify rendering.
* **Rendering**: Use `render` or `renderWithProviders` from `packages/cli/src/test-utils/render.tsx` instead of `ink-testing-library` directly.
* **Mocking**:
* Reuse existing mocks/fakes where possible.
* **Parameterized Tests**: Use parameterized tests with explicit types to reduce duplication and ensure type safety.
* Avoid mocking the file system, os, or child_process if at all possible; if you have to mock the Mock critical dependencies (`fs`, `os`, `child_process`) do so ONLY at the top of the file.
## React & Ink Architecture
* **Keyboard Handling**: You MUST use `useKeyPress.ts` from Gemini CLI for keyboard handling, NOT the standard ink library. This is critical for supporting slow terminals and multiple events per frame.
* Handle multiple events gracefully (often requires a reducer pattern, see `text-buffer.ts`).
* **State Management**:
* NEVER trigger side effects from within the body of a `setState` callback. Use a reducer or `useRef` if necessary.
* Initialize state explicitly (e.g., use `undefined` rather than `true` if unknown).
* **Performance**:
* Avoid synchronous file I/O in components.
* Do not introduce excessive property drilling; leverage or extend existing providers.
## Configuration & Settings
* **Settings vs Args**: Use settings for user-configurable options; do not add new CLI arguments.
* **Schema**: Add new settings to `packages/cli/src/config/settingsSchema.ts`.
* **Documentation**:
* If `showInDialog: true`, document in `docs/get-started/configuration.md`.
* Ensure `requiresRestart` is correctly set.
## Keyboard Shortcuts
* **Registration**: Define new shortcuts in `packages/cli/src/config/keyBindings.ts`.
* **Documentation**: Document all new shortcuts in `docs/cli/keyboard-shortcuts.md`.
* **Compatibility**: Avoid function keys and common VSCode shortcuts. Be cautious with `Meta` key (Mac-limited support).
## General
* **Logging**: Use `debugLogger` for errors. NEVER leave `console.log/warn/error` in the code.
* **TypeScript**: Avoid the non-null assertion operator (`!`).
{{args}}.
"""
+57
View File
@@ -0,0 +1,57 @@
description="Injects context of all relevant cli files"
prompt = """
The following output contains the complete
source code of the gemini cli library (`packages/cli`), and {packages/core} and representative test files
(`packages/cli/src/ui/components/InputPrompt.test.tsx` and `packages/cli/src/ui/App.test.tsx`) that best conform to the project's testing standards.
**Pay extremely close attention to these files.** They define the project's
core architecture, component patterns, and testing standards.
The source code contains the content of absolutely every source code file in
packages/cli and packages/core. In addition to the source code, the following test files are
included as they are test files that conform to the project's testing standards:
`packages/cli/src/ui/components/InputPrompt.test.tsx` and `packages/cli/src/ui/App.test.tsx`.
You should very rarely need to read any other files from packages/cli to resolve
prompts.
!{find packages/cli packages/core \\( -path packages/cli/dist -o -path packages/core/dist -o -name node_modules \\) -prune -o -type f \\( -name "*.ts" -o -name "*.tsx" \\) ! -name "*.test.ts" ! -name "*.test.tsx" ! -name "*.d.ts" -exec echo "--- {} ---" \\; -exec cat {} \\; && echo "--- packages/cli/src/ui/components/InputPrompt.test.tsx ---" && cat packages/cli/src/ui/components/InputPrompt.test.tsx && echo "--- packages/cli/src/ui/App.test.tsx ---" && cat packages/cli/src/ui/App.test.tsx}
In addition to the code context, you MUST strictly adhere to the following frontend-specific development guidelines when writing code in packages/cli.
## Testing Standards
* **Async Testing**: ALWAYS use `waitFor` from `packages/cli/src/test-utils/async.ts` instead of `vi.waitFor` to prevent flakiness and `act` warnings. NEVER use fixed waits (e.g., `await delay(100)`).
* **State Changes**: Wrap all blocks that change component state in `act`.
* **Snapshots**: Use `toMatchSnapshot` to verify rendering.
* **Rendering**: Use `render` or `renderWithProviders` from `packages/cli/src/test-utils/render.tsx` instead of `ink-testing-library` directly.
* **Mocking**:
* Reuse existing mocks/fakes where possible.
* **Parameterized Tests**: Use parameterized tests with explicit types to reduce duplication and ensure type safety.
* Avoid mocking the file system, os, or child_process if at all possible; if you have to mock the Mock critical dependencies (`fs`, `os`, `child_process`) do so ONLY at the top of the file.
## React & Ink Architecture
* **Keyboard Handling**: You MUST use `useKeyPress.ts` from Gemini CLI for keyboard handling, NOT the standard ink library. This is critical for supporting slow terminals and multiple events per frame.
* Handle multiple events gracefully (often requires a reducer pattern, see `text-buffer.ts`).
* **State Management**:
* NEVER trigger side effects from within the body of a `setState` callback. Use a reducer or `useRef` if necessary.
* Initialize state explicitly (e.g., use `undefined` rather than `true` if unknown).
* **Performance**:
* Avoid synchronous file I/O in components.
* Do not introduce excessive property drilling; leverage or extend existing providers.
## Configuration & Settings
* **Settings vs Args**: Use settings for user-configurable options; do not add new CLI arguments.
* **Schema**: Add new settings to `packages/cli/src/config/settingsSchema.ts`.
* **Documentation**:
* If `showInDialog: true`, document in `docs/get-started/configuration.md`.
* Ensure `requiresRestart` is correctly set.
## Keyboard Shortcuts
* **Registration**: Define new shortcuts in `packages/cli/src/config/keyBindings.ts`.
* **Documentation**: Document all new shortcuts in `docs/cli/keyboard-shortcuts.md`.
* **Compatibility**: Avoid function keys and common VSCode shortcuts. Be cautious with `Meta` key (Mac-limited support).
## General
* **Logging**: Use `debugLogger` for errors. NEVER leave `console.log/warn/error` in the code.
* **TypeScript**: Avoid the non-null assertion operator (`!`).
{{args}}.
"""
+64 -40
View File
@@ -1,16 +1,17 @@
{
"name": "@google/gemini-cli",
"version": "0.19.0-nightly.20251125.f6d97d448",
"version": "0.19.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@google/gemini-cli",
"version": "0.19.0-nightly.20251125.f6d97d448",
"version": "0.19.4",
"workspaces": [
"packages/*"
],
"dependencies": {
"@types/node": "^24.10.1",
"ink": "npm:@jrichman/ink@6.4.6",
"latest-version": "^9.0.0",
"simple-git": "^3.28.0"
@@ -2407,7 +2408,6 @@
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@octokit/auth-token": "^6.0.0",
"@octokit/graphql": "^9.0.2",
@@ -2588,7 +2588,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=8.0.0"
}
@@ -2622,7 +2621,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz",
"integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/semantic-conventions": "^1.29.0"
},
@@ -2991,7 +2989,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz",
"integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/semantic-conventions": "^1.29.0"
@@ -3025,7 +3022,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz",
"integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/resources": "2.0.1"
@@ -3078,7 +3074,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz",
"integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/resources": "2.0.1",
@@ -4237,12 +4232,12 @@
}
},
"node_modules/@types/node": {
"version": "20.19.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.1.tgz",
"integrity": "sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA==",
"version": "24.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
"undici-types": "~7.16.0"
}
},
"node_modules/@types/normalize-package-data": {
@@ -4289,7 +4284,6 @@
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -4577,7 +4571,6 @@
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.35.0",
"@typescript-eslint/types": "8.35.0",
@@ -5501,7 +5494,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -5937,7 +5929,8 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/array-includes": {
"version": "3.1.9",
@@ -7201,6 +7194,7 @@
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"safe-buffer": "5.2.1"
},
@@ -8216,7 +8210,6 @@
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -8806,6 +8799,7 @@
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
}
@@ -8815,6 +8809,7 @@
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"peer": true,
"dependencies": {
"ms": "2.0.0"
}
@@ -8824,6 +8819,7 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
}
@@ -9077,6 +9073,7 @@
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~2.0.0",
@@ -9095,6 +9092,7 @@
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"peer": true,
"dependencies": {
"ms": "2.0.0"
}
@@ -9103,13 +9101,15 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/finalhandler/node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
}
@@ -10320,7 +10320,6 @@
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.6.tgz",
"integrity": "sha512-QHl6l1cl3zPCaRMzt9TUbTX6Q5SzvkGEZDDad0DmSf5SPmT1/90k6pGPejEvDCJprkitwObXpPaTWGHItqsy4g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@alcalzone/ansi-tokenize": "^0.2.1",
"ansi-escapes": "^7.0.0",
@@ -13428,7 +13427,8 @@
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/path-type": {
"version": "3.0.0",
@@ -13963,7 +13963,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -13974,7 +13973,6 @@
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"shell-quote": "^1.6.1",
"ws": "^7"
@@ -16193,7 +16191,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -16358,8 +16355,7 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD",
"peer": true
"license": "0BSD"
},
"node_modules/tsx": {
"version": "4.20.3",
@@ -16367,7 +16363,6 @@
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "~0.25.0",
"get-tsconfig": "^4.7.5"
@@ -16552,7 +16547,6 @@
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -16627,9 +16621,9 @@
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"license": "MIT"
},
"node_modules/unicorn-magic": {
@@ -16715,6 +16709,7 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.4.0"
}
@@ -16770,7 +16765,6 @@
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -16887,7 +16881,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -16901,7 +16894,6 @@
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4",
@@ -17608,7 +17600,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -17624,7 +17615,7 @@
},
"packages/a2a-server": {
"name": "@google/gemini-cli-a2a-server",
"version": "0.19.0-nightly.20251125.f6d97d448",
"version": "0.19.4",
"dependencies": {
"@a2a-js/sdk": "^0.3.2",
"@google-cloud/storage": "^7.16.0",
@@ -17914,7 +17905,7 @@
},
"packages/cli": {
"name": "@google/gemini-cli",
"version": "0.19.0-nightly.20251125.f6d97d448",
"version": "0.19.4",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
"@google/genai": "1.30.0",
@@ -17981,6 +17972,16 @@
"node": ">=20"
}
},
"packages/cli/node_modules/@types/node": {
"version": "20.19.25",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz",
"integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"packages/cli/node_modules/string-width": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz",
@@ -18013,9 +18014,16 @@
"node": ">=18"
}
},
"packages/cli/node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
"packages/core": {
"name": "@google/gemini-cli-core",
"version": "0.19.0-nightly.20251125.f6d97d448",
"version": "0.19.4",
"dependencies": {
"@google-cloud/logging": "^11.2.1",
"@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0",
@@ -18150,7 +18158,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -18160,7 +18167,7 @@
},
"packages/test-utils": {
"name": "@google/gemini-cli-test-utils",
"version": "0.19.0-nightly.20251125.f6d97d448",
"version": "0.19.4",
"license": "Apache-2.0",
"devDependencies": {
"typescript": "^5.3.3"
@@ -18171,7 +18178,7 @@
},
"packages/vscode-ide-companion": {
"name": "gemini-cli-vscode-ide-companion",
"version": "0.19.0-nightly.20251125.f6d97d448",
"version": "0.19.4",
"license": "LICENSE",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.23.0",
@@ -18197,6 +18204,16 @@
"vscode": "^1.99.0"
}
},
"packages/vscode-ide-companion/node_modules/@types/node": {
"version": "20.19.25",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz",
"integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"packages/vscode-ide-companion/node_modules/@types/vscode": {
"version": "1.99.0",
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.99.0.tgz",
@@ -18325,6 +18342,13 @@
"engines": {
"node": ">= 0.6"
}
},
"packages/vscode-ide-companion/node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
}
}
}
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.19.0-nightly.20251125.f6d97d448",
"version": "0.19.4",
"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.19.0-nightly.20251125.f6d97d448"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.19.4"
},
"scripts": {
"start": "cross-env NODE_ENV=development node scripts/start.js",
@@ -119,6 +119,7 @@
"yargs": "^17.7.2"
},
"dependencies": {
"@types/node": "^24.10.1",
"ink": "npm:@jrichman/ink@6.4.6",
"latest-version": "^9.0.0",
"simple-git": "^3.28.0"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-a2a-server",
"version": "0.19.0-nightly.20251125.f6d97d448",
"version": "0.19.4",
"description": "Gemini CLI A2A Server",
"repository": {
"type": "git",
+1 -1
View File
@@ -658,7 +658,7 @@ export class Task {
errorMessage,
);
let errMessage = 'Unknown error from LLM stream';
let errMessage = `Unknown error from LLM stream: ${JSON.stringify(event)}`;
if (errorEvent.value) {
errMessage = parseAndFormatApiError(errorEvent.value);
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.19.0-nightly.20251125.f6d97d448",
"version": "0.19.4",
"description": "Gemini CLI",
"repository": {
"type": "git",
@@ -25,7 +25,7 @@
"dist"
],
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.19.0-nightly.20251125.f6d97d448"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.19.4"
},
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -138,11 +138,14 @@ const createSession = (overrides: Partial<SessionInfo>): SessionInfo => ({
describe('SessionBrowser component', () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2025-11-01T12:00:00Z'));
keypressHandlers.length = 0;
vi.clearAllMocks();
});
afterEach(() => {
vi.useRealTimers();
vi.restoreAllMocks();
});
@@ -4,13 +4,11 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { render } from '../../test-utils/render.js';
import { renderWithProviders } from '../../test-utils/render.js';
import { waitFor } from '../../test-utils/async.js';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { ThemeDialog } from './ThemeDialog.js';
import { LoadedSettings } from '../../config/settings.js';
import { KeypressProvider } from '../contexts/KeypressContext.js';
import { SettingsContext } from '../contexts/SettingsContext.js';
import { DEFAULT_THEME, themeManager } from '../themes/theme-manager.js';
import { act } from 'react';
@@ -76,12 +74,9 @@ describe('ThemeDialog Snapshots', () => {
it('should render correctly in theme selection mode', () => {
const settings = createMockSettings();
const { lastFrame } = render(
<SettingsContext.Provider value={settings}>
<KeypressProvider>
<ThemeDialog {...baseProps} settings={settings} />
</KeypressProvider>
</SettingsContext.Provider>,
const { lastFrame } = renderWithProviders(
<ThemeDialog {...baseProps} settings={settings} />,
{ settings },
);
expect(lastFrame()).toMatchSnapshot();
@@ -89,12 +84,9 @@ describe('ThemeDialog Snapshots', () => {
it('should render correctly in scope selector mode', async () => {
const settings = createMockSettings();
const { lastFrame, stdin } = render(
<SettingsContext.Provider value={settings}>
<KeypressProvider>
<ThemeDialog {...baseProps} settings={settings} />
</KeypressProvider>
</SettingsContext.Provider>,
const { lastFrame, stdin } = renderWithProviders(
<ThemeDialog {...baseProps} settings={settings} />,
{ settings },
);
// Press Tab to switch to scope selector mode
@@ -111,16 +103,13 @@ describe('ThemeDialog Snapshots', () => {
it('should call onCancel when ESC is pressed', async () => {
const mockOnCancel = vi.fn();
const settings = createMockSettings();
const { stdin } = render(
<SettingsContext.Provider value={settings}>
<KeypressProvider>
<ThemeDialog
{...baseProps}
onCancel={mockOnCancel}
settings={settings}
/>
</KeypressProvider>
</SettingsContext.Provider>,
const { stdin } = renderWithProviders(
<ThemeDialog
{...baseProps}
onCancel={mockOnCancel}
settings={settings}
/>,
{ settings },
);
act(() => {
@@ -131,4 +120,26 @@ describe('ThemeDialog Snapshots', () => {
expect(mockOnCancel).toHaveBeenCalled();
});
});
it('should call refreshStatic when a theme is selected', async () => {
const mockRefreshStatic = vi.fn();
const settings = createMockSettings();
const { stdin } = renderWithProviders(
<ThemeDialog {...baseProps} settings={settings} />,
{
settings,
uiActions: { refreshStatic: mockRefreshStatic },
},
);
// Press Enter to select the theme
act(() => {
stdin.write('\r');
});
await waitFor(() => {
expect(mockRefreshStatic).toHaveBeenCalled();
expect(baseProps.onSelect).toHaveBeenCalled();
});
});
});
@@ -21,6 +21,7 @@ import { getScopeMessageForSetting } from '../../utils/dialogScopeUtils.js';
import { useKeypress } from '../hooks/useKeypress.js';
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
import { ScopeSelector } from './shared/ScopeSelector.js';
import { useUIActions } from '../contexts/UIActionsContext.js';
interface ThemeDialogProps {
/** Callback function when a theme is selected */
@@ -46,6 +47,7 @@ export function ThemeDialog({
terminalWidth,
}: ThemeDialogProps): React.JSX.Element {
const isAlternateBuffer = useAlternateBuffer();
const { refreshStatic } = useUIActions();
const [selectedScope, setSelectedScope] = useState<LoadableSettingScope>(
SettingScope.User,
);
@@ -93,8 +95,9 @@ export function ThemeDialog({
const handleThemeSelect = useCallback(
(themeName: string) => {
onSelect(themeName, selectedScope);
refreshStatic();
},
[onSelect, selectedScope],
[onSelect, selectedScope, refreshStatic],
);
const handleThemeHighlight = (themeName: string) => {
@@ -109,8 +112,9 @@ export function ThemeDialog({
const handleScopeSelect = useCallback(
(scope: LoadableSettingScope) => {
onSelect(highlightedThemeName, scope);
refreshStatic();
},
[onSelect, highlightedThemeName],
[onSelect, highlightedThemeName, refreshStatic],
);
const [mode, setMode] = useState<'theme' | 'scope'>('theme');
@@ -1,38 +1,38 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ThemeDialog Snapshots > should render correctly in scope selector mode 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ > Apply To │
│ ● 1. User Settings │
│ 2. Workspace Settings │
│ 3. System Settings │
│ │
│ (Use Enter to apply scope, Tab to select theme, Esc to close) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
"╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ > Apply To
│ ● 1. User Settings
│ 2. Workspace Settings
│ 3. System Settings
│ (Use Enter to apply scope, Tab to select theme, Esc to close)
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`ThemeDialog Snapshots > should render correctly in theme selection mode 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ > Select Theme Preview
│ ▲ ─────────────────────────────────────────────────┐ │
│ 1. ANSI Dark │ │
│ 2. Atom One Dark │ 1 # function │ │
│ 3. Ayu Dark │ 2 def fibonacci(n): │ │
│ ● 4. Default Dark │ 3 a, b = 0, 1 │ │
│ 5. Dracula Dark │ 4 for _ in range(n): │ │
│ 6. GitHub Dark │ 5 a, b = b, a + b │ │
│ 7. Shades Of Purple Dark │ 6 return a │ │
│ 8. ANSI Light Light │ │ │
│ 9. Ayu Light Light │ 1 - print("Hello, " + name) │ │
│ 10. Default Light Light │ 1 + print(f"Hello, {name}!") │ │
│ 11. GitHub Light Light │ │
│ 12. Google Code Light ─────────────────────────────────────────────────┘ │
│ ▼ │
│ │
│ (Use Enter to select, Tab to configure scope, Esc to close) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
"╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ > Select Theme Preview
│ ▲ ┌────────────────────────────────────────────────────────────┐ │
│ 1. ANSI Dark │ │
│ 2. Atom One Dark │ 1 # function │ │
│ 3. Ayu Dark │ 2 def fibonacci(n): │ │
│ ● 4. Default Dark │ 3 a, b = 0, 1 │ │
│ 5. Dracula Dark │ 4 for _ in range(n): │ │
│ 6. GitHub Dark │ 5 a, b = b, a + b │ │
│ 7. Holiday Dark │ 6 return a │ │
│ 8. Shades Of Purple Dark │ │
│ 9. ANSI Light Light │ 1 - print("Hello, " + name) │ │
│ 10. Ayu Light Light │ 1 + print(f"Hello, {name}!") │ │
│ 11. Default Light Light │ │
│ 12. GitHub Light Light └────────────────────────────────────────────────────────────┘ │
│ ▼
│ (Use Enter to select, Tab to configure scope, Esc to close)
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
+169
View File
@@ -0,0 +1,169 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { type ColorsTheme, Theme } from './theme.js';
import { interpolateColor } from './color-utils.js';
const holidayColors: ColorsTheme = {
type: 'dark',
Background: '#00210e',
Foreground: '#F0F8FF',
LightBlue: '#B0E0E6',
AccentBlue: '#3CB371',
AccentPurple: '#FF9999',
AccentCyan: '#33F9FF',
AccentGreen: '#3CB371',
AccentYellow: '#FFEE8C',
AccentRed: '#FF6347',
DiffAdded: '#2E8B57',
DiffRemoved: '#CD5C5C',
Comment: '#8FBC8F',
Gray: '#D7F5D3',
DarkGray: interpolateColor('#D7F5D3', '#151B18', 0.5),
GradientColors: ['#FF0000', '#FFFFFF', '#008000'],
};
export const Holiday: Theme = new Theme(
'Holiday',
'dark',
{
hljs: {
display: 'block',
overflowX: 'auto',
padding: '0.5em',
background: holidayColors.Background,
color: holidayColors.Foreground,
},
'hljs-keyword': {
color: holidayColors.AccentBlue,
},
'hljs-literal': {
color: holidayColors.AccentBlue,
},
'hljs-symbol': {
color: holidayColors.AccentBlue,
},
'hljs-name': {
color: holidayColors.AccentBlue,
},
'hljs-link': {
color: holidayColors.AccentBlue,
textDecoration: 'underline',
},
'hljs-built_in': {
color: holidayColors.AccentCyan,
},
'hljs-type': {
color: holidayColors.AccentCyan,
},
'hljs-number': {
color: holidayColors.AccentGreen,
},
'hljs-class': {
color: holidayColors.AccentGreen,
},
'hljs-string': {
color: holidayColors.AccentYellow,
},
'hljs-meta-string': {
color: holidayColors.AccentYellow,
},
'hljs-regexp': {
color: holidayColors.AccentRed,
},
'hljs-template-tag': {
color: holidayColors.AccentRed,
},
'hljs-subst': {
color: holidayColors.Foreground,
},
'hljs-function': {
color: holidayColors.Foreground,
},
'hljs-title': {
color: holidayColors.Foreground,
},
'hljs-params': {
color: holidayColors.Foreground,
},
'hljs-formula': {
color: holidayColors.Foreground,
},
'hljs-comment': {
color: holidayColors.Comment,
fontStyle: 'italic',
},
'hljs-quote': {
color: holidayColors.Comment,
fontStyle: 'italic',
},
'hljs-doctag': {
color: holidayColors.Comment,
},
'hljs-meta': {
color: holidayColors.Gray,
},
'hljs-meta-keyword': {
color: holidayColors.Gray,
},
'hljs-tag': {
color: holidayColors.Gray,
},
'hljs-variable': {
color: holidayColors.AccentPurple,
},
'hljs-template-variable': {
color: holidayColors.AccentPurple,
},
'hljs-attr': {
color: holidayColors.LightBlue,
},
'hljs-attribute': {
color: holidayColors.LightBlue,
},
'hljs-builtin-name': {
color: holidayColors.LightBlue,
},
'hljs-section': {
color: holidayColors.AccentYellow,
},
'hljs-emphasis': {
fontStyle: 'italic',
},
'hljs-strong': {
fontWeight: 'bold',
},
'hljs-bullet': {
color: holidayColors.AccentYellow,
},
'hljs-selector-tag': {
color: holidayColors.AccentYellow,
},
'hljs-selector-id': {
color: holidayColors.AccentYellow,
},
'hljs-selector-class': {
color: holidayColors.AccentYellow,
},
'hljs-selector-attr': {
color: holidayColors.AccentYellow,
},
'hljs-selector-pseudo': {
color: holidayColors.AccentYellow,
},
'hljs-addition': {
backgroundColor: holidayColors.DiffAdded,
display: 'inline-block',
width: '100%',
},
'hljs-deletion': {
backgroundColor: holidayColors.DiffRemoved,
display: 'inline-block',
width: '100%',
},
},
holidayColors,
);
@@ -11,6 +11,7 @@ import { Dracula } from './dracula.js';
import { GitHubDark } from './github-dark.js';
import { GitHubLight } from './github-light.js';
import { GoogleCode } from './googlecode.js';
import { Holiday } from './holiday.js';
import { DefaultLight } from './default-light.js';
import { DefaultDark } from './default.js';
import { ShadesOfPurple } from './shades-of-purple.js';
@@ -51,6 +52,7 @@ class ThemeManager {
GitHubDark,
GitHubLight,
GoogleCode,
Holiday,
ShadesOfPurple,
XCode,
ANSI,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-core",
"version": "0.19.0-nightly.20251125.f6d97d448",
"version": "0.19.4",
"description": "Gemini CLI Core",
"repository": {
"type": "git",
@@ -8,9 +8,12 @@ import { vi, describe, it, expect, beforeEach, type Mock } from 'vitest';
import EventEmitter from 'node:events';
import type { Readable } from 'node:stream';
import { type ChildProcess } from 'node:child_process';
import type { ShellOutputEvent } from './shellExecutionService.js';
import type {
ShellOutputEvent,
ShellExecutionConfig,
} from './shellExecutionService.js';
import { ShellExecutionService } from './shellExecutionService.js';
import type { AnsiOutput } from '../utils/terminalSerializer.js';
import type { AnsiOutput, AnsiToken } from '../utils/terminalSerializer.js';
// Hoisted Mocks
const mockPtySpawn = vi.hoisted(() => vi.fn());
@@ -64,7 +67,7 @@ const mockProcessKill = vi
.spyOn(process, 'kill')
.mockImplementation(() => true);
const shellExecutionConfig = {
const shellExecutionConfig: ShellExecutionConfig = {
terminalWidth: 80,
terminalHeight: 24,
pager: 'cat',
@@ -74,21 +77,19 @@ const shellExecutionConfig = {
const createExpectedAnsiOutput = (text: string | string[]): AnsiOutput => {
const lines = Array.isArray(text) ? text : text.split('\n');
const expected: AnsiOutput = Array.from(
{ length: shellExecutionConfig.terminalHeight },
(_, i) => [
{
text: expect.stringMatching((lines[i] || '').trim()),
bold: false,
italic: false,
underline: false,
dim: false,
inverse: false,
fg: '',
bg: '',
},
],
);
const len = (shellExecutionConfig.terminalHeight ?? 24) as number;
const expected: AnsiOutput = Array.from({ length: len }, (_, i) => [
{
text: expect.stringMatching((lines[i] || '').trim()),
bold: false,
italic: false,
underline: false,
dim: false,
inverse: false,
fg: '',
bg: '',
} as AnsiToken,
]);
return expected;
};
@@ -242,6 +243,113 @@ describe('ShellExecutionService', () => {
);
});
it('should capture large output (10000 lines)', async () => {
const lineCount = 10000;
const lines = Array.from({ length: lineCount }, (_, i) => `line ${i}`);
const expectedOutput = lines.join('\n');
const { result } = await simulateExecution(
'large-output-command',
(pty) => {
// Send data in chunks to simulate realistic streaming
// Use \r\n to ensure the terminal moves the cursor to the start of the line
const chunkSize = 1000;
for (let i = 0; i < lineCount; i += chunkSize) {
const chunk = lines.slice(i, i + chunkSize).join('\r\n') + '\r\n';
pty.onData.mock.calls[0][0](chunk);
}
pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null });
},
);
expect(result.exitCode).toBe(0);
// The terminal buffer output includes trailing spaces for each line (up to terminal width).
// We trim each line to match our expected simple string.
const processedOutput = result.output
.split('\n')
.map((l) => l.trimEnd())
.join('\n')
.trim();
expect(processedOutput).toBe(expectedOutput);
expect(result.output.split('\n').length).toBeGreaterThanOrEqual(
lineCount,
);
});
it('should not wrap long lines in the final output', async () => {
// Set a small width to force wrapping
const narrowConfig = { ...shellExecutionConfig, terminalWidth: 10 };
const longString = '123456789012345'; // 15 chars, should wrap at 10
const { result } = await simulateExecution(
'long-line-command',
(pty) => {
pty.onData.mock.calls[0][0](longString);
pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null });
},
narrowConfig,
);
expect(result.exitCode).toBe(0);
expect(result.output.trim()).toBe(longString);
});
it('should not add extra padding but preserve explicit trailing whitespace', async () => {
const { result } = await simulateExecution('cmd', (pty) => {
// "value" should not get terminal-width padding
// "value2 " should keep its spaces
pty.onData.mock.calls[0][0]('value\r\nvalue2 ');
pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null });
});
expect(result.output).toBe('value\nvalue2 ');
});
it('should truncate output exceeding the scrollback limit', async () => {
const scrollbackLimit = 100;
const totalLines = 150;
// Generate lines: "line 0", "line 1", ...
const lines = Array.from({ length: totalLines }, (_, i) => `line ${i}`);
const { result } = await simulateExecution(
'overflow-command',
(pty) => {
const chunk = lines.join('\r\n') + '\r\n';
pty.onData.mock.calls[0][0](chunk);
pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null });
},
{ ...shellExecutionConfig, scrollback: scrollbackLimit },
);
expect(result.exitCode).toBe(0);
// The terminal should keep the *last* 'scrollbackLimit' lines + lines in the viewport.
// xterm.js scrollback is the number of lines *above* the viewport.
// So total lines retained = scrollback + rows.
// However, our `getFullBufferText` implementation iterates the *active* buffer.
// In headless xterm, the buffer length grows.
// Let's verify that we have fewer lines than totalLines.
const outputLines = result.output
.trim()
.split('\n')
.map((l) => l.trimEnd());
// We expect the *start* of the output to be truncated.
// The first retained line should be > "line 0".
// Specifically, if we sent 150 lines and have space for roughly 100 + viewport(24),
// we should miss the first ~26 lines.
// Check that we lost some lines from the beginning
expect(outputLines.length).toBeLessThan(totalLines);
expect(outputLines[0]).not.toBe('line 0');
// Check that we have the *last* lines
expect(outputLines[outputLines.length - 1]).toBe(
`line ${totalLines - 1}`,
);
});
it('should call onPid with the process id', async () => {
const abortController = new AbortController();
const handle = await ShellExecutionService.execute(
@@ -24,6 +24,11 @@ const { Terminal } = pkg;
const SIGKILL_TIMEOUT_MS = 200;
const MAX_CHILD_PROCESS_BUFFER_SIZE = 16 * 1024 * 1024; // 16MB
// We want to allow shell outputs that are close to the context window in size.
// 300,000 lines is roughly equivalent to a large context window, ensuring
// we capture significant output from long-running commands.
export const SCROLLBACK_LIMIT = 300000;
const BASH_SHOPT_OPTIONS = 'promptvars nullglob extglob nocaseglob dotglob';
const BASH_SHOPT_GUARD = `shopt -u ${BASH_SHOPT_OPTIONS};`;
@@ -77,6 +82,7 @@ export interface ShellExecutionConfig {
defaultBg?: string;
// Used for testing
disableDynamicLineTrimming?: boolean;
scrollback?: number;
}
/**
@@ -110,10 +116,36 @@ const getFullBufferText = (terminal: pkg.Terminal): string => {
const lines: string[] = [];
for (let i = 0; i < buffer.length; i++) {
const line = buffer.getLine(i);
const lineContent = line ? line.translateToString() : '';
lines.push(lineContent);
if (!line) {
continue;
}
// If the NEXT line is wrapped, it means it's a continuation of THIS line.
// We should not trim the right side of this line because trailing spaces
// might be significant parts of the wrapped content.
// If it's not wrapped, we trim normally.
let trimRight = true;
if (i + 1 < buffer.length) {
const nextLine = buffer.getLine(i + 1);
if (nextLine?.isWrapped) {
trimRight = false;
}
}
const lineContent = line.translateToString(trimRight);
if (line.isWrapped && lines.length > 0) {
lines[lines.length - 1] += lineContent;
} else {
lines.push(lineContent);
}
}
return lines.join('\n').trimEnd();
// Remove trailing empty lines
while (lines.length > 0 && lines[lines.length - 1] === '') {
lines.pop();
}
return lines.join('\n');
};
/**
@@ -443,6 +475,7 @@ export class ShellExecutionService {
allowProposedApi: true,
cols,
rows,
scrollback: shellExecutionConfig.scrollback ?? SCROLLBACK_LIMIT,
});
headlessTerminal.scrollToTop();
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-test-utils",
"version": "0.19.0-nightly.20251125.f6d97d448",
"version": "0.19.4",
"private": true,
"main": "src/index.ts",
"license": "Apache-2.0",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "gemini-cli-vscode-ide-companion",
"displayName": "Gemini CLI Companion",
"description": "Enable Gemini CLI with direct access to your IDE workspace.",
"version": "0.19.0-nightly.20251125.f6d97d448",
"version": "0.19.4",
"publisher": "google",
"icon": "assets/icon.png",
"repository": {