Compare commits

...

8 Commits

Author SHA1 Message Date
gemini-cli-robot 001dc5f344 chore(release): v0.0.888888 2025-09-19 21:28:40 +00:00
Sandy Tao 85409d730e fix(security): Pin wrap-ansi to 9.0.2 (#8934) 2025-09-19 14:23:35 -07:00
Gal Zahavi 6dde251e1e fix(cli) : fix shell colors to match new coloring (#8747)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-09-19 13:14:13 -07:00
Gal Zahavi a2356b0230 fix(shell): update shell setting from usePty to enableInteractiveShell (#8726)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-09-19 13:14:00 -07:00
Gal Zahavi e20d7f1e99 refactor(shell): Send AnsiOutput when ShowColor is false (#8647) 2025-09-19 13:13:47 -07:00
Tommaso Sciortino 1610fecc3e Rollback shrinkwrap (#8926) 2025-09-19 11:52:56 -07:00
gemini-cli[bot] 84cc6a4e2d fix(patch): cherry-pick f3abfb8 to release/v0.6.0-preview.2 (#8904)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
2025-09-19 04:19:38 -07:00
gemini-cli-robot 5d676414ec chore(release): v0.6.0-preview.2 2025-09-17 20:34:54 +00:00
28 changed files with 299 additions and 273 deletions
+2 -2
View File
@@ -4,10 +4,10 @@
# Require reviews from the release approvers for critical files.
# These patterns override the rule above.
/package.json @google-gemini/gemini-cli-askmode-approvers
/npm-shrinkwrap.json @google-gemini/gemini-cli-askmode-approvers
/package-lock.json @google-gemini/gemini-cli-askmode-approvers
/GEMINI.md @google-gemini/gemini-cli-askmode-approvers
/SECURITY.md @google-gemini/gemini-cli-askmode-approvers
/LICENSE @google-gemini/gemini-cli-askmode-approvers
/.github/workflows/ @google-gemini/gemini-cli-askmode-approvers
/packages/cli/package.json @google-gemini/gemini-cli-askmode-approvers
/packages/core/package.json @google-gemini/gemini-cli-askmode-approvers
/packages/core/package.json @google-gemini/gemini-cli-askmode-approvers
+2 -8
View File
@@ -69,14 +69,8 @@ runs:
BRANCH_NAME: '${{ steps.release_branch.outputs.BRANCH_NAME }}'
DRY_RUN: '${{ inputs.dry-run }}'
RELEASE_TAG: '${{ inputs.release-tag }}'
run: |
git add package.json packages/*/package.json
if [ -f npm-shrinkwrap.json ]; then
git add npm-shrinkwrap.json
fi
if [ -f package-lock.json ]; then
git add package-lock.json
fi
run: |-
git add package.json package-lock.json packages/*/package.json
git commit -m "chore(release): ${RELEASE_TAG}"
if [[ "${DRY_RUN}" == "false" ]]; then
echo "Pushing release branch to remote..."
+35 -1
View File
@@ -24,7 +24,41 @@ jobs:
github.event_name == 'merge_group' ||
(github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event.label.name == 'maintainer:e2e:ok')
runs-on: '${{ matrix.os }}'
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
- name: 'Set up Node.js'
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: 'Install dependencies'
run: 'npm ci'
- name: 'Build project'
run: 'npm run build'
- name: 'Archive build artifacts'
run: 'tar -cvf build-artifacts.tar bundle/ node_modules/ packages/ package.json'
- name: 'Upload build artifacts'
uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02'
with:
name: 'build-artifacts-${{ github.run_id }}'
path: 'build-artifacts.tar'
e2e_linux:
name: 'E2E Test (Linux) - ${{ matrix.sandbox }}'
needs:
- 'build'
if: |
github.event_name == 'push' ||
github.event_name == 'merge_group' ||
(github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event.label.name == 'maintainer:e2e:ok')
runs-on: 'gemini-cli-ubuntu-16-core'
strategy:
fail-fast: false
matrix:
-3
View File
@@ -303,9 +303,6 @@ jobs:
DRY_RUN: '${{ github.event.inputs.dry_run }}'
run: |-
git add package.json packages/*/package.json
if [ -f npm-shrinkwrap.json ]; then
git add npm-shrinkwrap.json
fi
if [ -f package-lock.json ]; then
git add package-lock.json
fi
-1
View File
@@ -17,5 +17,4 @@ eslint.config.js
**/generated
gha-creds-*.json
junit.xml
npm-shrinkwrap.json
Thumbs.db
+3 -3
View File
@@ -203,9 +203,9 @@ Settings are organized into categories. All settings should be placed within the
- **Description:** Sandbox execution environment (can be a boolean or a path string).
- **Default:** `undefined`
- **`tools.usePty`** (boolean):
- **Description:** Use node-pty for shell command execution. Fallback to child_process still applies.
- **Default:** `false`
- **`tools.shell.enableInteractiveShell`** (boolean):
Use `node-pty` for an interactive shell experience. Fallback to `child_process` still applies. Defaults to `false`.
- **`tools.core`** (array of strings):
- **Description:** This can be used to restrict the set of built-in tools [with an allowlist](./enterprise.md#restricting-tool-access). See [Built-in Tools](../core/tools-api.md#built-in-tools) for a list of core tools. The match semantics are the same as `tools.allowed`.
+7 -5
View File
@@ -4,7 +4,7 @@ This document describes the `run_shell_command` tool for the Gemini CLI.
## Description
Use `run_shell_command` to interact with the underlying system, run scripts, or perform command-line operations. `run_shell_command` executes a given shell command, including interactive commands that require user input (e.g., `vim`, `git rebase -i`) if the `tools.usePty` setting is set to `true`.
Use `run_shell_command` to interact with the underlying system, run scripts, or perform command-line operations. `run_shell_command` executes a given shell command, including interactive commands that require user input (e.g., `vim`, `git rebase -i`) if the `tools.shell.enableInteractiveShell` setting is set to `true`.
On Windows, commands are executed with `cmd.exe /c`. On other platforms, they are executed with `bash -c`.
@@ -61,21 +61,23 @@ You can configure the behavior of the `run_shell_command` tool by modifying your
### Enabling Interactive Commands
To enable interactive commands, you need to set the `tools.usePty` setting to `true`. This will use `node-pty` for shell command execution, which allows for interactive sessions. If `node-pty` is not available, it will fall back to the `child_process` implementation, which does not support interactive commands.
To enable interactive commands, you need to set the `tools.shell.enableInteractiveShell` setting to `true`. This will use `node-pty` for shell command execution, which allows for interactive sessions. If `node-pty` is not available, it will fall back to the `child_process` implementation, which does not support interactive commands.
**Example `settings.json`:**
```json
{
"tools": {
"usePty": true
"shell": {
"enableInteractiveShell": true
}
}
}
```
### Showing Color in Output
To show color in the shell output, you need to set the `tools.shell.showColor` setting to `true`. **Note: This setting only applies when `tools.usePty` is enabled.**
To show color in the shell output, you need to set the `tools.shell.showColor` setting to `true`. **Note: This setting only applies when `tools.shell.enableInteractiveShell` is enabled.**
**Example `settings.json`:**
@@ -91,7 +93,7 @@ To show color in the shell output, you need to set the `tools.shell.showColor` s
### Setting the Pager
You can set a custom pager for the shell output by setting the `tools.shell.pager` setting. The default pager is `cat`. **Note: This setting only applies when `tools.usePty` is enabled.**
You can set a custom pager for the shell output by setting the `tools.shell.pager` setting. The default pager is `cat`. **Note: This setting only applies when `tools.shell.enableInteractiveShell` is enabled.**
**Example `settings.json`:**
+38 -137
View File
@@ -1,12 +1,12 @@
{
"name": "@google/gemini-cli",
"version": "0.6.0-nightly",
"version": "0.0.888888",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@google/gemini-cli",
"version": "0.6.0-nightly",
"version": "0.0.888888",
"workspaces": [
"packages/*"
],
@@ -1655,51 +1655,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@inquirer/core/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@inquirer/core/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/@inquirer/core/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@inquirer/core/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@inquirer/core/node_modules/type-fest": {
"version": "0.21.3",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
@@ -1713,21 +1668,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@inquirer/core/node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@inquirer/figures": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz",
@@ -6341,23 +6281,6 @@
"node": ">=8"
}
},
"node_modules/cliui/node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/cockatiel": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz",
@@ -9764,23 +9687,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ink/node_modules/wrap-ansi": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
"integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/internal-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
@@ -15263,18 +15169,6 @@
"url": "https://github.com/yeoman/update-notifier?sponsor=1"
}
},
"node_modules/update-notifier/node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/update-notifier/node_modules/boxen": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz",
@@ -15371,23 +15265,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/update-notifier/node_modules/wrap-ansi": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
"integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -15943,17 +15820,17 @@
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
"integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=12"
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
@@ -16030,6 +15907,29 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/emoji-regex": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz",
"integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==",
"license": "MIT"
},
"node_modules/wrap-ansi/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -16279,7 +16179,7 @@
},
"packages/a2a-server": {
"name": "@google/gemini-cli-a2a-server",
"version": "0.6.0-nightly",
"version": "0.0.888888",
"dependencies": {
"@a2a-js/sdk": "^0.3.2",
"@google-cloud/storage": "^7.16.0",
@@ -16550,7 +16450,7 @@
},
"packages/cli": {
"name": "@google/gemini-cli",
"version": "0.6.0-nightly",
"version": "0.0.888888",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
"@google/genai": "1.16.0",
@@ -16578,6 +16478,7 @@
"strip-json-comments": "^3.1.1",
"undici": "^7.10.0",
"update-notifier": "^7.3.1",
"wrap-ansi": "9.0.2",
"yargs": "^17.7.2",
"zod": "^3.23.8"
},
@@ -16742,7 +16643,7 @@
},
"packages/core": {
"name": "@google/gemini-cli-core",
"version": "0.6.0-nightly",
"version": "0.0.888888",
"dependencies": {
"@google-cloud/logging": "^11.2.1",
"@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0",
@@ -16887,7 +16788,7 @@
},
"packages/test-utils": {
"name": "@google/gemini-cli-test-utils",
"version": "0.6.0-nightly",
"version": "0.0.888888",
"license": "Apache-2.0",
"devDependencies": {
"typescript": "^5.3.3"
@@ -16898,7 +16799,7 @@
},
"packages/vscode-ide-companion": {
"name": "gemini-cli-vscode-ide-companion",
"version": "0.6.0-nightly",
"version": "0.0.888888",
"license": "LICENSE",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.15.1",
+6 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.6.0-nightly",
"version": "0.0.888888",
"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.6.0-nightly"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.0.888888"
},
"scripts": {
"start": "node scripts/start.js",
@@ -59,8 +59,7 @@
"files": [
"bundle/",
"README.md",
"LICENSE",
"npm-shrinkwrap.json"
"LICENSE"
],
"devDependencies": {
"@types/marked": "^5.0.2",
@@ -103,5 +102,8 @@
"@lydell/node-pty-win32-arm64": "1.1.0",
"@lydell/node-pty-win32-x64": "1.1.0",
"node-pty": "^1.0.0"
},
"overrides": {
"wrap-ansi": "9.0.2"
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-a2a-server",
"version": "0.6.0-nightly",
"version": "0.0.888888",
"private": true,
"description": "Gemini CLI A2A Server",
"repository": {
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.6.0-nightly",
"version": "0.0.888888",
"description": "Gemini CLI",
"repository": {
"type": "git",
@@ -25,7 +25,7 @@
"dist"
],
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.6.0-nightly"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.0.888888"
},
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -54,6 +54,7 @@
"strip-json-comments": "^3.1.1",
"undici": "^7.10.0",
"update-notifier": "^7.3.1",
"wrap-ansi": "9.0.2",
"yargs": "^17.7.2",
"zod": "^3.23.8"
},
+1 -1
View File
@@ -633,7 +633,7 @@ export async function loadCliConfig(
interactive,
trustedFolder,
useRipgrep: settings.tools?.useRipgrep,
shouldUseNodePtyShell: settings.tools?.usePty,
shouldUseNodePtyShell: settings.tools?.shell?.enableInteractiveShell,
skipNextSpeakerCheck: settings.model?.skipNextSpeakerCheck,
enablePromptCompletion: settings.general?.enablePromptCompletion ?? false,
truncateToolOutputThreshold: settings.tools?.truncateToolOutputThreshold,
+1 -1
View File
@@ -107,7 +107,7 @@ const MIGRATION_MAP: Record<string, string> = {
preferredEditor: 'general.preferredEditor',
sandbox: 'tools.sandbox',
selectedAuthType: 'security.auth.selectedType',
shouldUseNodePtyShell: 'tools.usePty',
shouldUseNodePtyShell: 'tools.shell.enableInteractiveShell',
shellPager: 'tools.shell.pager',
shellShowColor: 'tools.shell.showColor',
skipNextSpeakerCheck: 'model.skipNextSpeakerCheck',
+10 -10
View File
@@ -639,16 +639,6 @@ const SETTINGS_SCHEMA = {
'Sandbox execution environment (can be a boolean or a path string).',
showInDialog: false,
},
usePty: {
type: 'boolean',
label: 'Use node-pty for Shell Execution',
category: 'Tools',
requiresRestart: true,
default: false,
description:
'Use node-pty for shell command execution. Fallback to child_process still applies.',
showInDialog: true,
},
shell: {
type: 'object',
label: 'Shell',
@@ -658,6 +648,16 @@ const SETTINGS_SCHEMA = {
description: 'Settings for shell execution.',
showInDialog: false,
properties: {
enableInteractiveShell: {
type: 'boolean',
label: 'Enable Interactive Shell',
category: 'Tools',
requiresRestart: true,
default: false,
description:
'Use node-pty for an interactive shell experience. Fallback to child_process still applies.',
showInDialog: true,
},
pager: {
type: 'string',
label: 'Pager',
@@ -1262,7 +1262,7 @@ describe('SettingsDialog', () => {
},
},
tools: {
usePty: true,
enableInteractiveShell: true,
autoAccept: true,
useRipgrep: true,
},
@@ -1375,7 +1375,7 @@ describe('SettingsDialog', () => {
},
tools: {
useRipgrep: true,
usePty: false,
enableInteractiveShell: false,
},
},
);
@@ -1449,7 +1449,7 @@ describe('SettingsDialog', () => {
it('should render with tools and security settings', () => {
const settings = createMockSettings({
tools: {
usePty: true,
enableInteractiveShell: true,
autoAccept: false,
useRipgrep: true,
truncateToolOutputThreshold: 25000,
@@ -1508,7 +1508,7 @@ describe('SettingsDialog', () => {
},
},
tools: {
usePty: false,
enableInteractiveShell: false,
autoAccept: false,
useRipgrep: false,
},
@@ -12,7 +12,7 @@ import { ToolCallStatus } from '../../types.js';
import { ToolMessage } from './ToolMessage.js';
import { ToolConfirmationMessage } from './ToolConfirmationMessage.js';
import { theme } from '../../semantic-colors.js';
import { SHELL_COMMAND_NAME } from '../../constants.js';
import { SHELL_COMMAND_NAME, SHELL_NAME } from '../../constants.js';
import { useConfig } from '../../contexts/ConfigContext.js';
interface ToolGroupMessageProps {
@@ -47,11 +47,15 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
);
const config = useConfig();
const isShellCommand = toolCalls.some((t) => t.name === SHELL_COMMAND_NAME);
const isShellCommand = toolCalls.some(
(t) => t.name === SHELL_COMMAND_NAME || t.name === SHELL_NAME,
);
const borderColor =
hasPending || isShellCommand || isShellFocused
? theme.status.warning
: theme.border.default;
isShellCommand || isShellFocused
? theme.ui.symbol
: hasPending
? theme.status.warning
: theme.border.default;
const staticHeight = /* border */ 2 + /* marginBottom */ 1;
// This is a bit of a magic number, but it accounts for the border and
@@ -14,7 +14,11 @@ import { AnsiOutputText } from '../AnsiOutput.js';
import { GeminiRespondingSpinner } from '../GeminiRespondingSpinner.js';
import { MaxSizedBox } from '../shared/MaxSizedBox.js';
import { ShellInputPrompt } from '../ShellInputPrompt.js';
import { SHELL_COMMAND_NAME, TOOL_STATUS } from '../../constants.js';
import {
SHELL_COMMAND_NAME,
SHELL_NAME,
TOOL_STATUS,
} from '../../constants.js';
import { theme } from '../../semantic-colors.js';
import type { AnsiOutput, Config } from '@google/gemini-cli-core';
@@ -58,6 +62,11 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
ptyId === activeShellPtyId &&
shellFocused;
const isThisShellFocusable =
(name === SHELL_COMMAND_NAME || name === 'Shell') &&
status === ToolCallStatus.Executing &&
config?.getShouldUseNodePtyShell();
const availableHeight = availableTerminalHeight
? Math.max(
availableTerminalHeight - STATIC_HEIGHT - RESERVED_LINE_COUNT,
@@ -83,16 +92,18 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
return (
<Box paddingX={1} paddingY={0} flexDirection="column">
<Box minHeight={1}>
<ToolStatusIndicator status={status} />
<ToolStatusIndicator status={status} name={name} />
<ToolInfo
name={name}
status={status}
description={description}
emphasis={emphasis}
/>
{isThisShellFocused && (
<Box marginLeft={1}>
<Text color={theme.text.accent}>[Focused]</Text>
{isThisShellFocusable && (
<Box marginLeft={1} flexShrink={0}>
<Text color={theme.text.accent}>
{isThisShellFocused ? '(Focused)' : '(ctrl+f to focus)'}
</Text>
</Box>
)}
{emphasis === 'high' && <TrailingIndicator />}
@@ -118,7 +129,7 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
</Box>
</MaxSizedBox>
) : typeof resultDisplay === 'object' &&
!Array.isArray(resultDisplay) ? (
'fileDiff' in resultDisplay ? (
<DiffRenderer
diffContent={resultDisplay.fileDiff}
filename={resultDisplay.fileName}
@@ -148,43 +159,50 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
type ToolStatusIndicatorProps = {
status: ToolCallStatus;
name: string;
};
const ToolStatusIndicator: React.FC<ToolStatusIndicatorProps> = ({
status,
}) => (
<Box minWidth={STATUS_INDICATOR_WIDTH}>
{status === ToolCallStatus.Pending && (
<Text color={theme.status.success}>{TOOL_STATUS.PENDING}</Text>
)}
{status === ToolCallStatus.Executing && (
<GeminiRespondingSpinner
spinnerType="toggle"
nonRespondingDisplay={TOOL_STATUS.EXECUTING}
/>
)}
{status === ToolCallStatus.Success && (
<Text color={theme.status.success} aria-label={'Success:'}>
{TOOL_STATUS.SUCCESS}
</Text>
)}
{status === ToolCallStatus.Confirming && (
<Text color={theme.status.warning} aria-label={'Confirming:'}>
{TOOL_STATUS.CONFIRMING}
</Text>
)}
{status === ToolCallStatus.Canceled && (
<Text color={theme.status.warning} aria-label={'Canceled:'} bold>
{TOOL_STATUS.CANCELED}
</Text>
)}
{status === ToolCallStatus.Error && (
<Text color={theme.status.error} aria-label={'Error:'} bold>
{TOOL_STATUS.ERROR}
</Text>
)}
</Box>
);
name,
}) => {
const isShell = name === SHELL_COMMAND_NAME || name === SHELL_NAME;
const statusColor = isShell ? theme.ui.symbol : theme.status.warning;
return (
<Box minWidth={STATUS_INDICATOR_WIDTH}>
{status === ToolCallStatus.Pending && (
<Text color={theme.status.success}>{TOOL_STATUS.PENDING}</Text>
)}
{status === ToolCallStatus.Executing && (
<GeminiRespondingSpinner
spinnerType="toggle"
nonRespondingDisplay={TOOL_STATUS.EXECUTING}
/>
)}
{status === ToolCallStatus.Success && (
<Text color={theme.status.success} aria-label={'Success:'}>
{TOOL_STATUS.SUCCESS}
</Text>
)}
{status === ToolCallStatus.Confirming && (
<Text color={statusColor} aria-label={'Confirming:'}>
{TOOL_STATUS.CONFIRMING}
</Text>
)}
{status === ToolCallStatus.Canceled && (
<Text color={statusColor} aria-label={'Canceled:'} bold>
{TOOL_STATUS.CANCELED}
</Text>
)}
{status === ToolCallStatus.Error && (
<Text color={theme.status.error} aria-label={'Error:'} bold>
{TOOL_STATUS.ERROR}
</Text>
)}
</Box>
);
};
type ToolInfo = {
name: string;
+2
View File
@@ -16,6 +16,8 @@ export const STREAM_DEBOUNCE_MS = 100;
export const SHELL_COMMAND_NAME = 'Shell Command';
export const SHELL_NAME = 'Shell';
// Tool status symbols used in ToolMessage component
export const TOOL_STATUS = {
SUCCESS: '✓',
@@ -238,7 +238,7 @@ describe('useShellCommandProcessor', () => {
'/test/dir',
expect.any(Function),
expect.any(Object),
false, // usePty
false, // enableInteractiveShell
expect.any(Object),
);
@@ -143,6 +143,8 @@ export const useShellCommandProcessor = (
const activeTheme = themeManager.getActiveTheme();
const shellExecutionConfig = {
...config.getShellExecutionConfig(),
terminalWidth,
terminalHeight,
defaultFg: activeTheme.colors.Foreground,
defaultBg: activeTheme.colors.Background,
};
@@ -158,14 +160,14 @@ export const useShellCommandProcessor = (
if (isBinaryStream) break;
// PTY provides the full screen state, so we just replace.
// Child process provides chunks, so we append.
if (
if (config.getShouldUseNodePtyShell()) {
cumulativeStdout = event.chunk;
shouldUpdate = true;
} else if (
typeof event.chunk === 'string' &&
typeof cumulativeStdout === 'string'
) {
cumulativeStdout += event.chunk;
} else {
cumulativeStdout = event.chunk;
shouldUpdate = true;
}
break;
case 'binary_detected':
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-core",
"version": "0.6.0-nightly",
"version": "0.0.888888",
"description": "Gemini CLI Core",
"repository": {
"type": "git",
@@ -10,6 +10,7 @@ import type { Readable } from 'node:stream';
import { type ChildProcess } from 'node:child_process';
import type { ShellOutputEvent } from './shellExecutionService.js';
import { ShellExecutionService } from './shellExecutionService.js';
import type { AnsiOutput } from '../utils/terminalSerializer.js';
// Hoisted Mocks
const mockPtySpawn = vi.hoisted(() => vi.fn());
@@ -54,6 +55,10 @@ vi.mock('../utils/terminalSerializer.js', () => ({
serializeTerminalToObject: mockSerializeTerminalToObject,
}));
const mockProcessKill = vi
.spyOn(process, 'kill')
.mockImplementation(() => true);
const shellExecutionConfig = {
terminalWidth: 80,
terminalHeight: 24,
@@ -61,9 +66,25 @@ const shellExecutionConfig = {
showColor: false,
};
const mockProcessKill = vi
.spyOn(process, 'kill')
.mockImplementation(() => true);
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: '',
},
],
);
return expected;
};
describe('ShellExecutionService', () => {
let mockPtyProcess: EventEmitter & {
@@ -77,6 +98,11 @@ describe('ShellExecutionService', () => {
let mockHeadlessTerminal: {
resize: Mock;
scrollLines: Mock;
buffer: {
active: {
viewportY: number;
};
};
};
let onOutputEventMock: Mock<(event: ShellOutputEvent) => void>;
@@ -110,6 +136,11 @@ describe('ShellExecutionService', () => {
mockHeadlessTerminal = {
resize: vi.fn(),
scrollLines: vi.fn(),
buffer: {
active: {
viewportY: 0,
},
},
};
mockPtySpawn.mockReturnValue(mockPtyProcess);
@@ -161,7 +192,7 @@ describe('ShellExecutionService', () => {
expect(onOutputEventMock).toHaveBeenCalledWith({
type: 'data',
chunk: 'file1.txt',
chunk: createExpectedAnsiOutput('file1.txt'),
});
});
@@ -175,7 +206,7 @@ describe('ShellExecutionService', () => {
expect(onOutputEventMock).toHaveBeenCalledWith(
expect.objectContaining({
type: 'data',
chunk: expect.stringContaining('aredword'),
chunk: createExpectedAnsiOutput('aredword'),
}),
);
});
@@ -197,7 +228,7 @@ describe('ShellExecutionService', () => {
expect(onOutputEventMock).toHaveBeenCalledWith(
expect.objectContaining({
chunk: expect.stringMatching(/^\s*$/),
chunk: createExpectedAnsiOutput(''),
}),
);
});
@@ -438,17 +469,44 @@ describe('ShellExecutionService', () => {
);
});
it('should call onOutputEvent with plain string when showColor is false', async () => {
await simulateExecution('ls --color=auto', (pty) => {
pty.onData.mock.calls[0][0]('a\u001b[31mred\u001b[0mword');
pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null });
});
it('should call onOutputEvent with AnsiOutput when showColor is false', async () => {
await simulateExecution(
'ls --color=auto',
(pty) => {
pty.onData.mock.calls[0][0]('a\u001b[31mred\u001b[0mword');
pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null });
},
{ ...shellExecutionConfig, showColor: false },
);
const expected = createExpectedAnsiOutput('aredword');
expect(mockSerializeTerminalToObject).not.toHaveBeenCalled();
expect(onOutputEventMock).toHaveBeenCalledWith(
expect.objectContaining({
type: 'data',
chunk: 'aredword',
chunk: expected,
}),
);
});
it('should handle multi-line output correctly when showColor is false', async () => {
await simulateExecution(
'ls --color=auto',
(pty) => {
pty.onData.mock.calls[0][0](
'line 1\n\u001b[32mline 2\u001b[0m\nline 3',
);
pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null });
},
{ ...shellExecutionConfig, showColor: false },
);
const expected = createExpectedAnsiOutput(['line 1', 'line 2', 'line 3']);
expect(onOutputEventMock).toHaveBeenCalledWith(
expect.objectContaining({
type: 'data',
chunk: expected,
}),
);
});
@@ -541,7 +599,7 @@ describe('ShellExecutionService child_process fallback', () => {
expect(onOutputEventMock).toHaveBeenCalledWith(
expect.objectContaining({
type: 'data',
chunk: expect.stringContaining('aredword'),
chunk: 'aredword',
}),
);
});
@@ -85,17 +85,6 @@ interface ActivePty {
headlessTerminal: pkg.Terminal;
}
const getVisibleText = (terminal: pkg.Terminal): string => {
const buffer = terminal.buffer.active;
const lines: string[] = [];
for (let i = 0; i < terminal.rows; i++) {
const line = buffer.getLine(buffer.viewportY + i);
const lineContent = line ? line.translateToString(true) : '';
lines.push(lineContent);
}
return lines.join('\n').trimEnd();
};
const getFullBufferText = (terminal: pkg.Terminal): string => {
const buffer = terminal.buffer.active;
const lines: string[] = [];
@@ -379,6 +368,7 @@ export class ShellExecutionService {
cols,
rows,
});
headlessTerminal.scrollToTop();
this.activePtys.set(ptyProcess.pid, { ptyProcess, headlessTerminal });
@@ -404,14 +394,33 @@ export class ShellExecutionService {
if (!isStreamingRawContent) {
return;
}
const newOutput = shellExecutionConfig.showColor
? serializeTerminalToObject(headlessTerminal, {
defaultFg: shellExecutionConfig.defaultFg,
defaultBg: shellExecutionConfig.defaultBg,
})
: getVisibleText(headlessTerminal);
// console.log(newOutput)
let newOutput: AnsiOutput;
if (shellExecutionConfig.showColor) {
newOutput = serializeTerminalToObject(headlessTerminal, {
defaultFg: shellExecutionConfig.defaultFg,
defaultBg: shellExecutionConfig.defaultBg,
});
} else {
const buffer = headlessTerminal.buffer.active;
const lines: AnsiOutput = [];
for (let y = 0; y < headlessTerminal.rows; y++) {
const line = buffer.getLine(buffer.viewportY + y);
const lineContent = line ? line.translateToString(true) : '';
lines.push([
{
text: lineContent,
bold: false,
italic: false,
underline: false,
dim: false,
inverse: false,
fg: '',
bg: '',
},
]);
}
newOutput = lines;
}
// Using stringify for a quick deep comparison.
if (JSON.stringify(output) !== JSON.stringify(newOutput)) {
@@ -602,6 +611,9 @@ export class ShellExecutionService {
if (activePty) {
try {
activePty.headlessTerminal.scrollLines(lines);
if (activePty.headlessTerminal.buffer.active.viewportY < 0) {
activePty.headlessTerminal.scrollToTop();
}
} catch (e) {
// Ignore errors if the pty has already exited, which can happen
// due to a race condition between the exit event and this call.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-test-utils",
"version": "0.6.0-nightly",
"version": "0.0.888888",
"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.6.0-nightly",
"version": "0.0.888888",
"publisher": "google",
"icon": "assets/icon.png",
"repository": {
@@ -94,7 +94,7 @@ function collectDependencies(packageName, packageLock, dependenciesMap) {
const packageInfo = packageLock.packages[`node_modules/${packageName}`];
if (!packageInfo) {
console.warn(
`Warning: Could not find package info for ${packageName} in npm-shrinkwrap.json.`,
`Warning: Could not find package info for ${packageName} in package-lock.json.`,
);
return;
}
@@ -114,7 +114,7 @@ async function main() {
const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8');
const packageJson = JSON.parse(packageJsonContent);
const packageLockJsonPath = path.join(projectRoot, 'npm-shrinkwrap.json');
const packageLockJsonPath = path.join(projectRoot, 'package-lock.json');
const packageLockJsonContent = await fs.readFile(
packageLockJsonPath,
'utf-8',
+2 -2
View File
@@ -10,7 +10,7 @@ import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = join(__dirname, '..');
const lockfilePath = join(root, 'npm-shrinkwrap.json');
const lockfilePath = join(root, 'package-lock.json');
function readJsonFile(filePath) {
try {
@@ -64,7 +64,7 @@ for (const [location, details] of Object.entries(packages)) {
if (invalidPackages.length > 0) {
console.error(
'\nError: The following dependencies in npm-shrinkwrap.json are missing the "resolved" or "integrity" field:',
'\nError: The following dependencies in package-lock.json are missing the "resolved" or "integrity" field:',
);
invalidPackages.forEach((pkg) => console.error(`- ${pkg}`));
process.exitCode = 1;
+1 -1
View File
@@ -75,7 +75,7 @@ if (cliPackageJson.config?.sandboxImageUri) {
writeJson(cliPackageJsonPath, cliPackageJson);
}
// 6. Run `npm install` to update npm-shrinkwrap.json.
// 6. Run `npm install` to update package-lock.json.
run('npm install');
console.log(`Successfully bumped versions to v${newVersion}.`);