Compare commits

...

142 Commits

Author SHA1 Message Date
Philipp Schmid ac24fd27e4 Update Auth Label to include AI Studio (#2130)
Co-authored-by: Allen Hutchison <adh@google.com>
2025-06-27 21:33:26 +00:00
Xi Chen d35e7d3eb1 Fix a circular dependency (#2246)
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
2025-06-27 20:59:47 +00:00
Brandon Keiji 8d1d40cc7a chore: bump to 0.1.6 (#2285) 2025-06-27 20:57:07 +00:00
Tommaso Sciortino a2a46c7c67 Add privacy notice slash command (#2059) 2025-06-27 19:07:38 +00:00
Billy Biggs 4fbffdf617 Handle stdin for prompts using readline for escape character parsing (#1972) 2025-06-27 17:57:32 +00:00
Preston Holmes 5fd6664c4b Further clarify the situations where a Project ID is required (#2029)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
2025-06-27 16:51:28 +00:00
Preston Holmes 3aabb940f5 Add the current auth method and GCP Project config to the about message (#2112) 2025-06-27 15:46:27 +00:00
Srinath Padmanabhan 3ebf54f367 Refine Fallback message providing more options. (#1961) 2025-06-27 15:21:46 +00:00
Billy Biggs 582b4861a9 Use 2-space indent for saved checkpoint files (#1152) (#1977)
Co-authored-by: Jacob Richman <jacob314@gmail.com>
2025-06-27 13:39:27 +00:00
DongJin Jeong a943cee45d Fix typos and formatting issues (#1480) 2025-06-27 10:31:42 +00:00
Seth Troisi 6742a1b7f9 Explicit message for missing subcommand (#2019) 2025-06-27 05:01:00 +00:00
Brandon Keiji bf873a1d85 feat: add prepublishOnly checks (#2052) 2025-06-26 23:49:43 +00:00
Brandon Keiji d9892ada7f fix: add repository field to package.jsons (#2032) 2025-06-26 22:36:34 +00:00
Bryan Morgan 560905154c Updating the first user message to mention the product name (#2037) 2025-06-26 22:32:19 +00:00
anj-s 267173c7e8 Revert "feat: Add model selection logic (#1678)" (#1983) 2025-06-26 20:59:16 +00:00
Tommaso Sciortino c55b15f705 Improve LoadCodeAssist error handling (#1645) 2025-06-26 15:27:20 +00:00
N. Taylor Mullen 24ccc9c457 feat: Add model selection logic (#1678) 2025-06-26 14:51:32 +00:00
Vladislav Semyanov 121bba3464 docs: fix broken configuration link in themes.md (#1780) 2025-06-26 07:37:42 +00:00
김진엽 (Nathan) 02bd8dfeff docs: fix multiple typos in documentation files (#1781) 2025-06-26 07:36:31 +00:00
Masato Sugiyama ee5bf842eb fix: remove unnecessary @gemini-code/core mock from slashCommandProcessor test (#1739)
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
2025-06-26 04:56:51 +00:00
Keith Ballinger 891116a6c2 Flaky test (#1405)
Co-authored-by: matt korwel <matt.korwel@gmail.com>
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
2025-06-26 04:45:39 +00:00
Noritaka Kobayashi dbe217828d chore: fix typos in comment-out (#1540)
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
2025-06-26 04:34:53 +00:00
Andrew Drozdov b8ae12a109 Update geminiChat.ts (#1681)
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
2025-06-26 04:27:35 +00:00
Billy Biggs 759ad4cc96 When resuming a checkpoint always add items to history even if not shown (#1653)
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
2025-06-26 04:23:47 +00:00
Jennifer Davis 4d4b95a41d fix: add engines check to ensure older Node.js versions don't fail at… (#1752) 2025-06-26 04:09:40 +00:00
mpAtGoogle 046c7f84bc Update LICENSE (#1664) 2025-06-26 03:34:01 +00:00
Brandon Keiji 01ff27709d chore: bump to 0.1.5 (#1731) 2025-06-26 02:48:16 +00:00
anj-s 1078a546fe Fix doc link in the auth dialog (#1688) 2025-06-26 01:59:13 +00:00
Brandon Keiji a8763abfb7 feat: add release trigger configuration (#1697) 2025-06-26 01:54:58 +00:00
Bryan Morgan bb797ded7d 429 fix (#1668) 2025-06-26 01:45:38 +00:00
Jerop Kipruto b6b9923dc3 Streamline issue submission with YAML forms (#1608) 2025-06-25 22:50:24 +00:00
Tommaso Sciortino 79c647d486 Merge "Login with Google Workspace" auth option into "Login with Google" (#1574)
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
2025-06-25 22:38:18 +00:00
anj-s 00b24c917e Update usage stats doc (#1636) 2025-06-25 22:26:07 +00:00
Logan Kilpatrick 32b2e79b62 Update README.md (#1632) 2025-06-25 21:41:52 +00:00
anj-s 5aa6b9a84b Update docs and add faq section (#1625) 2025-06-25 21:32:16 +00:00
Jerop Kipruto 8c5a0b6f88 prompt users to search for existing issues before creating a new one (#1595) 2025-06-25 20:29:51 +00:00
zhiyue b0cf9bcece fix(telemetry): handle all promise rejections in ClearcutLogger (#1557)
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
2025-06-25 20:14:44 +00:00
Seth Troisi 31f32421a3 Minor style changes to prompt (#1578) 2025-06-25 20:00:44 +00:00
Bryan Morgan eacbb3551c changed 429 failover from 3 consecutive to 2 for OAuth users (#1579) 2025-06-25 19:37:23 +00:00
anj-s 4b5ca6bc77 Add tos and privacy links docs for clarity (#1571) 2025-06-25 19:32:25 +00:00
Mark McDonald 452b82162b Adding some troubleshooting text for login issues (#1451) 2025-06-25 19:00:36 +00:00
DongJin Jeong aa0e375508 Fix: update npx command to correct GitHub repository URL (#1488) 2025-06-25 18:58:11 +00:00
Jenna Inouye 6a0b8a733b Add information request to bug reports: login method. (#1501) 2025-06-25 18:55:35 +00:00
Arjun Lall 63a7fbc5fd Fixed Checkpointing docs for enabling checkpointing using settings.json (#1534) 2025-06-25 18:51:38 +00:00
N. Taylor Mullen 3a369ddec3 feat: Refine model fallback messaging to reflect reality. (#1527) 2025-06-25 17:33:32 +00:00
starsandskies 0915bf7d67 Remove GEMINI_CODE_ASSIST env variable from configuration.md doc (#1514) 2025-06-25 16:45:40 +00:00
Marat Boshernitsan 9897a2b80a Clarify why authentication failures might be happening and direct users to documentation (#1478)
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
Co-authored-by: matt korwel <matt.korwel@gmail.com>
2025-06-25 16:11:54 +00:00
Jason Davenport 819507c5e8 Update Readme to call out gemini command after npm install (#1423)
Co-authored-by: Mark McDonald <macd@google.com>
2025-06-25 16:03:07 +00:00
matt korwel 45b2b382cc Prod: Integration Tests Main Only (#1461) 2025-06-25 15:56:06 +00:00
Thomas Kosiewski af4dfd9327 Update authentication.md (#1429)
Co-authored-by: matt korwel <matt.korwel@gmail.com>
2025-06-25 15:48:15 +00:00
matt korwel 21cfe9f680 issue template update (#1441) 2025-06-25 14:44:34 +00:00
matt korwel 6991ba1387 Version 0 1 1 (#1426) 2025-06-25 13:47:27 +00:00
Asad Memon 452dca4301 Fix typo in intro on MCP line (#1421) 2025-06-25 13:04:42 +00:00
N. Taylor Mullen 852210e108 Shipping it! (#1418) 2025-06-25 12:42:04 +00:00
Brandon Keiji f6c36f75e3 fix: prepublish changes to package names (#1420) 2025-06-25 12:41:11 +00:00
Jenna Inouye a3c46c0d31 Docs: Add link links to tools/index.md. (#1419) 2025-06-25 11:16:17 +00:00
Mark McDonald f78479d7ce Add intro text back to README (#1417)
Co-authored-by: N. Taylor Mullen <ntaylormullen@google.com>
2025-06-25 10:12:31 +00:00
Mark McDonald cfc13fbd58 Add 'npm i' instruction to README (#1416) 2025-06-25 09:46:05 +00:00
matt korwel 9a093e4b51 CI: Linting Fix (#1413)
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
2025-06-25 04:18:51 +00:00
Preston Holmes 4cc2b27f1d Docs: update docs to clarify the differences between Google account login o… (#1409)
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
Co-authored-by: matt korwel <mattkorwel@google.com>
Co-authored-by: matt korwel <matt.korwel@gmail.com>
2025-06-25 04:15:45 +00:00
Scott Densmore 39bfa108b5 refactor: remove deplicate dependency in slashCommandProcessor (#1410)
Co-authored-by: matt korwel <matt.korwel@gmail.com>
2025-06-25 04:07:20 +00:00
Daniel Tedesco 268d29f05c docs: fix typos in documentation (#1411)
Co-authored-by: Dan Tedesco <dted@google.com>
2025-06-25 03:53:03 +00:00
Keith Ballinger b6ccf12551 [June 25] handle early output pipe closer (#1402) 2025-06-25 00:39:01 +00:00
matt korwel fbd8725c07 fix(update-notifier): resolve __dirname error on npx execution (#1406) 2025-06-25 00:26:50 +00:00
matt korwel 2505af8522 Prerelease: Cleanup (#1404) 2025-06-24 23:41:36 +00:00
Brian Ray d45d414c93 sandbox doc (#1390)
Co-authored-by: matt korwel <matt.korwel@gmail.com>
Co-authored-by: Jenna Inouye <jinouye@google.com>
2025-06-24 23:11:39 +00:00
Seth Troisi d8000c9248 Add slashCommand dependency (#1401) 2025-06-24 22:55:26 +00:00
Marat Boshernitsan e3def2dd49 fix: use correct directory for update checks (#1394) 2025-06-24 22:51:16 +00:00
Bryan Morgan e356949d3f [JUNE 25] Permanent failover to Flash model for OAuth users after persistent 429 errors (#1376)
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
2025-06-24 22:48:55 +00:00
Jenna Inouye 4bf18da2b0 Documents: Add docs tools pages (#1397) 2025-06-24 22:31:58 +00:00
Jacob Richman 8bd5645dd4 Truncate all strings before displaying in a tool messages to avoid stack overflows (#1395) 2025-06-24 22:31:55 +00:00
Mark McDonald 1f6fe59def Added ToS links for each surface (#1365)
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
Co-authored-by: matt korwel <matt.korwel@gmail.com>
2025-06-24 22:31:50 +00:00
anj-s 418f67086b Add the link to the privacy policy to the Usage Statistics section and remove debug logs (#1393) 2025-06-24 22:31:45 +00:00
cperry-goog 13cff94b1a docs: add screenshot to README (#1396) 2025-06-24 22:31:40 +00:00
Louis Jimenez 7421bf681b Checkpointing documentation (#1321)
Co-authored-by: matt korwel <matt.korwel@gmail.com>
2025-06-24 22:31:32 +00:00
matt korwel db57d38d92 feat: add merge_group trigger to CI and E2E workflows (#1400) 2025-06-24 15:30:31 -07:00
Jacob Richman 5ff00b0c5d Remove uses of the spread operator that appear to have caused a maximum call stack size exceeded error (#1389) 2025-06-24 21:38:25 +00:00
Jacob Richman 75ed7aaa06 Jacob314/max old space (#1314) 2025-06-24 14:18:55 -07:00
Sandy Tao a411c415a8 Do not render mcp responses as markdown (#1388) 2025-06-24 14:10:41 -07:00
Brian Ray e613cbc448 MCP documentation improvements (#1386)
Co-authored-by: Jenna Inouye <jinouye@google.com>
2025-06-24 13:19:36 -07:00
Bryan Morgan 5099f104bc [June 25] Fixed user agent string to be standards-compliant (#1384) 2025-06-24 09:11:09 -07:00
Billy Biggs b47a4240ff Bug/1369 at command recursive search (#1370) 2025-06-24 06:48:26 +00:00
cornmander a2ed4266aa Update editor.ts (#1371) 2025-06-24 06:41:40 +00:00
Scott Densmore 324715ee8b Add Zed Editor to Eidtor List (#1372) 2025-06-23 23:32:09 -07:00
starsandskies 9f5a625730 Fix broken doc links (#1368) 2025-06-23 20:56:57 -07:00
Abhi d3f13c71ae feat: add custom message for 429 errors (#1366) 2025-06-24 03:43:00 +00:00
Bryan Morgan f7caca5f94 Updated README.md (#1367) 2025-06-23 23:37:07 -04:00
Tommaso Sciortino 0abd2a644e Improve Auth error messaging (#1358) 2025-06-24 01:37:41 +00:00
Seth Troisi 104f23da90 Add /chat list (#1361) 2025-06-24 01:11:45 +00:00
owenofbrien 37034045ae Fix Clearcut logging wire format (#1359)
Fix for Clearcut logging wire format based on validation thread with Clearcut / Concord eng
2025-06-23 17:47:37 -07:00
Jerop Kipruto aca034fdfe Refactor usage statistics to be a top-level setting (#1363)
This commit refactors the `usageStatisticsEnabled` setting from a sub-property of the `telemetry` configuration to a top-level setting. This change simplifies the configuration by decoupling usage statistics from the telemetry settings.

The documentation has also been updated to reflect this change.
2025-06-24 00:29:31 +00:00
cperry-goog b3741f7016 docs: update configuration.md with new CLI options (#1360) 2025-06-23 17:24:55 -07:00
anj-s 3012684469 Fix duplicate startSession logs and duplicate logging events over the wire (#1357) 2025-06-24 00:05:42 +00:00
Seth Troisi 335802f4dd moving /save, /resume to /chat <save|resume> (#1355) 2025-06-23 16:56:08 -07:00
Jacob Richman f741630572 Polish Theme Dialog (#1356) 2025-06-23 16:43:17 -07:00
Seth Troisi 8c6545bf9d Include all chat messages (#1354) 2025-06-23 22:41:33 +00:00
Bryan Morgan e21dbed8c8 [June 25] Updated docs for telemetry and user statistics (#1346) 2025-06-23 22:25:49 +00:00
starsandskies 160d6a6552 Docs: Add telemetry to the list of configuration options (#1348) 2025-06-23 22:18:07 +00:00
Jerop Kipruto b443b5e800 Ensure telemetry events are flushed immediately (#1344)
The previous implementation used `flushIfNeeded` to batch most telemetry events, but it was not reliably sending them, leading to data loss. Notably, the `startSession` event, which already used `flushToClearcut`, was working correctly, indicating an issue with the batching logic itself.

This change replaces all calls to `flushIfNeeded` with `flushToClearcut` to align all event logging with the working `startSession` implementation and ensure that events are sent immediately. This prioritizes the reliability of data collection over network efficiency.

This is a temporary solution to prevent further data loss. The underlying issue with the batching mechanism in `flushIfNeeded` should be investigated and fixed in the future, at which point this change can be reverted.
2025-06-23 22:05:02 +00:00
Jerop Kipruto 58572a6eaa Use concurrently to run start script with GCP telemetry (#1329)
## TLDR

Introduces the `concurrently` package to simplify the dev startup process with GCP telemetry enabled.

## Dive Deeper

Previously, developers had to run the telemetry script and the main application start script in separate terminals. This change updates the `start:gcp` script to use `concurrently`, allowing both processes to be launched and managed with a single command. This improves the developer experience and reduces the chance of forgetting to start one of the required processes. 

## Reviewer Test Plan
Set the required environment variable:

```shell
export OTLP_GOOGLE_CLOUD_PROJECT=<your-project-id>
```

Run the following command:

```shell
npm run start:gcp
```

#750 

cc @teeler
2025-06-23 22:01:22 +00:00
Bryan Morgan e423d20a8d Updated docs and /stats command to support lack of token caching support for OAuth users (b/426943001) (#1307) 2025-06-23 21:55:24 +00:00
Sandy Tao fcb8be2fb9 Refine refresh static logic (#1349) 2025-06-23 21:45:15 +00:00
Jacob Richman 1faf53a3af Remove fallback to render normall rather than using custom MaxSizedBox layout logic (#1340) 2025-06-23 21:41:45 +00:00
N. Taylor Mullen fd58d3267e feat: Open MCP docs if no MCPs are configured (#1325) 2025-06-23 21:35:23 +00:00
Abhi dc76bcc433 Add error messaging for 429 errors (#1316) 2025-06-23 21:30:13 +00:00
Jerop Kipruto 21e6a36cf1 docs(telemetry): relocate telemetry documentation (#1327)
Moves the telemetry.md file from docs/core to the top-level docs/ directory to make it more discoverable.

Updates the link in the main index.md and removes the old reference from the CLI configuration page.
2025-06-23 21:24:48 +00:00
Jerop Kipruto 98f3a7066e refactor: rename disableDataCollection to dataCollectionEnabled (#1319)
Renames the `disableDataCollection` flag to the more intuitive and positive `dataCollectionEnabled`.

This change improves code clarity by avoiding double negatives and making the purpose of the flag more direct. The logic has been inverted wherever the flag is used to accommodate the new naming convention.

Using a suffix like `"Enabled"` follows a common convention that improves readability. 
- A condition like `if (dataCollectionEnabled)` reads like a natural language sentence ("if data collection is enabled"), which reduces cognitive load.
- Distinguishes the boolean flag (representing a state) from potential functions that would perform an action (e.g., `enableDataCollection()` or `disableDataCollection()`), avoiding ambiguity between checking a value and calling a function.

#750
2025-06-23 21:19:40 +00:00
Jerop Kipruto 4d88054d35 Fix batch flush to Clearcut (#1337)
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
2025-06-23 21:12:58 +00:00
starsandskies d2ae7af487 Edit pass of docs/core/index.md (#1338)
Make a pass through the docs/core/index.md file to hopefully improve readability and consistency.

Of particular note, I've cut out a big chunk that felt mostly like implementation details that aren't particularly relevant for public consumption, and I cut out a discussion of cli/core interconnection that is covered better higher up in the docs hierarchy
2025-06-23 21:07:03 +00:00
cperry-goog 139668c1d1 Docs/authentication doc (#1341) 2025-06-23 13:02:52 -07:00
cperry-goog 1825105d8f feat(docs): add /chat and /restore commands (#1336) 2025-06-23 12:10:23 -07:00
Jacob Richman 3e25c350f2 A couple of these log messages were now showing up every time on app startup (#1335) 2025-06-23 11:31:13 -07:00
N. Taylor Mullen 4b7307accb Remove .gitignore logging from startup. (#1323) 2025-06-23 11:12:42 -07:00
owenofbrien 631591ce79 Enable clearcut logging by default (#1309)
Clearcut logging can now be disabled via disableDataCollection in user settings
2025-06-23 10:18:58 -07:00
Tommaso Sciortino 07880d43d2 Sanitize MCP FunctionDeclarations to workaround Vertex bug (#1330) 2025-06-23 09:13:53 -07:00
Mark McDonald 7b39dd8b28 Added some use cases to the README (#1257) 2025-06-23 07:07:18 +00:00
matt korwel d0800ab917 Adding .gitattributes (#1303) 2025-06-23 06:58:41 +00:00
Scott Densmore 99d521569d Scotdensmore/first run auth fix (#1322) 2025-06-22 22:52:25 -07:00
Jacob Richman 156feff5b1 Fix so that pressing ctrl-s a second time toggles off constrain height mode (#1306) 2025-06-23 05:42:20 +00:00
Mark McDonald 523aeec544 Use shorter URL for docs link (#1324) 2025-06-23 05:37:41 +00:00
Jerop Kipruto 64e1c7df75 docs: update telemetry documentation and scripts (#1318) 2025-06-23 06:10:26 +01:00
matt korwel da128e725d {bug} Vertex Auth Support (#1302)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
2025-06-23 00:30:58 +00:00
Abhi d8ecbde9bd add bash block to doc for easy copypasta (#1317) 2025-06-22 20:19:13 -04:00
Billy Biggs c9bea8e646 Plumb extension context filenames through for /memory refresh (#1312) 2025-06-22 16:17:05 -07:00
cperry-goog b05b8673cd update tips (#1315) 2025-06-22 16:02:48 -07:00
Jacob Richman b831ffc1b3 Jacob314/auto exit unconstrained height mode (#1293)
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
2025-06-22 13:43:36 -07:00
N. Taylor Mullen 6a1c62731b Fix seatbelt sandboxing when GEMINI_SANDBOX="" and starting with -s (#1298) 2025-06-22 18:33:29 +00:00
N. Taylor Mullen 3c656cfbc6 feat: Only show ctrl-s when idle (#1299) 2025-06-22 15:57:53 +00:00
N. Taylor Mullen cb76b08e31 feat: Add /docs command and update UI (#1297) 2025-06-22 15:54:10 +00:00
owenofbrien 4cfab0a893 Clearcut logging - initial implementation (#1274)
Flag-guarded initial implementation of a clearcut logger to collect telemetry data and send it to Concord for dashboards, etc.
2025-06-22 14:26:48 +00:00
Abhi c9950b3cb2 feat: Add client-initiated tool call handling (#1292) 2025-06-22 01:35:36 -04:00
cperry-goog 5cf8dc4f07 Docs update (#1295) 2025-06-22 05:06:58 +00:00
Louis Jimenez 383212034c Provide a .gitconfig for shadow repos (#1294) 2025-06-22 00:13:03 -04:00
Billy Biggs 0779697da6 Add setting enableRecursiveFileSearch to control @-file completion (#1290) 2025-06-22 01:23:35 +00:00
Jacob Richman 63f6a497cb Jacob314/overflow notification and one MaxSizedBox bug fix (#1288) 2025-06-22 00:54:10 +00:00
cperry-goog e20171e7dd Updating missing commands (#1289) 2025-06-21 17:41:03 -07:00
cperry-goog 47780e984c docs: update README (#1287) 2025-06-21 16:18:23 -07:00
Marlon Gamez 076f81f130 point Code Assist client to prod server (#1201) 2025-06-21 22:25:18 +00:00
cperry-goog e255eb6124 feat(cli): update Tips.tsx component (#1284) 2025-06-21 12:58:00 -07:00
Billy Biggs 99a6dc0267 Update memory and context summary UI for multiple context filenames (#1282) 2025-06-21 19:15:43 +00:00
Sandy Tao 03af6235a9 Fix Static duplication and input prompt tearing (#1279) 2025-06-21 11:11:42 -07:00
Tommaso Sciortino f9b2a33732 Remove packages/cli/README.md (#1278) 2025-06-21 16:43:01 +01:00
190 changed files with 7713 additions and 2452 deletions
-47
View File
@@ -1,47 +0,0 @@
steps:
# Step 1: Install root dependencies (includes workspaces)
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
entrypoint: 'npm'
args: ['install']
# Step 2: Update version in root package.json
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
entrypoint: 'bash'
args:
- -c # Use bash -c to allow for command substitution and string manipulation
- |
current_version=$(npm pkg get version | sed 's/"//g')
new_version="$${current_version}-$SHORT_SHA.$_REVISION"
npm pkg set "version=$${new_version}"
echo "Set root package.json version to: $${new_version}"
# Step 3: Run prerelease:dev to update workspace versions and dependencies
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
entrypoint: 'npm'
args: ['run', 'prerelease:dev'] # This will run prerelease:version and prerelease:deps
# Step 4: Authenticate for Docker and NPM
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
entrypoint: 'npm'
args: ['run', 'auth']
# Step 5: Run the master release script
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
entrypoint: 'npm'
args: ['run', 'publish:release']
env:
- 'GEMINI_SANDBOX=$_CONTAINER_TOOL'
- 'SANDBOX_IMAGE_REGISTRY=$_SANDBOX_IMAGE_REGISTRY'
- 'SANDBOX_IMAGE_NAME=$_SANDBOX_IMAGE_NAME'
- 'NPM_PUBLISH_TAG=$_NPM_PUBLISH_TAG'
options:
defaultLogsBucketBehavior: REGIONAL_USER_OWNED_BUCKET
dynamicSubstitutions: true
substitutions:
_REVISION: '0'
_SANDBOX_IMAGE_REGISTRY: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers'
_SANDBOX_IMAGE_NAME: 'gemini-cli-sandbox'
_NPM_PUBLISH_TAG: 'head'
_CONTAINER_TOOL: 'docker'
+2 -1
View File
@@ -24,7 +24,8 @@ steps:
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
entrypoint: 'npm'
args: ['publish', '--tag=head', '--dry-run', '--workspace=@gemini-cli/cli']
args:
['publish', '--tag=head', '--dry-run', '--workspace=@google/gemini-cli']
options:
defaultLogsBucketBehavior: REGIONAL_USER_OWNED_BUCKET
+150
View File
@@ -0,0 +1,150 @@
steps:
# Step 1: Install root dependencies (includes workspaces)
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Install Dependencies'
entrypoint: 'npm'
args: ['install']
# Step 2: Update version in root package.json
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Set version in workspace root'
entrypoint: 'bash'
args:
- -c # Use bash -c to allow for command substitution and string manipulation
- |
current_version=$(npm pkg get version | sed 's/"//g')
if [ "$_OFFICIAL_RELEASE" = "true" ]; then
new_version="$current_version"
else
new_version="${current_version}-rc.$_REVISION"
fi
npm pkg set "version=${new_version}"
echo "Set root package.json version to: ${new_version}"
# Step 3: Binds the package versions to the version in the repo root's package.json
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Bind package versions to workspace root'
entrypoint: 'npm'
args: ['run', 'prerelease:dev'] # This will run prerelease:version and prerelease:deps
# Step 4: Authenticate for Docker (so we can push images to the artifact registry)
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Authenticate docker'
entrypoint: 'npm'
args: ['run', 'auth']
# Step 5: Build workspace packages
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Build packages'
entrypoint: 'npm'
args: ['run', 'build:packages']
# Step 6: Prepare CLI package.json for publishing
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Prepare @google/gemini-cli and @google/gemini-cli-core packages'
entrypoint: 'npm'
args: ['run', 'prepare:packages']
env:
- 'GEMINI_SANDBOX=$_CONTAINER_TOOL'
- 'SANDBOX_IMAGE_REGISTRY=$_SANDBOX_IMAGE_REGISTRY'
- 'SANDBOX_IMAGE_NAME=$_SANDBOX_IMAGE_NAME'
# Step 7: Build sandbox container image
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Build sandbox Docker image'
entrypoint: 'npm'
args: ['run', 'build:sandbox:fast']
env:
- 'GEMINI_SANDBOX=$_CONTAINER_TOOL'
- 'SANDBOX_IMAGE_REGISTRY=$_SANDBOX_IMAGE_REGISTRY'
- 'SANDBOX_IMAGE_NAME=$_SANDBOX_IMAGE_NAME'
# Step 8: Publish sandbox container image
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Publish sandbox Docker image'
entrypoint: 'npm'
args: ['run', 'publish:sandbox']
env:
- 'GEMINI_SANDBOX=$_CONTAINER_TOOL'
- 'SANDBOX_IMAGE_REGISTRY=$_SANDBOX_IMAGE_REGISTRY'
- 'SANDBOX_IMAGE_NAME=$_SANDBOX_IMAGE_NAME'
# Pre-Step 9: authenticate to our intermediate npm registry
# NOTE: when running locally, run this instead (from the `packages/core` directory):
# - `npm login --registry https://wombat-dressing-room.appspot.com`
# - use a 24hr token
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Setup @google/gemini-cli-core auth token for publishing'
entrypoint: 'bash'
args:
- -c
- |
echo "//wombat-dressing-room.appspot.com/:_authToken=$$CORE_PACKAGE_PUBLISH_TOKEN" > $$HOME/.npmrc
secretEnv: ['CORE_PACKAGE_PUBLISH_TOKEN']
# Step 9: Publish @google/gemini-cli-core to NPM
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Publish @google/gemini-cli-core package'
entrypoint: 'bash'
args:
- -c
- |
if [ "$_OFFICIAL_RELEASE" = "true" ]; then
npm publish --workspace=@google/gemini-cli-core --tag=latest
else
npm publish --workspace=@google/gemini-cli-core --tag=rc
fi
env:
- 'GEMINI_SANDBOX=$_CONTAINER_TOOL'
- 'SANDBOX_IMAGE_REGISTRY=$_SANDBOX_IMAGE_REGISTRY'
- 'SANDBOX_IMAGE_NAME=$_SANDBOX_IMAGE_NAME'
# Pre-Step 10: authenticate to our intermediate npm registry
# NOTE: when running locally, run this instead (from the `packages/cli` directory)
# - `npm login --registry https://wombat-dressing-room.appspot.com`
# - use a 24hr token
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Setup @google/gemini-cli auth token for publishing'
entrypoint: 'bash'
args:
- -c
- |
echo "//wombat-dressing-room.appspot.com/:_authToken=$$CLI_PACKAGE_PUBLISH_TOKEN" > $$HOME/.npmrc
secretEnv: ['CLI_PACKAGE_PUBLISH_TOKEN']
# Step 10: Publish @google/gemini-cli to NPM
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Publish @google/gemini-cli package'
entrypoint: 'bash'
args:
- -c
- |
if [ "$_OFFICIAL_RELEASE" = "true" ]; then
npm publish --workspace=@google/gemini-cli --tag=latest
else
npm publish --workspace=@google/gemini-cli --tag=rc
fi
env:
- 'GEMINI_SANDBOX=$_CONTAINER_TOOL'
- 'SANDBOX_IMAGE_REGISTRY=$_SANDBOX_IMAGE_REGISTRY'
- 'SANDBOX_IMAGE_NAME=$_SANDBOX_IMAGE_NAME'
options:
defaultLogsBucketBehavior: REGIONAL_USER_OWNED_BUCKET
dynamicSubstitutions: true
availableSecrets:
secretManager:
- versionName: ${_CLI_PACKAGE_WOMBAT_TOKEN_RESOURCE_NAME}
env: 'CLI_PACKAGE_PUBLISH_TOKEN'
- versionName: ${_CORE_PACKAGE_WOMBAT_TOKEN_RESOURCE_NAME}
env: 'CORE_PACKAGE_PUBLISH_TOKEN'
substitutions:
_REVISION: '0'
_OFFICIAL_RELEASE: 'false'
_CONTAINER_TOOL: 'docker'
_SANDBOX_IMAGE_REGISTRY: ''
_SANDBOX_IMAGE_NAME: ''
_CLI_PACKAGE_WOMBAT_TOKEN_RESOURCE_NAME: ''
_CORE_PACKAGE_WOMBAT_TOKEN_RESOURCE_NAME: ''
+24
View File
@@ -0,0 +1,24 @@
# Set the default behavior for all files to automatically handle line endings.
# This will ensure that all text files are normalized to use LF (line feed)
# line endings in the repository, which helps prevent cross-platform issues.
* text=auto eol=lf
# Explicitly declare files that must have LF line endings for proper execution
# on Unix-like systems.
*.sh eol=lf
*.bash eol=lf
Makefile eol=lf
# Explicitly declare binary file types to prevent Git from attempting to
# normalize their line endings.
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.pdf binary
*.woff binary
*.woff2 binary
*.eot binary
*.ttf binary
*.otf binary
-13
View File
@@ -1,13 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Additional context**
Add any other context about the problem here.
+52
View File
@@ -0,0 +1,52 @@
name: Bug Report
description: Report a bug to help us improve Gemini CLI
labels: ['kind/bug', 'status/need-triage']
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Please search [existing issues](https://github.com/google-gemini/gemini-cli/issues) to see if an issue already exists for the bug you encountered.
- type: textarea
id: problem
attributes:
label: What happened?
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: expected
attributes:
label: What did you expect to happen?
validations:
required: true
- type: textarea
id: info
attributes:
label: Client information
description: Please paste the full text from the `/about` command run from Gemini CLI. Also include which platform (MacOS, Windows, Linux).
value: |
<details>
```console
$ gemini /about
# paste output here
```
</details>
validations:
required: true
- type: textarea
id: login-info
attributes:
label: Login information
description: Describe how you are logging in (e.g., Google Account, API key).
- type: textarea
id: additional-context
attributes:
label: Anything else we need to know?
description: Add any other context about the problem here.
@@ -0,0 +1,30 @@
name: Feature Request
description: Suggest an idea for this project
labels: ['kind/enhancement', 'status/need-triage']
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to suggest an enhancement! Please search [existing issues](https://github.com/google-gemini/gemini-cli/issues) to see if a similar feature has already been requested.
- type: textarea
id: feature
attributes:
label: What would you like to be added?
description: A clear and concise description of the enhancement.
validations:
required: true
- type: textarea
id: rationale
attributes:
label: Why is this needed?
description: A clear and concise description of why this enhancement is needed.
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional context
description: Add any other context or screenshots about the feature request here.
+2 -1
View File
@@ -7,6 +7,7 @@ on:
branches: [main, release]
pull_request:
branches: [main, release]
merge_group:
jobs:
build:
@@ -36,7 +37,7 @@ jobs:
git diff --exit-code
- name: Run linter
run: npm run lint
run: npm run lint:ci
- name: Build project
run: npm run build
+2 -3
View File
@@ -4,9 +4,8 @@ name: E2E Tests
on:
push:
branches: [main, release]
pull_request:
branches: [main, release]
branches: [main]
merge_group:
jobs:
e2e-test:
+1 -1
View File
@@ -1 +1 @@
@gemini-cli:registry=https://us-west1-npm.pkg.dev/gemini-code-dev/gemini-code/
@google:registry=https://wombat-dressing-room.appspot.com
+2 -2
View File
@@ -12,9 +12,9 @@
"runtimeArgs": ["run", "start"],
"skipFiles": ["<node_internals>/**"],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"env": {
"GEMINI_SANDBOX": "false",
"GEMINI_API_KEY": "testkey"
"GEMINI_SANDBOX": "false"
}
},
{
+3 -3
View File
@@ -76,9 +76,9 @@ In the PR description, explain the "why" behind your changes and link to the rel
## Forking
If you are forking the repository you will be able to run the Built, Test and Integration test workflows. However in order to make the integration tests run you'll need to add a [Github Repository Secret](<[url](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository)>) with a value of `GEMINI_API_KEY` and set that to a valid API key that you have available. Your key and secret it private to your repo; no one without access can see your key and you cannot see any secrets related to this repo.
If you are forking the repository you will be able to run the Built, Test and Integration test workflows. However in order to make the integration tests run you'll need to add a [GitHub Repository Secret](<[url](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository)>) with a value of `GEMINI_API_KEY` and set that to a valid API key that you have available. Your key and secret are private to your repo; no one without access can see your key and you cannot see any secrets related to this repo.
Additionally you will need to click on the `Actions` tab and enable workflows for your repository, you'll find its the large blue button in the center of the screen.
Additionally you will need to click on the `Actions` tab and enable workflows for your repository, you'll find it's the large blue button in the center of the screen.
## Development Setup and Workflow
@@ -198,7 +198,7 @@ This command uses Prettier to format the code according to the project's style g
#### Linting
To separately lint the code in this project, run the following command fro the root directory:
To separately lint the code in this project, run the following command from the root directory:
```bash
npm run lint
+4 -4
View File
@@ -40,11 +40,11 @@ ENV PATH=$PATH:/usr/local/share/npm-global/bin
USER node
# install gemini-cli and clean up
COPY packages/cli/dist/gemini-cli-cli-*.tgz /usr/local/share/npm-global/gemini-cli-cli.tgz
COPY packages/core/dist/gemini-cli-core-*.tgz /usr/local/share/npm-global/gemini-cli-core.tgz
RUN npm install -g /usr/local/share/npm-global/gemini-cli-cli.tgz /usr/local/share/npm-global/gemini-cli-core.tgz \
COPY packages/cli/dist/google-gemini-cli-*.tgz /usr/local/share/npm-global/gemini-cli.tgz
COPY packages/core/dist/google-gemini-cli-core-*.tgz /usr/local/share/npm-global/gemini-core.tgz
RUN npm install -g /usr/local/share/npm-global/gemini-cli.tgz /usr/local/share/npm-global/gemini-core.tgz \
&& npm cache clean --force \
&& rm -f /usr/local/share/npm-global/gemini-cli-{cli,core}.tgz
&& rm -f /usr/local/share/npm-global/gemini-{cli,core}.tgz
# default entrypoint when none specified
CMD ["gemini"]
+2 -1
View File
@@ -71,7 +71,8 @@ JavaScript classes, by their nature, are designed to encapsulate internal state
- Reduced Boilerplate and Increased Conciseness: Classes often promote the use of constructors, this binding, getters, setters, and other boilerplate that can unnecessarily bloat code. TypeScript interface and type declarations provide powerful static type checking without the runtime overhead or verbosity of class definitions. This allows for more succinct and readable code, aligning with JavaScript's strengths in functional programming.
- Enhanced Readability and Predictability: Plain objects, especially when their structure is clearly defined by TypeScript interfaces, are often easier to read and understand. Their properties are directly accessible, and there's no hidden internal state or complex inheritance chains to navigate. This predictability leads to fewer bugs and a more maintainable codebase.
Simplified Immutability: While not strictly enforced, plain objects encourage an immutable approach to data. When you need to modify an object, you typically create a new one with the desired changes, rather than mutating the original. This pattern aligns perfectly with React's reconciliation process and helps prevent subtle bugs related to shared mutable state.
- Simplified Immutability: While not strictly enforced, plain objects encourage an immutable approach to data. When you need to modify an object, you typically create a new one with the desired changes, rather than mutating the original. This pattern aligns perfectly with React's reconciliation process and helps prevent subtle bugs related to shared mutable state.
- Better Serialization and Deserialization: Plain JavaScript objects are naturally easy to serialize to JSON and deserialize back, which is a common requirement in web development (e.g., for API communication or local storage). Classes, with their methods and prototypes, can complicate this process.
+1 -1
View File
@@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+113 -98
View File
@@ -2,120 +2,135 @@
[![Gemini CLI CI](https://github.com/google-gemini/gemini-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/google-gemini/gemini-cli/actions/workflows/ci.yml)
This repository contains the Gemini CLI tool.
![Gemini CLI Screenshot](./docs/assets/gemini-screenshot.png)
This repository contains the Gemini CLI, a command-line AI workflow tool that connects to your
tools, understands your code and accelerates your workflows.
With the Gemini CLI you can:
- Query and edit large codebases in and beyond Gemini's 1M token context window.
- Generate new apps from PDFs or sketches, using Gemini's multimodal capabilities.
- Automate operational tasks, like querying pull requests or handling complex rebases.
- Use tools and MCP servers to connect new capabilities, including [media generation with Imagen,
Veo or Lyria](https://github.com/GoogleCloudPlatform/vertex-ai-creative-studio/tree/main/experiments/mcp-genmedia)
- Ground your queries with the [Google Search](https://ai.google.dev/gemini-api/docs/grounding)
tool, built in to Gemini.
## Quickstart
1. [Install Node 18+](https://nodejs.org/en/download).
2. [Get an API key from Google AI Studio](https://aistudio.google.com/apikey).
3. Set the API key in your shell using the following command, replacing `YOUR_API_KEY` with the API key you obtained: `export GEMINI_API_KEY="YOUR_API_KEY"`.
4. Run the Gemini CLI from your shell using the following command: `npx https://github.com/google-gemini/gemini-cli`
5. Enjoy.
1. **Prerequisites:** Ensure you have [Node.js version 18](https://nodejs.org/en/download) or higher installed.
2. **Run the CLI:** Execute the following command in your terminal:
```bash
npx https://github.com/google-gemini/gemini-cli
```
Or install it with:
```bash
npm install -g @google/gemini-cli
gemini
```
3. **Pick a color theme**
4. **Authenticate:** When prompted, sign in with your personal Google account. This will grant you up to 60 model requests per minute and 1,000 model requests per day using Gemini.
You are now ready to use the Gemini CLI!
### For advanced use or increased limits:
If you need to use a specific model or require a higher request capacity, you can use an API key:
1. Generate a key from [Google AI Studio](https://aistudio.google.com/apikey).
2. Set it as an environment variable in your terminal. Replace `YOUR_API_KEY` with your generated key.
```bash
export GEMINI_API_KEY="YOUR_API_KEY"
```
For other authentication methods, including Google Workspace accounts, see the [authentication](./docs/cli/authentication.md) guide.
## Examples
Once the CLI is running, you can start interacting with Gemini from your shell. Try a simple query:
Once the CLI is running, you can start interacting with Gemini from your shell.
```
> How can I build a web app?
You can start a project from a new directory:
```sh
cd new-project/
gemini
> Write me a Gemini Discord bot that answers questions using a FAQ.md file I will provide
```
Or ask it to perform a task using its tools:
Or work with an existing project:
```
> List files in the current directory.
```sh
git clone https://github.com/google-gemini/gemini-cli
cd gemini-cli
gemini
> Give me a summary of all of the changes that went in yesterday
```
## API Key Setup
The Gemini CLI requires you to authenticate with Google's AI services. You'll need to configure **one** of the following authentication methods:
1. **Gemini Code Assist:**
- To enable this mode you only need set the GEMINI_CODE_ASSIST environment variable to true.
- You can temporarily set the environment variable in your current shell session using the following command:
```bash
export GEMINI_CODE_ASSIST="true"
```
- For repeated use, you can add the environment variable to your `.env` file (located in the project directory or user home directory) or your shell's configuration file (like `~/.bashrc`, `~/.zshrc`, or `~/.profile`). For example, the following command adds the environment variable to a `~/.bashrc` file:
```bash
echo 'export GEMINI_CODE_ASSIST="true"' >> ~/.bashrc
source ~/.bashrc
```
- There are two types of Google Accounts you can use with Gemini CLI:
- **Personal Google Account**: This is the standard, free account you use for services like Gmail, Google Photos, and Google Drive for personal use (e.g. your-name@gmail.com).
- **Google Workspace Account**: This is a paid service for businesses and organizations that provides a suite of productivity tools, including a custom email domain (e.g. your-name@your-company.com), enhanced security features, and administrative controls. These accounts are often managed by an employer or school.
- Google Workspace Account must configure a Google Cloud Project Id to use. You can temporarily set the environment variable in your current shell session using the following command:
```bash
export GOOGLE_CLOUD_PROJECT_ID="YOUR_PROJECT_ID"
```
- For repeated use, you can add the environment variable to your `.env` file (located in the project directory or user home directory) or your shell's configuration file (like `~/.bashrc`, `~/.zshrc`, or `~/.profile`). For example, the following command adds the environment variable to a `~/.bashrc` file:
```bash
echo 'export GOOGLE_CLOUD_PROJECT_ID="YOUR_PROJECT_ID"' >> ~/.bashrc
source ~/.bashrc
```
- During start up, Gemini CLI will direct you to a webpage for authentication. Once authenticated, your credentials will be cached locally so the web login can be skipped on subsequent runs. Cached credentials last about 20 hours before expiring.
- Note that the the web login must be done in a browser that can communicate with the machine Gemini Cli is being run from. (Specifically, the browser will be redirected to a localhost url that Gemini CLI will be listening on).
2. **Gemini API key:**
- Obtain your API key from Google AI Studio: [https://aistudio.google.com/app/apikey](https://aistudio.google.com/app/apikey)
- Set the `GEMINI_API_KEY` environment variable. In the following methods, replace `YOUR_GEMINI_API_KEY` with the API key you obtained from Google AI Studio:
- You can temporarily set the environment variable in your current shell session using the following command:
```bash
export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
```
- For repeated use, you can add the environment variable to your `.env` file (located in the project directory or user home directory) or your shell's configuration file (like `~/.bashrc`, `~/.zshrc`, or `~/.profile`). For example, the following command adds the environment variable to a `~/.bashrc` file:
```bash
echo 'export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"' >> ~/.bashrc
source ~/.bashrc
```
3. **Google API Key (Vertex AI Express Mode):**
- You can use a general Google Cloud API key if it has been enabled for the Gemini API or Vertex AI.
- Set the `GOOGLE_API_KEY` and `GOOGLE_GENAI_USE_VERTEXAI` environment variables. In the following methods, replace `YOUR_GEMINI_API_KEY` with your Google Cloud API key:
- You can temporarily set these environment variables in your current shell session using the following commands:
```bash
export GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"
export GOOGLE_GENAI_USE_VERTEXAI=true
```
- For repeated use, you can add the environment variables to your `.env` file (located in the project directory or user home directory) or your shell's configuration file (like `~/.bashrc`, `~/.zshrc`, or `~/.profile`). For example, the following commands adds the environment variables to a `~/.bashrc` file:
```bash
echo 'export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"' >> ~/.bashrc
echo 'export GOOGLE_GENAI_USE_VERTEXAI=true' >> ~/.bashrc
source ~/.bashrc
```
4. **Vertex AI (Project and Location):**
- Ensure you have a Google Cloud project and have enabled the Vertex AI API.
- Set up Application Default Credentials (ADC), using the following command:
```bash
gcloud auth application-default login
```
For more information, see [Set up Application Default Credentials for Google Cloud](https://cloud.google.com/docs/authentication/provide-credentials-adc).
- Set the `GOOGLE_CLOUD_PROJECT`, `GOOGLE_CLOUD_LOCATION`, and `GOOGLE_GENAI_USE_VERTEXAI` environment variables. In the following methods, replace `YOUR_PROJECT_ID` and `YOUR_PROJECT_LOCATION` with the relevant values for your project:
- You can temporarily set these environment variables in your current shell session using the following commands:
```bash
export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
export GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION" # e.g., us-central1
export GOOGLE_GENAI_USE_VERTEXAI=true
```
- For repeated use, you can add the environment variables to your `.env` file (located in the project directory or user home directory) or your shell's configuration file (like `~/.bashrc`, `~/.zshrc`, or `~/.profile`). For example, the following commands adds the environment variables to a `~/.bashrc` file:
```bash
echo 'export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"' >> ~/.bashrc
echo 'export GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"' >> ~/.bashrc
echo 'export GOOGLE_GENAI_USE_VERTEXAI=true' >> ~/.bashrc
source ~/.bashrc
```
### Next Steps
### Next steps
- Learn how to [contribute to or build from the source](./CONTRIBUTING.md).
- Explore the available **[CLI Commands](./docs/cli/commands.md)**.
- If you encounter any issues, review the **[Troubleshooting guide](./docs/troubleshooting.md)**.
- For more comprehensive documentation, see the [full documentation](./docs/index.md).
- Take a look at some [popular tasks](#popular-tasks) for more inspiration.
## Gemini APIs
### Troubleshooting
This project leverages the Gemini APIs to provide AI capabilities. For details on the terms of service governing the Gemini API, please refer to the [Gemini API Terms of Service](https://ai.google.dev/gemini-api/terms).
Head over to the [troubleshooting](docs/troubleshooting.md) guide if you're
having issues.
## Popular tasks
### Explore a new codebase
Start by `cd`ing into an existing or newly-cloned repository and running `gemini`.
```text
> Describe the main pieces of this system's architecture.
```
```text
> What security mechanisms are in place?
```
### Work with your existing code
```text
> Implement a first draft for GitHub issue #123.
```
```text
> Help me migrate this codebase to the latest version of Java. Start with a plan.
```
### Automate your workflows
Use MCP servers to integrate your local system tools with your enterprise collaboration suite.
```text
> Make me a slide deck showing the git history from the last 7 days, grouped by feature and team member.
```
```text
> Make a full-screen web app for a wall display to show our most interacted-with GitHub issues.
```
### Interact with your system
```text
> Convert all the images in this directory to png, and rename them to use dates from the exif data.
```
```text
> Organise my PDF invoices by month of expenditure.
```
## Terms of Service and Privacy Notice
For details on the terms of service and privacy notice applicable to your use of Gemini CLI, see the [Terms of Service and Privacy Notice](./docs/tos-privacy.md).
Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

+75
View File
@@ -0,0 +1,75 @@
# Checkpointing
The Gemini CLI includes a Checkpointing feature that automatically saves a snapshot of your project's state before any file modifications are made by AI-powered tools. This allows you to safely experiment with and apply code changes, knowing you can instantly revert back to the state before the tool was run.
## How It Works
When you approve a tool that modifies the file system (like `write_file` or `replace`), the CLI automatically creates a "checkpoint." This checkpoint includes:
1. **A Git Snapshot:** A commit is made in a special, shadow Git repository located in your home directory (`~/.gemini/history/<project_hash>`). This snapshot captures the complete state of your project files at that moment. It does **not** interfere with your own project's Git repository.
2. **Conversation History:** The entire conversation you've had with the agent up to that point is saved.
3. **The Tool Call:** The specific tool call that was about to be executed is also stored.
If you want to undo the change or simply go back, you can use the `/restore` command. Restoring a checkpoint will:
- Revert all files in your project to the state captured in the snapshot.
- Restore the conversation history in the CLI.
- Re-propose the original tool call, allowing you to run it again, modify it, or simply ignore it.
All checkpoint data, including the Git snapshot and conversation history, is stored locally on your machine. The Git snapshot is stored in the shadow repository while the conversation history and tool calls are saved in a JSON file in your project's temporary directory, typically located at `~/.gemini/tmp/<project_hash>/checkpoints`.
## Enabling the Feature
The Checkpointing feature is disabled by default. To enable it, you can either use a command-line flag or edit your `settings.json` file.
### Using the Command-Line Flag
You can enable checkpointing for the current session by using the `--checkpointing` flag when starting the Gemini CLI:
```bash
gemini --checkpointing
```
### Using the `settings.json` File
To enable checkpointing by default for all sessions, you need to edit your `settings.json` file.
Add the following key to your `settings.json`:
```json
{
"checkpointing": {
"enabled": true
}
}
```
## Using the `/restore` Command
Once enabled, checkpoints are created automatically. To manage them, you use the `/restore` command.
### List Available Checkpoints
To see a list of all saved checkpoints for the current project, simply run:
```
/restore
```
The CLI will display a list of available checkpoint files. These file names are typically composed of a timestamp, the name of the file being modified, and the name of the tool that was about to be run (e.g., `2025-06-22T10-00-00_000Z-my-file.txt-write_file`).
### Restore a Specific Checkpoint
To restore your project to a specific checkpoint, use the checkpoint file from the list:
```
/restore <checkpoint_file>
```
For example:
```
/restore 2025-06-22T10-00-00_000Z-my-file.txt-write_file
```
After running the command, your files and conversation will be immediately restored to the state they were in when the checkpoint was created, and the original tool prompt will reappear.
+81
View File
@@ -0,0 +1,81 @@
## Authentication Setup
The Gemini CLI requires you to authenticate with Google's AI services. On initial startup you'll need to configure **one** of the following authentication methods:
1. **Login with Google (Gemini Code Assist):**
- Use this option to log in with your google account.
- During initial startup, Gemini CLI will direct you to a webpage for authentication. Once authenticated, your credentials will be cached locally so the web login can be skipped on subsequent runs.
- Note that the web login must be done in a browser that can communicate with the machine Gemini CLI is being run from. (Specifically, the browser will be redirected to a localhost url that Gemini CLI will be listening on).
- <a id="workspace-gca">Users may have to specify a GOOGLE_CLOUD_PROJECT if:</a>
1. You have a Google Workspace account. Google Workspace is a paid service for businesses and organizations that provides a suite of productivity tools, including a custom email domain (e.g. your-name@your-company.com), enhanced security features, and administrative controls. These accounts are often managed by an employer or school.
1. You have recieved a free Code Assist license through the [Google Developer Program](https://developers.google.com/program/plans-and-pricing) (including qualified Google Developer Experts)
1. You have been assigned a license to a current Gemini Code Assist standard or enterprise subscription.
1. You are using the product outside the the [supported regions](https://developers.google.com/gemini-code-assist/resources/available-locations) for free individual usage.>
1. You are a Google account holder under the age of 18
- If you fall into one of these categories, you must first configure a Google Cloud Project Id to use, [enable the Gemini for Cloud API](https://cloud.google.com/gemini/docs/discover/set-up-gemini#enable-api) and [configure access permissions](https://cloud.google.com/gemini/docs/discover/set-up-gemini#grant-iam).
You can temporarily set the environment variable in your current shell session using the following command:
```bash
export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
```
- For repeated use, you can add the environment variable to your `.env` file (located in the project directory or user home directory) or your shell's configuration file (like `~/.bashrc`, `~/.zshrc`, or `~/.profile`). For example, the following command adds the environment variable to a `~/.bashrc` file:
```bash
echo 'export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"' >> ~/.bashrc
source ~/.bashrc
```
2. **<a id="gemini-api-key"></a>Gemini API key:**
- Obtain your API key from Google AI Studio: [https://aistudio.google.com/app/apikey](https://aistudio.google.com/app/apikey)
- Set the `GEMINI_API_KEY` environment variable. In the following methods, replace `YOUR_GEMINI_API_KEY` with the API key you obtained from Google AI Studio:
- You can temporarily set the environment variable in your current shell session using the following command:
```bash
export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
```
- For repeated use, you can add the environment variable to your `.env` file (located in the project directory or user home directory) or your shell's configuration file (like `~/.bashrc`, `~/.zshrc`, or `~/.profile`). For example, the following command adds the environment variable to a `~/.bashrc` file:
```bash
echo 'export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"' >> ~/.bashrc
source ~/.bashrc
```
3. **Vertex AI:**
- If not using express mode:
- Ensure you have a Google Cloud project and have enabled the Vertex AI API.
- Set up Application Default Credentials (ADC), using the following command:
```bash
gcloud auth application-default login
```
For more information, see [Set up Application Default Credentials for Google Cloud](https://cloud.google.com/docs/authentication/provide-credentials-adc).
- Set the `GOOGLE_CLOUD_PROJECT`, `GOOGLE_CLOUD_LOCATION`, and `GOOGLE_GENAI_USE_VERTEXAI` environment variables. In the following methods, replace `YOUR_PROJECT_ID` and `YOUR_PROJECT_LOCATION` with the relevant values for your project:
- You can temporarily set these environment variables in your current shell session using the following commands:
```bash
export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
export GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION" # e.g., us-central1
export GOOGLE_GENAI_USE_VERTEXAI=true
```
- For repeated use, you can add the environment variables to your `.env` file (located in the project directory or user home directory) or your shell's configuration file (like `~/.bashrc`, `~/.zshrc`, or `~/.profile`). For example, the following commands add the environment variables to a `~/.bashrc` file:
```bash
echo 'export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"' >> ~/.bashrc
echo 'export GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"' >> ~/.bashrc
echo 'export GOOGLE_GENAI_USE_VERTEXAI=true' >> ~/.bashrc
source ~/.bashrc
```
- If using express mode:
- Set the `GOOGLE_API_KEY` environment variable. In the following methods, replace `YOUR_GOOGLE_API_KEY` with your Vertex AI API key provided by express mode:
- You can temporarily set these environment variables in your current shell session using the following commands:
```bash
export GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"
export GOOGLE_GENAI_USE_VERTEXAI=true
```
- For repeated use, you can add the environment variables to your `.env` file (located in the project directory or user home directory) or your shell's configuration file (like `~/.bashrc`, `~/.zshrc`, or `~/.profile`). For example, the following commands add the environment variables to a `~/.bashrc` file:
```bash
echo 'export GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"' >> ~/.bashrc
echo 'export GOOGLE_GENAI_USE_VERTEXAI=true' >> ~/.bashrc
source ~/.bashrc
```
+30 -1
View File
@@ -10,6 +10,19 @@ Slash commands provide meta-level control over the CLI itself.
- **Description:** File an issue about Gemini CLI. By default, the issue is filed within the GitHub repository for Gemini CLI. The string you enter after `/bug` will become the headline for the bug being filed. The default `/bug` behavior can be modified using the `bugCommand` setting in your `.gemini/settings.json` files.
- **`/chat`**
- **Description:** Save and resume conversation history for branching conversation state interactively, or resuming a previous state from a later session.
- **Sub-commands:**
- **`save`**
- **Description:** Saves the current conversation history. You must add a `<tag>` for identifying the conversation state.
- **Usage:** `/chat save <tag>`
- **`resume`**
- **Description:** Resumes a conversation from a previous save.
- **Usage:** `/chat resume <tag>`
- **`list`**
- **Description:** Lists available tags for chat state resumption.
- **`/clear`**
- **Description:** Clear the terminal screen, including the visible session history and scrollback within the CLI. The underlying session data (for history recall) might be preserved depending on the exact implementation, but the visual display is cleared.
@@ -43,20 +56,36 @@ Slash commands provide meta-level control over the CLI itself.
- **Description:** Manage the AI's instructional context (hierarchical memory loaded from `GEMINI.md` files).
- **Sub-commands:**
- **`add`**:
- **Description:** Adds the following text to the AI's memory. Usage: `/memory add <text to remember>`
- **`show`**:
- **Description:** Display the full, concatenated content of the current hierarchical memory that has been loaded from all `GEMINI.md` files. This lets you inspect the instructional context being provided to the Gemini model.
- **`refresh`**:
- **Description:** Reload the hierarchical instructional memory from all `GEMINI.md` files found in the configured locations (global, project/ancestors, and sub-directories). This command updates the model with the latest `GEMINI.md` content.
- **Note:** For more details on how `GEMINI.md` files contribute to hierarchical memory, see the [CLI Configuration documentation](./configuration.md#4-geminimd-files-hierarchical-instructional-context).
- **`/restore`**
- **Description:** Restores the project files to the state they were in just before a tool was executed. This is particularly useful for undoing file edits made by a tool. If run without a tool call ID, it will list available checkpoints to restore from.
- **Usage:** `/restore [tool_call_id]`
- **Note:** Only available if the CLI is invoked with the `--checkpointing` option or configured via [settings](./configuration.md). See [Checkpointing documentation](../checkpointing.md) for more details.
- **`/stats`**
- **Description:** Display detailed statistics for the current Gemini CLI session, such as the session duration.
- **Description:** Display detailed statistics for the current Gemini CLI session, including token usage, cached token savings (when available), and session duration. Note: Cached token information is only displayed when cached tokens are being used, which occurs with API key authentication but not with OAuth authentication at this time.
- [**`/theme`**](./themes.md)
- **Description:** Open a dialog that lets you change the visual theme of Gemini CLI.
- **`/auth`**
- **Description:** Open a dialog that lets you change the authentication method.
- **`/about`**
- **Description:** Show version info. Please share this information when filing issues.
- [**`/tools`**](../tools/index.md)
- **Description:** Display a list of tools that are currently available within Gemini CLI.
+116 -24
View File
@@ -30,7 +30,6 @@ Gemini CLI uses `settings.json` files for persistent configuration. There are tw
In addition to a project settings file, a project's `.gemini` directory can contain other project-specific files related to Gemini CLI's operation, such as:
- [Custom sandbox profiles](#sandboxing) (e.g., `.gemini/sandbox-macos-custom.sb`, `.gemini/sandbox.Dockerfile`).
- [Telemtry configurations](../core/telemetry.md#running-an-otel-collector) (e.g., `.gemini/otel/`).
### Available settings in `settings.json`:
@@ -43,26 +42,28 @@ In addition to a project settings file, a project's `.gemini` directory can cont
- **`bugCommand`** (object):
- **Description:** Overrides the default URL for the `/bug` command.
- **Default:** `"urlTemplate": "https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.md&title={title}&body={body}"`
- **Default:** `"urlTemplate": "https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.yml&title={title}&info={info}"`
- **Properties:**
- **`urlTemplate`** (string): A URL that can contain `{title}` and `{body}` placeholders.
- **`urlTemplate`** (string): A URL that can contain `{title}` and `{info}` placeholders.
- **Example:**
```json
"bugCommand": {
"urlTemplate": "https://bug.example.com/new?title={title}&body={body}"
"urlTemplate": "https://bug.example.com/new?title={title}&info={info}"
}
```
- **`fileFiltering`** (object):
- **Description:** Controls git-aware file filtering behavior for @ commands and file discovery tools.
- **Default:** `"respectGitIgnore": true`
- **Default:** `"respectGitIgnore": true, "enableRecursiveFileSearch": true`
- **Properties:**
- **`respectGitIgnore`** (boolean): Whether to respect .gitignore patterns when discovering files. When set to `true`, git-ignored files (like `node_modules/`, `dist/`, `.env`) are automatically excluded from @ commands and file listing operations.
- **`enableRecursiveFileSearch`** (boolean): Whether to enable searching recursively for filenames under the current tree when completing @ prefixes in the prompt.
- **Example:**
```json
"fileFiltering": {
"respectGitIgnore": true
"respectGitIgnore": true,
"enableRecursiveFileSearch": false
}
```
@@ -147,9 +148,43 @@ In addition to a project settings file, a project's `.gemini` directory can cont
}
```
- **`mcpServerCommand`** (string, **deprecated**):
- **`checkpointing`** (object):
- This is a legacy setting for configuring a single MCP server. Use `mcpServers` instead.
- **Description:** Configures the checkpointing feature, which allows you to save and restore conversation and file states. See the [Checkpointing documentation](../checkpointing.md) for more details.
- **Default:** `{"enabled": false}`
- **Properties:**
- **`enabled`** (boolean): When `true`, the `/restore` command is available.
- **`preferredEditor`** (string):
- **Description:** Specifies the preferred editor to use for viewing diffs.
- **Default:** `vscode`
- **Example:** `"preferredEditor": "vscode"`
- **`telemetry`** (object)
- **Description:** Configures logging and metrics collection for Gemini CLI. For more information, see [Telemetry](../telemetry.md).
- **Default:** `{"enabled": false, "target": "local", "otlpEndpoint": "http://localhost:4317", "logPrompts": true}`
- **Properties:**
- **`enabled`** (boolean): Whether or not telemetry is enabled.
- **`target`** (string): The destination for collected telemetry. Supported values are `local` and `gcp`.
- **`otlpEndpoint`** (string): The endpoint for the OTLP Exporter.
- **`logPrompts`** (boolean): Whether or not to include the content of user prompts in the logs.
- **Example:**
```json
"telemetry": {
"enabled": true,
"target": "local",
"otlpEndpoint": "http://localhost:16686",
"logPrompts": false
}
```
- **`usageStatisticsEnabled`** (boolean):
- **Description:** Enables or disables the collection of usage statistics. See [Usage Statistics](#usage-statistics) for more information.
- **Default:** `true`
- **Example:**
```json
"usageStatisticsEnabled": false
```
### Example `settings.json`:
@@ -167,10 +202,25 @@ In addition to a project settings file, a project's `.gemini` directory can cont
"command": "node",
"args": ["mcp_server.js", "--verbose"]
}
}
},
"telemetry": {
"enabled": true,
"target": "local",
"otlpEndpoint": "http://localhost:4317",
"logPrompts": true
},
"usageStatisticsEnabled": true
}
```
## Shell History
The CLI keeps a history of shell commands you run. To avoid conflicts between different projects, this history is stored in a project-specific directory within your user's home folder.
- **Location:** `~/.gemini/tmp/<project_hash>/shell_history`
- `<project_hash>` is a unique identifier generated from your project's root path.
- The history is stored in a file named `shell_history`.
## Environment Variables & `.env` Files
Environment variables are a common way to configure applications, especially for sensitive information like API keys or for settings that might change between environments.
@@ -187,18 +237,21 @@ The CLI automatically loads environment variables from an `.env` file. The loadi
- Set this in your shell profile (e.g., `~/.bashrc`, `~/.zshrc`) or an `.env` file.
- **`GEMINI_MODEL`**:
- Specifies the default Gemini model to use.
- Overrides the hardcoded default, which is currently `gemini-2.5-pro-preview-05-06`.
- Example: `export GEMINI_MODEL="gemini-1.5-flash-latest"`
- Overrides the hardcoded default
- Example: `export GEMINI_MODEL="gemini-2.5-flash"`
- **`GOOGLE_API_KEY`**:
- Your Google Cloud API key.
- Required for using Vertex AI in express mode.
- Ensure you have the necessary permissions and set the `GOOGLE_GENAI_USE_VERTEXAI=true` environment variable.
- Example: `export GOOGLE_API_KEY="YOUR_GOOGLE_API_ KEY"`.
- Example: `export GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"`.
- **`GOOGLE_CLOUD_PROJECT`**:
- Your Google Cloud Project ID.
- Required for using Code Assist or Vertex AI.
- If using Vertex AI, ensure you have the necessary permissions and set the `GOOGLE_GENAI_USE_VERTEXAI=true` environment variable.
- Example: `export 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"`
- **`OTLP_GOOGLE_CLOUD_PROJECT`**:
- Your Google Cloud Project ID for Telemetry in Google Cloud
- Example: `export OTLP_GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"`.
@@ -207,11 +260,6 @@ The CLI automatically loads environment variables from an `.env` file. The loadi
- Required for using Vertex AI in non express mode.
- If using Vertex AI, ensure you have the necessary permissions and set the `GOOGLE_GENAI_USE_VERTEXAI=true` environment variable.
- Example: `export GOOGLE_CLOUD_LOCATION="YOUR_PROJECT_LOCATION"`.
- **`GEMINI_CODE_ASSIST`**:
- Enables Code Assist functionality.
- Accepts `true`, `false`, or a custom command string.
- If you are using an Enterprise account you should also set the `GOOGLE_CLOUD_PROJECT` environment variable.
- Example: `export GEMINI_CODE_ASSIST=true`.
- **`GEMINI_SANDBOX`**:
- Alternative to the `sandbox` setting in `settings.json`.
- Accepts `true`, `false`, `docker`, `podman`, or a custom command string.
@@ -234,19 +282,37 @@ The CLI automatically loads environment variables from an `.env` file. The loadi
Arguments passed directly when running the CLI can override other configurations for that specific session.
- **`--model <model_name>`** (or **`-m <model_name>`**):
- **`--model <model_name>`** (**`-m <model_name>`**):
- Specifies the Gemini model to use for this session.
- Example: `npm start -- --model gemini-1.5-pro-latest`
- **`--sandbox`** (or **`-s`**):
- Enables sandbox mode for this session. The exact behavior might depend on other sandbox configurations (environment variables, settings files).
- **`--debug_mode`** (or **`-d`**):
- **`--prompt <your_prompt>`** (**`-p <your_prompt>`**):
- Used to pass a prompt directly to the command. This invokes Gemini CLI in a non-interactive mode.
- **`--sandbox`** (**`-s`**):
- Enables sandbox mode for this session.
- **`--sandbox-image`**:
- Sets the sandbox image URI.
- **`--debug_mode`** (**`-d`**):
- Enables debug mode for this session, providing more verbose output.
- **`--question <your_question>`** (or **`-q <your_question>`**):
- Used to pass a question directly to the command, especially when piping input to the CLI.
- **`--all_files`** (or **`-a`**):
- **`--all_files`** (**`-a`**):
- If set, recursively includes all files within the current directory as context for the prompt.
- **`--help`** (or **`-h`**):
- Displays help information about command-line arguments.
- **`--show_memory_usage`**:
- Displays the current memory usage.
- **`--yolo`**:
- Enables YOLO mode, which automatically approves all tool calls.
- **`--telemetry`**:
- Enables [telemetry](../telemetry.md).
- **`--telemetry-target`**:
- Sets the telemetry target. See [telemetry](../telemetry.md) for more information.
- **`--telemetry-otlp-endpoint`**:
- Sets the OTLP endpoint for telemetry. See [telemetry](../telemetry.md) for more information.
- **`--telemetry-log-prompts`**:
- Enables logging of prompts for telemetry. See [telemetry](../telemetry.md) for more information.
- **`--checkpointing`**:
- Enables [checkpointing](./commands.md#checkpointing-commands).
- **`--version`**:
- Displays the version of the CLI.
## Context Files (Hierarchical Instructional Context)
@@ -335,3 +401,29 @@ When `.gemini/sandbox.Dockerfile` exists, you can use `BUILD_SANDBOX` environmen
```bash
BUILD_SANDBOX=1 gemini -s
```
## Usage Statistics
To help us improve the Gemini CLI, we collect anonymized usage statistics. This data helps us understand how the CLI is used, identify common issues, and prioritize new features.
**What we collect:**
- **Tool Calls:** We log the names of the tools that are called, whether they succeed or fail, and how long they take to execute. We do not collect the arguments passed to the tools or any data returned by them.
- **API Requests:** We log the Gemini model used for each request, the duration of the request, and whether it was successful. We do not collect the content of the prompts or responses.
- **Session Information:** We collect information about the configuration of the CLI, such as the enabled tools and the approval mode.
**What we DON'T collect:**
- **Personally Identifiable Information (PII):** We do not collect any personal information, such as your name, email address, or API keys.
- **Prompt and Response Content:** We do not log the content of your prompts or the responses from the Gemini model.
- **File Content:** We do not log the content of any files that are read or written by the CLI.
**How to opt out:**
You can opt out of usage statistics collection at any time by setting the `usageStatisticsEnabled` property to `false` in your `settings.json` file:
```json
{
"usageStatisticsEnabled": false
}
```
+2
View File
@@ -4,8 +4,10 @@ Within Gemini CLI, `packages/cli` is the frontend for users to send and receive
## Navigating this section
- **[Authentication](./authentication.md):** A guide to setting up authentication with Google's AI services.
- **[Commands](./commands.md):** A reference for Gemini CLI commands (e.g., `/help`, `/tools`, `/theme`).
- **[Configuration](./configuration.md):** A guide to tailoring Gemini CLI behavior using configuration files.
- **[Token Caching](./token-caching.md):** Optimize API costs through token caching.
- **[Themes](./themes.md)**: A guide to customizing the CLI's appearance with different themes.
- **[Tutorials](tutorials.md)**: A tutorial showing how to use Gemini CLI to automate a development task.
+1 -1
View File
@@ -30,7 +30,7 @@ Gemini CLI comes with a selection of pre-defined themes, which you can list usin
### Theme Persistence
Selected themes are saved in Gemini CLI's [configuration](./docs/cli/configuration.md) so your preference is remembered across sessions.
Selected themes are saved in Gemini CLI's [configuration](./configuration.md) so your preference is remembered across sessions.
## Dark Themes
+14
View File
@@ -0,0 +1,14 @@
# Token Caching and Cost Optimization
Gemini CLI automatically optimizes API costs through token caching when using API key authentication (Gemini API key or Vertex AI). This feature reuses previous system instructions and context to reduce the number of tokens processed in subsequent requests.
**Token caching is available for:**
- API key users (Gemini API key)
- Vertex AI users (with project and location setup)
**Token caching is not available for:**
- OAuth users (Google Personal/Enterprise accounts) - the Code Assist API does not support cached content creation at this time
You can view your token usage and cached token savings using the `/stats` command. When cached tokens are available, they will be displayed in the stats output.
+21 -43
View File
@@ -1,76 +1,54 @@
# Gemini CLI Core
The Gemini CLI Core (`packages/core`) acts as the backend engine for the Gemini CLI, handling communication with the Gemini API, managing tools, and processing requests from the CLI client. For a general overview of the Gemini CLI, see the [main documentation page](../index.md).
Gemini CLI's core package (`packages/core`) is the backend portion of Gemini CLI, handling communication with the Gemini API, managing tools, and processing requests sent from `packages/cli`. For a general overview of Gemini CLI, see the [main documentation page](../index.md).
## Navigating this Section
## Navigating this section
- **[Core Configuration](../cli/configuration.md):** Details on how to configure the core component, including environment variables and specific settings.
- **[Core Tools API](./tools-api.md):** Information on how tools are defined, registered, and used by the core.
- **[Core tools API](./tools-api.md):** Information on how tools are defined, registered, and used by the core.
## Role of the Core
## Role of the core
The core package is a crucial part of the Gemini CLI ecosystem. While the CLI (`packages/cli`) provides the user interface, the core is responsible for:
While the `packages/cli` portion of Gemini CLI provides the user interface, `packages/core` is responsible for:
- **API Interaction:** Securely communicating with the Google Gemini API, sending user prompts, and receiving model responses.
- **Prompt Engineering:** Constructing effective prompts for the Gemini model, potentially incorporating conversation history, tool definitions, and instructional context from `GEMINI.md` files.
- **Tool Management & Orchestration:**
- **Gemini API interaction:** Securely communicating with the Google Gemini API, sending user prompts, and receiving model responses.
- **Prompt engineering:** Constructing effective prompts for the Gemini model, potentially incorporating conversation history, tool definitions, and instructional context from `GEMINI.md` files.
- **Tool management & orchestration:**
- Registering available tools (e.g., file system tools, shell command execution).
- Interpreting tool use requests from the Gemini model.
- Executing the requested tools with the provided arguments.
- Returning tool execution results to the Gemini model for further processing.
- **Session and State Management:** Keeping track of the conversation state, including history and any relevant context required for coherent interactions.
- **Session and state management:** Keeping track of the conversation state, including history and any relevant context required for coherent interactions.
- **Configuration:** Managing core-specific configurations, such as API key access, model selection, and tool settings.
## Key Components and Functionality
While the exact implementation details are within the `packages/core/src/` directory, key conceptual components include:
- **API Client** (`client.ts`): A module responsible for making HTTP requests to the Gemini API, handling authentication, and parsing responses.
- **Prompt Management** (`prompts.ts`): Logic for creating and formatting the prompts sent to the Gemini model. This includes integrating user queries, historical context, and tool specifications.
- **Tool Registry and Execution** (`tool-registry.ts`, `tools.ts`, individual tool files like `read-file.ts`, `shell.ts`):
- A system for discovering, registering, and describing available tools to the Gemini model.
- Code for executing each tool safely and effectively, often involving interaction with the operating system or external services.
- **Configuration (`config.ts`):** Handles loading and providing access to core-side configurations, including API keys, model choices, and potentially tool-specific settings.
- **Turn Management (`turn.ts`):** Manages the flow of a single conversational turn, from receiving user input to generating a final response, potentially involving multiple tool calls.
## Interaction with the CLI
The CLI and Core typically communicate over a local interface (e.g., standard input/output, or a local network connection if designed for broader use, though the current structure suggests a tightly coupled Node.js application).
1. The CLI captures user input and forwards it to the Core.
2. The Core processes the input, interacts with the Gemini API and tools as needed.
3. The Core sends responses (text, tool calls, errors) back to the CLI.
4. The CLI formats and displays these responses to the user.
## Security Considerations
## Security considerations
The core plays a vital role in security:
- **API Key Management:** It handles the `GEMINI_API_KEY` and ensures it is used securely when communicating with the Gemini API.
- **Tool Execution:** When tools interact with the local system (e.g., `run_shell_command`), the core (and its underlying tool implementations) must do so with appropriate caution, often involving sandboxing mechanisms to prevent unintended side effects.
- **API key management:** It handles the `GEMINI_API_KEY` and ensures it's used securely when communicating with the Gemini API.
- **Tool execution:** When tools interact with the local system (e.g., `run_shell_command`), the core (and its underlying tool implementations) must do so with appropriate caution, often involving sandboxing mechanisms to prevent unintended modifications.
## Chat History Compression
## Chat history compression
To ensure that long conversations don't exceed the token limits of the Gemini model, the CLI includes a chat history compression feature.
To ensure that long conversations don't exceed the token limits of the Gemini model, the core includes a chat history compression feature.
When a conversation approaches the token limit for the configured model, the CLI will automatically compress the conversation history before sending it to the model. This compression is designed to be lossless in terms of the information conveyed, but it reduces the overall number of tokens used.
When a conversation approaches the token limit for the configured model, the core automatically compresses the conversation history before sending it to the model. This compression is designed to be lossless in terms of the information conveyed, but it reduces the overall number of tokens used.
You can find the token limits for each model in the [Google AI documentation](https://ai.google.dev/gemini-api/docs/models).
## Model Fallback
## Model fallback
The Gemini CLI includes a model fallback mechanism to ensure that you can continue to use the CLI even if the default "pro" model is rate-limited.
Gemini CLI includes a model fallback mechanism to ensure that you can continue to use the CLI even if the default "pro" model is rate-limited.
If you are using the default "pro" model and the CLI detects that you are being rate-limited, it will automatically switch to the "flash" model for the current session. This allows you to continue working without interruption.
If you are using the default "pro" model and the CLI detects that you are being rate-limited, it automatically switches to the "flash" model for the current session. This allows you to continue working without interruption.
## File Discovery Service
## File discovery service
The file discovery service is responsible for finding files in the project that are relevant to the current context. It is used by the `@` command and other tools that need to access files.
## Memory Discovery Service
## Memory discovery service
The memory discovery service is responsible for finding and loading the `GEMINI.md` files that provide context to the model. It searches for these files in a hierarchical manner, starting from the current working directory and moving up to the project root and the user's home directory. It also searches in subdirectories.
This allows you to have global, project-level, and component-level context files, which are all combined to provide the model with the most relevant information.
You can use the `/memory show` command to see the combined content of all loaded `GEMINI.md` files, and the `/memory refresh` command to reload them.
You can use the [`/memory` command](../cli/commands.md) to `show`, `add`, and `refresh` the content of loaded `GEMINI.md` files.
-287
View File
@@ -1,287 +0,0 @@
# Gemini CLI Observability Guide
Telemetry provides crucial data about the Gemini CLI's performance, health, and usage. By enabling it, you can monitor operations, debug issues, and optimize tool usage through traces, metrics, and structured logs.
This entire system is built on the **[OpenTelemetry] (OTEL)** standard, allowing you to send data to any compatible backend, from your local terminal to a cloud service.
[OpenTelemetry]: https://opentelemetry.io/
## Quick Start
### Telemetry with Google Cloud
1. **Ensure Prerequisites:**
Ensure that:
- You have exported the `OTLP_GOOGLE_CLOUD_PROJECT` environment variable.
- You have authenticated with Google Cloud and have the necessary IAM roles.
For full details, see the [Google Cloud](#google-cloud) prerequisites.
1. **Run the Command:** Execute the following command from the project root:
```bash
npm run telemetry -- --target=gcp
```
1. **Run Gemini CLI:** In a separate terminal, run your Gemini CLI commands. This will generate telemetry data that the collector will capture.
1. **View Data:** The script will provide links to view your telemetry data (traces, metrics, logs) in the Google Cloud Console.
1. **Details:** Refer to documentation for telemetry in [Google Cloud](#google-cloud).
### Local Telemetry with Jaeger UI (for Traces)
1. **Run the Command:** Execute the following command from the project root:
```bash
npm run telemetry -- --target=local
```
2. **Run Gemini CLI:** In a separate terminal, run your Gemini CLI commands. This will generate telemetry data that the collector will capture.
3. **View Logs/Metrics:** Check the `.gemini/otel/collector.log` file for raw logs and metrics.
4. **View Traces:** Open your browser and go to `http://localhost:16686` to see traces in the Jaeger UI.
5. **Details:** Refer to documentation for telemetry in [Local](#local).
## Enabling Telemetry
You can enable telemetry in multiple ways. [Configuration](configuration.md) is primarily managed via the `.gemini/settings.json` file and environment variables, but CLI flags can override these settings for a specific session.
> **A Note on Sandbox Mode:** Telemetry is not compatible with sandbox mode at this time. Turn off sandbox mode before enabling telemetry. Tracked in #894.
**Order of Precedence:**
Telemetry settings are resolved in the following order (highest precedence first):
1. **CLI Flags (for `gemini` command):**
- `--telemetry` / `--no-telemetry`: Overrides `telemetry.enabled`. If this flag is not provided, telemetry is disabled unless enabled in settings files.
- `--telemetry-target <local|gcp>`: Overrides `telemetry.target`.
- `--telemetry-otlp-endpoint <URL>`: Overrides `telemetry.otlpEndpoint`.
- `--telemetry-log-prompts` / `--no-telemetry-log-prompts`: Overrides `telemetry.logPrompts`.
2. **Environment Variables:**
- `OTEL_EXPORTER_OTLP_ENDPOINT`: Overrides `telemetry.otlpEndpoint` if no `--telemetry-otlp-endpoint` flag is present.
3. **Workspace Settings File (`.gemini/settings.json`):** Values from the `telemetry` object in this project-specific file.
4. **User Settings File (`~/.gemini/settings.json`):** Values from the `telemetry` object in this global user file.
5. **Defaults:** applied if not set by any of the above.
- `telemetry.enabled`: `false`
- `telemetry.target`: `local`
- `telemetry.otlpEndpoint`: `http://localhost:4317`
- `telemetry.logPrompts`: `true`
**For the `npm run telemetry -- --target=<gcp|local>` script:**
The `--target` argument to this script _only_ overrides the `telemetry.target` for the duration and purpose of that script (i.e., choosing which collector to start). It does not permanently change your `settings.json`. The script will first look at `settings.json` for a `telemetry.target` to use as its default.
**Example settings:**
Add these lines to configure telemetry in your workspace (`.gemini/settings.json`) or user (`~/.gemini/settings.json`) settings for GCP:
```json
{
"telemetry": {
"enabled": true,
"target": "gcp"
},
"sandbox": false
}
```
## Running an OTEL Collector
An OTEL Collector is a service that receives, processes, and exports telemetry data.
The CLI sends data using the OTLP/gRPC protocol.
Learn more about OTEL exporter standard configuration in [documentation][otel-config-docs].
[otel-config-docs]: https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/
### Configuration
1. Install [otelcol-contrib] or use [docker]
[otelcol-contrib]: https://github.com/open-telemetry/opentelemetry-collector-contrib
[docker]: https://www.docker.com/
2. Create a folder for the OTEL configurations:
```
mkdir .gemini/otel
```
### Local
Use the `npm run telemetry -- --target=local` command which automates the entire process of setting up a local telemetry pipeline, including configuring the necessary settings in your `.gemini/settings.json` file. The underlying script installs `otelcol-contrib` (The OpenTelemetry Collector) and `jaeger` (The Jaeger UI for viewing traces). To use it:
1. **Run the Command**:
Execute the command from the root of the repository:
```bash
npm run telemetry -- --target=local
```
The script will:
- Download Jaeger and OTEL if needed.
- Start a local Jaeger instance.
- Start an OTEL collector configured to receive data from the Gemini CLI.
- Automatically enable telemetry in your workspace settings.
- On exit, disable telemetry.
2. **View Traces**:
Open your web browser and navigate to **http://localhost:16686** to access the Jaeger UI. Here you can inspect detailed traces of Gemini CLI operations.
3. **Inspect Logs and Metrics**:
The script redirects the OTEL collector's output (which includes logs and metrics) to `.gemini/otel/collector.log`. You can monitor this file to see the raw telemetry data:
```bash
tail -f .gemini/otel/collector.log
```
4. **Stop the Services**:
Press `Ctrl+C` in the terminal where the script is running to stop the OTEL Collector and Jaeger services.
### Google Cloud
Use the `npm run telemetry -- --target=gcp` command which automates setting up a local OpenTelemetry collector that forwards data to your Google Cloud project, including configuring the necessary settings in your `.gemini/settings.json` file. The underlying script installs `otelcol-contrib`. To use it:
1. **Prerequisites**:
- Ensure you have a Google Cloud Project ID.
- Export the `GOOGLE_CLOUD_PROJECT` environment variable to make it available to the OTEL collector.
```bash
export OTLP_GOOGLE_CLOUD_PROJECT="your-project-id"
```
- Authenticate with Google Cloud (e.g., run `gcloud auth application-default login` or ensure `GOOGLE_APPLICATION_CREDENTIALS` is set).
- Ensure your account/service account has the necessary roles: "Cloud Trace Agent", "Monitoring Metric Writer", and "Logs Writer".
2. **Run the Command**:
Execute the command from the root of the repository:
```bash
npm run telemetry -- --target=gcp
```
The script will:
- Download the `otelcol-contrib` binary if needed.
- Start an OTEL collector configured to receive data from the Gemini CLI and export it to your specified Google Cloud project.
- Automatically enable telemetry and disable sandbox mode in your workspace settings (`.gemini/settings.json`).
- Provide direct links to view traces, metrics, and logs in your Google Cloud Console.
- On exit (Ctrl+C), it will attempt to restore your original telemetry and sandbox settings.
3. **Run Gemini CLI:**
In a separate terminal, run your Gemini CLI commands. This will generate telemetry data that the collector will capture.
4. **View Telemetry in Google Cloud**:
Use the links provided by the script to navigate to the Google Cloud Console and view your traces, metrics, and logs.
5. **Inspect Local Collector Logs**:
The script redirects the local OTEL collector\'s output to `.gemini/otel/collector-gcp.log`. You can monitor this file for detailed information or troubleshooting:
```bash
tail -f .gemini/otel/collector-gcp.log
```
6. **Stop the Service**:
Press `Ctrl+C` in the terminal where the script is running to stop the OTEL Collector.
## Data Reference: Logs & Metrics
A `sessionId` is included as a common attribute on all logs and metrics.
### Logs
These are timestamped records of specific events.
- `gemini_cli.config`: Fired once at startup with the CLI's configuration.
- **Attributes**:
- `model` (string)
- `embedding_model` (string)
- `sandbox_enabled` (boolean)
- `core_tools_enabled` (string)
- `approval_mode` (string)
- `api_key_enabled` (boolean)
- `vertex_ai_enabled` (boolean)
- `code_assist_enabled` (boolean)
- `log_prompts_enabled` (boolean)
- `file_filtering_respect_git_ignore` (boolean)
- `debug_mode` (boolean)
- `mcp_servers` (string)
- `gemini_cli.user_prompt`: Fired when a user submits a prompt.
- **Attributes**:
- `prompt_length`
- `prompt` (except if `log_prompts_enabled` is false)
- `gemini_cli.tool_call`: Fired for every function call.
- **Attributes**:
- `function_name`
- `function_args`
- `duration_ms`
- `success` (boolean)
- `decision` (string: "accept", "reject", or "modify", optional)
- `error` (optional)
- `error_type` (optional)
- `gemini_cli.api_request`: Fired when making a request to the Gemini API.
- **Attributes**:
- `model`
- `request_text` (optional)
- `gemini_cli.api_error`: Fired if the API request fails.
- **Attributes**:
- `model`
- `error`
- `error_type`
- `status_code`
- `duration_ms`
- `gemini_cli.api_response`: Fired upon receiving a response from the Gemini API.
- **Attributes**:
- `model`
- `status_code`
- `duration_ms`
- `error` (optional)
- `input_token_count`
- `output_token_count`
- `cached_content_token_count`
- `thoughts_token_count`
- `tool_token_count`
- `response_text` (optional)
### Metrics
These are numerical measurements of behavior over time.
- `gemini_cli.session.count` (Counter, Int): Incremented once per CLI startup.
- `gemini_cli.tool.call.count` (Counter, Int): Counts tool calls.
- **Attributes**:
- `function_name`
- `success` (boolean)
- `decision` (string: "accept", "reject", or "modify", optional)
- `gemini_cli.tool.call.latency` (Histogram, ms): Measures tool call latency.
- **Attributes**:
- `function_name`
- `decision` (string: "accept", "reject", or "modify", optional)
- `gemini_cli.api.request.count` (Counter, Int): Counts all API requests.
- **Attributes**:
- `model`
- `status_code`
- `error_type` (optional)
- `gemini_cli.api.request.latency` (Histogram, ms): Measures API request latency.
- **Attributes**:
- `model`
- `gemini_cli.token.usage` (Counter, Int): Counts the number of tokens used.
- **Attributes**:
- `model`
- `type` (string: "input", "output", "thought", "cache", or "tool")
- `gemini_cli.file.operation.count` (Counter, Int): Counts file operations.
- **Attributes**:
- `operation` (string: "create", "read", "update"): The type of file operation.
- `lines` (optional, Int): Number of lines in the file.
- `mimetype` (optional, string): Mimetype of the file.
- `extension` (optional, string): File extension of the file.
+3
View File
@@ -44,6 +44,9 @@ The core comes with a suite of pre-defined tools, typically found in `packages/c
- `ShellTool` (`shell.ts`): Executes arbitrary shell commands (requires careful sandboxing and user confirmation).
- **Web Tools:**
- `WebFetchTool` (`web-fetch.ts`): Fetches content from a URL.
- `WebSearchTool` (`web-search.ts`): Performs a web search.
- **Memory Tools:**
- `MemoryTool` (`memoryTool.ts`): Interacts with the AI's memory.
Each of these tools extends `BaseTool` and implements the required methods for its specific functionality.
+8 -8
View File
@@ -16,7 +16,7 @@ This is the recommended way for end-users to install Gemini CLI. It involves dow
```bash
# Install the CLI globally
npm install -g @gemini-cli/cli
npm install -g @google/gemini-cli
# Now you can run the CLI from anywhere
gemini
@@ -25,7 +25,7 @@ This is the recommended way for end-users to install Gemini CLI. It involves dow
- **NPX execution:**
```bash
# Execute the latest version from NPM without a global install
npx @gemini-cli/cli
npx @google/gemini-cli
```
---
@@ -38,7 +38,7 @@ For security and isolation, Gemini CLI can be run inside a container. This is th
You can run the published sandbox image directly. This is useful for environments where you only have Docker and want to run the CLI.
```bash
# Run the published sandbox image
docker run --rm -it us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.1.0
docker run --rm -it us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.1.1
```
- **Using the `--sandbox` flag:**
If you have Gemini CLI installed locally (using the standard installation described above), you can instruct it to run inside the sandbox container.
@@ -73,11 +73,11 @@ Contributors to the project will want to run the CLI directly from the source co
### 4. Running the latest Gemini CLI commit from GitHub
You can run the most recently committed version of Gemini CLI directly from the GitHub repository. This is useful for testing features still in developement.
You can run the most recently committed version of Gemini CLI directly from the GitHub repository. This is useful for testing features still in development.
```bash
# Execute the CLI directly from the main branch on GitHub
npx https://github.com/google/gemini-cli
npx https://github.com/google-gemini/gemini-cli
```
## Deployment architecture
@@ -88,8 +88,8 @@ The execution methods described above are made possible by the following archite
Gemini CLI project is a monorepo that publishes two core packages to the NPM registry:
- `@gemini-cli/core`: The backend, handling logic and tool execution.
- `@gemini-cli/cli`: The user-facing frontend.
- `@google/gemini-cli-core`: The backend, handling logic and tool execution.
- `@google/gemini-cli`: The user-facing frontend.
These packages are used when performing the standard installation and when running Gemini CLI from the source.
@@ -97,7 +97,7 @@ These packages are used when performing the standard installation and when runni
There are two distinct build processes used, depending on the distribution channel:
- **NPM publication:** For publishing to the NPM registry, the TypeScript source code in `@gemini-cli/core` and `@gemini-cli/cli` is transpiled into standard JavaScript using the TypeScript Compiler (`tsc`). The resulting `dist/` directory is what gets published in the NPM package. This is a standard approach for TypeScript libraries.
- **NPM publication:** For publishing to the NPM registry, the TypeScript source code in `@google/gemini-cli-core` and `@google/gemini-cli` is transpiled into standard JavaScript using the TypeScript Compiler (`tsc`). The resulting `dist/` directory is what gets published in the NPM package. This is a standard approach for TypeScript libraries.
- **GitHub `npx` execution:** When running the latest version of Gemini CLI directly from GitHub, a different process is triggered by the `prepare` script in `package.json`. This script uses `esbuild` to bundle the entire application and its dependencies into a single, self-contained JavaScript file. This bundle is created on-the-fly on the user's machine and is not checked into the repository.
+4 -1
View File
@@ -16,10 +16,11 @@ This documentation is organized into the following sections:
- **[CLI Introduction](./cli/index.md):** Overview of the command-line interface.
- **[Commands](./cli/commands.md):** Description of available CLI commands.
- **[Configuration](./cli/configuration.md):** Information on configuring the CLI.
- **[Checkpointing](./checkpointing.md):** Documentation for the checkpointing feature.
- **[Extensions](./extension.md):** How to extend the CLI with new functionality.
- **[Telemetry](./telemetry.md):** Overview of telemetry in the CLI.
- **Core Details:** Documentation for `packages/core`.
- **[Core Introduction](./core/index.md):** Overview of the core component.
- **[Configuration](./core/configuration.md):** Information on configuring the core component.
- **[Tools API](./core/tools-api.md):** Information on how the core manages and exposes tools.
- **Tools:**
- **[Tools Overview](./tools/index.md):** Overview of the available tools.
@@ -28,7 +29,9 @@ This documentation is organized into the following sections:
- **[Shell Tool](./tools/shell.md):** Documentation for the `run_shell_command` tool.
- **[Web Fetch Tool](./tools/web-fetch.md):** Documentation for the `web_fetch` tool.
- **[Web Search Tool](./tools/web-search.md):** Documentation for the `google_web_search` tool.
- **[Memory Tool](./tools/memory.md):** Documentation for the `save_memory` tool.
- **[Contributing & Development Guide](../CONTRIBUTING.md):** Information for contributors and developers, including setup, building, testing, and coding conventions.
- **[Troubleshooting Guide](./troubleshooting.md):** Find solutions to common problems and FAQs.
- **[Terms of Service and Privacy Notice](./tos-privacy.md):** Information on the terms of service and privacy notices applicable to your use of Gemini CLI.
We hope this documentation helps you make the most of the Gemini CLI!
+1 -1
View File
@@ -18,7 +18,7 @@ The integration tests can also be run using the following shortcut:
npm run test:e2e
```
## Running a specfic set of tests
## Running a specific set of tests
To run a subset of test files, you can use `npm run <integration test command> <file_name1> ....` where <integration test command> is either `test:e2e` or `test:integration*` and `<file_name>` is any of the `.test.js` files in the `integration-tests/` directory. For example, the following command runs `list_directory.test.js` and `write_file.test.js`:
+133
View File
@@ -0,0 +1,133 @@
# Sandboxing in the Gemini CLI
This document provides a guide to sandboxing in the Gemini CLI, including prerequisites, quickstart, and configuration.
## Prerequisites
Before using sandboxing, you need to install and set up the Gemini CLI:
```bash
# install gemini-cli with npm
npm install -g @google/gemini-cli
# Verify installation
gemini --version
```
## Overview of sandboxing
Sandboxing isolates potentially dangerous operations (such as shell commands or file modifications) from your host system, providing a security barrier between AI operations and your environment.
The benefits of sandboxing include:
- **Security**: Prevent accidental system damage or data loss.
- **Isolation**: Limit file system access to project directory.
- **Consistency**: Ensure reproducible environments across different systems.
- **Safety**: Reduce risk when working with untrusted code or experimental commands.
## Sandboxing methods
Your ideal method of sandboxing may differ depending on your platform and your preferred container solution.
### 1. macOS Seatbelt (macOS only)
Lightweight, built-in sandboxing using `sandbox-exec`.
**Default profile**: `permissive-open` - restricts writes outside project directory but allows most other operations.
### 2. Container-based (Docker/Podman)
Cross-platform sandboxing with complete process isolation.
**Note**: Requires building the sandbox image locally or using a published image from your organization's registry.
## Quickstart
```bash
# Enable sandboxing with command flag
gemini -s -p "analyze the code structure"
# Use environment variable
export GEMINI_SANDBOX=true
gemini -p "run the test suite"
# Configure in settings.json
{
"sandbox": "docker"
}
```
## Configuration
### Enable sandboxing (in order of precedence)
1. **Command flag**: `-s` or `--sandbox`
2. **Environment variable**: `GEMINI_SANDBOX=true|docker|podman|sandbox-exec`
3. **Settings file**: `"sandbox": true` in `settings.json`
### macOS Seatbelt profiles
Built-in profiles (set via `SEATBELT_PROFILE` env var):
- `permissive-open` (default): Write restrictions, network allowed
- `permissive-closed`: Write restrictions, no network
- `permissive-proxied`: Write restrictions, network via proxy
- `restrictive-open`: Strict restrictions, network allowed
- `restrictive-closed`: Maximum restrictions
## Linux UID/GID handling
The sandbox automatically handles user permissions on Linux. Override these permissions with:
```bash
export SANDBOX_SET_UID_GID=true # Force host UID/GID
export SANDBOX_SET_UID_GID=false # Disable UID/GID mapping
```
## Troubleshooting
### Common issues
**"Operation not permitted"**
- Operation requires access outside sandbox.
- Try more permissive profile or add mount points.
**Missing commands**
- Add to custom Dockerfile.
- Install via `sandbox.bashrc`.
**Network issues**
- Check sandbox profile allows network.
- Verify proxy configuration.
### Debug mode
```bash
DEBUG=1 gemini -s -p "debug command"
```
### Inspect sandbox
```bash
# Check environment
gemini -s -p "run shell command: env | grep SANDBOX"
# List mounts
gemini -s -p "run shell command: mount | grep workspace"
```
## Security notes
- Sandboxing reduces but doesn't eliminate all risks.
- Use the most restrictive profile that allows your work.
- Container overhead is minimal after first build.
- GUI applications may not work in sandboxes.
## Related documentation
- [Configuration](./cli/configuration.md): Full configuration options.
- [Commands](./cli/commands.md): Available commands.
- [Troubleshooting](./troubleshooting.md): General troubleshooting.
+248
View File
@@ -0,0 +1,248 @@
# Gemini CLI Observability Guide
Telemetry provides data about Gemini CLI's performance, health, and usage. By enabling it, you can monitor operations, debug issues, and optimize tool usage through traces, metrics, and structured logs.
Gemini CLI's telemetry system is built on the **[OpenTelemetry] (OTEL)** standard, allowing you to send data to any compatible backend.
[OpenTelemetry]: https://opentelemetry.io/
## Enabling telemetry
You can enable telemetry in multiple ways. Configuration is primarily managed via the [`.gemini/settings.json` file](./cli/configuration.md) and environment variables, but CLI flags can override these settings for a specific session.
### Order of precedence
The following lists the precedence for applying telemetry settings, with items listed higher having greater precedence:
1. **CLI flags (for `gemini` command):**
- `--telemetry` / `--no-telemetry`: Overrides `telemetry.enabled`.
- `--telemetry-target <local|gcp>`: Overrides `telemetry.target`.
- `--telemetry-otlp-endpoint <URL>`: Overrides `telemetry.otlpEndpoint`.
- `--telemetry-log-prompts` / `--no-telemetry-log-prompts`: Overrides `telemetry.logPrompts`.
1. **Environment variables:**
- `OTEL_EXPORTER_OTLP_ENDPOINT`: Overrides `telemetry.otlpEndpoint`.
1. **Workspace settings file (`.gemini/settings.json`):** Values from the `telemetry` object in this project-specific file.
1. **User settings file (`~/.gemini/settings.json`):** Values from the `telemetry` object in this global user file.
1. **Defaults:** applied if not set by any of the above.
- `telemetry.enabled`: `false`
- `telemetry.target`: `local`
- `telemetry.otlpEndpoint`: `http://localhost:4317`
- `telemetry.logPrompts`: `true`
**For the `npm run telemetry -- --target=<gcp|local>` script:**
The `--target` argument to this script _only_ overrides the `telemetry.target` for the duration and purpose of that script (i.e., choosing which collector to start). It does not permanently change your `settings.json`. The script will first look at `settings.json` for a `telemetry.target` to use as its default.
### Example settings
The following code can be added to your workspace (`.gemini/settings.json`) or user (`~/.gemini/settings.json`) settings to enable telemetry and send the output to Google Cloud:
```json
{
"telemetry": {
"enabled": true,
"target": "gcp"
},
"sandbox": false
}
```
## Running an OTEL Collector
An OTEL Collector is a service that receives, processes, and exports telemetry data.
The CLI sends data using the OTLP/gRPC protocol.
Learn more about OTEL exporter standard configuration in [documentation][otel-config-docs].
[otel-config-docs]: https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/
### Local
Use the `npm run telemetry -- --target=local` command to automate the process of setting up a local telemetry pipeline, including configuring the necessary settings in your `.gemini/settings.json` file. The underlying script installs `otelcol-contrib` (the OpenTelemetry Collector) and `jaeger` (The Jaeger UI for viewing traces). To use it:
1. **Run the command**:
Execute the command from the root of the repository:
```bash
npm run telemetry -- --target=local
```
The script will:
- Download Jaeger and OTEL if needed.
- Start a local Jaeger instance.
- Start an OTEL collector configured to receive data from Gemini CLI.
- Automatically enable telemetry in your workspace settings.
- On exit, disable telemetry.
1. **View traces**:
Open your web browser and navigate to **http://localhost:16686** to access the Jaeger UI. Here you can inspect detailed traces of Gemini CLI operations.
1. **Inspect logs and metrics**:
The script redirects the OTEL collector output (which includes logs and metrics) to `~/.gemini/tmp/<projectHash>/otel/collector.log`. The script will provide links to view and command to tail your telemetry data (traces, metrics, logs) locally.
1. **Stop the services**:
Press `Ctrl+C` in the terminal where the script is running to stop the OTEL Collector and Jaeger services.
### Google Cloud
Use the `npm run telemetry -- --target=gcp` command to automate setting up a local OpenTelemetry collector that forwards data to your Google Cloud project, including configuring the necessary settings in your `.gemini/settings.json` file. The underlying script installs `otelcol-contrib`. To use it:
1. **Prerequisites**:
- Have a Google Cloud project ID.
- Export the `GOOGLE_CLOUD_PROJECT` environment variable to make it available to the OTEL collector.
```bash
export OTLP_GOOGLE_CLOUD_PROJECT="your-project-id"
```
- Authenticate with Google Cloud (e.g., run `gcloud auth application-default login` or ensure `GOOGLE_APPLICATION_CREDENTIALS` is set).
- Ensure your Google Cloud account/service account has the necessary IAM roles: "Cloud Trace Agent", "Monitoring Metric Writer", and "Logs Writer".
1. **Run the command**:
Execute the command from the root of the repository:
```bash
npm run telemetry -- --target=gcp
```
The script will:
- Download the `otelcol-contrib` binary if needed.
- Start an OTEL collector configured to receive data from Gemini CLI and export it to your specified Google Cloud project.
- Automatically enable telemetry and disable sandbox mode in your workspace settings (`.gemini/settings.json`).
- Provide direct links to view traces, metrics, and logs in your Google Cloud Console.
- On exit (Ctrl+C), it will attempt to restore your original telemetry and sandbox settings.
1. **Run Gemini CLI:**
In a separate terminal, run your Gemini CLI commands. This generates telemetry data that the collector captures.
1. **View telemetry in Google Cloud**:
Use the links provided by the script to navigate to the Google Cloud Console and view your traces, metrics, and logs.
1. **Inspect local collector logs**:
The script redirects the local OTEL collector output to `~/.gemini/tmp/<projectHash>/otel/collector-gcp.log`. The script provides links to view and command to tail your collector logs locally.
1. **Stop the service**:
Press `Ctrl+C` in the terminal where the script is running to stop the OTEL Collector.
## Logs and metric reference
The following section describes the structure of logs and metrics generated for Gemini CLI.
- A `sessionId` is included as a common attribute on all logs and metrics.
### Logs
Logs are timestamped records of specific events. The following events are logged for Gemini CLI:
- `gemini_cli.config`: This event occurs once at startup with the CLI's configuration.
- **Attributes**:
- `model` (string)
- `embedding_model` (string)
- `sandbox_enabled` (boolean)
- `core_tools_enabled` (string)
- `approval_mode` (string)
- `api_key_enabled` (boolean)
- `vertex_ai_enabled` (boolean)
- `code_assist_enabled` (boolean)
- `log_prompts_enabled` (boolean)
- `file_filtering_respect_git_ignore` (boolean)
- `debug_mode` (boolean)
- `mcp_servers` (string)
- `gemini_cli.user_prompt`: This event occurs when a user submits a prompt.
- **Attributes**:
- `prompt_length`
- `prompt` (this attribute is excluded if `log_prompts_enabled` is configured to be `false`)
- `gemini_cli.tool_call`: This event occurs for each function call.
- **Attributes**:
- `function_name`
- `function_args`
- `duration_ms`
- `success` (boolean)
- `decision` (string: "accept", "reject", or "modify", if applicable)
- `error` (if applicable)
- `error_type` (if applicable)
- `gemini_cli.api_request`: This event occurs when making a request to Gemini API.
- **Attributes**:
- `model`
- `request_text` (if applicable)
- `gemini_cli.api_error`: This event occurs if the API request fails.
- **Attributes**:
- `model`
- `error`
- `error_type`
- `status_code`
- `duration_ms`
- `gemini_cli.api_response`: This event occurs upon receiving a response from Gemini API.
- **Attributes**:
- `model`
- `status_code`
- `duration_ms`
- `error` (optional)
- `input_token_count`
- `output_token_count`
- `cached_content_token_count`
- `thoughts_token_count`
- `tool_token_count`
- `response_text` (if applicable)
### Metrics
Metrics are numerical measurements of behavior over time. The following metrics are collected for Gemini CLI:
- `gemini_cli.session.count` (Counter, Int): Incremented once per CLI startup.
- `gemini_cli.tool.call.count` (Counter, Int): Counts tool calls.
- **Attributes**:
- `function_name`
- `success` (boolean)
- `decision` (string: "accept", "reject", or "modify", if applicable)
- `gemini_cli.tool.call.latency` (Histogram, ms): Measures tool call latency.
- **Attributes**:
- `function_name`
- `decision` (string: "accept", "reject", or "modify", if applicable)
- `gemini_cli.api.request.count` (Counter, Int): Counts all API requests.
- **Attributes**:
- `model`
- `status_code`
- `error_type` (if applicable)
- `gemini_cli.api.request.latency` (Histogram, ms): Measures API request latency.
- **Attributes**:
- `model`
- `gemini_cli.token.usage` (Counter, Int): Counts the number of tokens used.
- **Attributes**:
- `model`
- `type` (string: "input", "output", "thought", "cache", or "tool")
- `gemini_cli.file.operation.count` (Counter, Int): Counts file operations.
- **Attributes**:
- `operation` (string: "create", "read", "update"): The type of file operation.
- `lines` (Int, if applicable): Number of lines in the file.
- `mimetype` (string, if applicable): Mimetype of the file.
- `extension` (string, if applicable): File extension of the file.
+132 -34
View File
@@ -1,19 +1,33 @@
# Gemini CLI: File System Tools
# Gemini CLI file system tools
The Gemini CLI provides a comprehensive suite of tools for interacting with the local file system. These tools allow the Gemini model to read from, write to, list, search, and modify files and directories, all under your control and typically with confirmation for sensitive operations.
All file system tools operate within a `rootDirectory` (usually the current working directory where you launched the CLI) for security, preventing unintended access to other parts of your system. Paths provided to these tools are generally expected to be absolute or are resolved relative to this root directory.
**Note:** All file system tools operate within a `rootDirectory` (usually the current working directory where you launched the CLI) for security. Paths that you provide to these tools are generally expected to be absolute or are resolved relative to this root directory.
## 1. `list_directory` (ReadFolder)
- **Tool Name:** `list_directory`
- **Display Name:** ReadFolder
`list_directory` lists the names of files and subdirectories directly within a specified directory path. It can optionally ignore entries matching provided glob patterns.
- **Tool name:** `list_directory`
- **Display name:** ReadFolder
- **File:** `ls.ts`
- **Description:** Lists the names of files and subdirectories directly within a specified directory path. It can optionally ignore entries matching provided glob patterns.
- **Parameters:**
- `path` (string, required): The absolute path to the directory to list.
- `ignore` (array of strings, optional): A list of glob patterns to exclude from the listing (e.g., `["*.log", ".git"]`).
- `respect_git_ignore` (boolean, optional): Whether to respect .gitignore patterns when listing files. Defaults to true.
- `respect_git_ignore` (boolean, optional): Whether to respect `.gitignore` patterns when listing files. Defaults to `true`.
- **Behavior:**
- Returns a list of file and directory names.
- Indicates whether each entry is a directory.
- Sorts entries with directories first, then alphabetically.
- **Output (`llmContent`):** A string like: `Directory listing for /path/to/your/folder:\n[DIR] subfolder1\nfile1.txt\nfile2.png`
- **Confirmation:** No.
- **Tool name:** `list_directory`
- **Display name:** ReadFolder
- **File:** `ls.ts`
- **Parameters:**
- `path` (string, required): The absolute path to the directory to list.
- `ignore` (array of strings, optional): A list of glob patterns to exclude from the listing (e.g., `["*.log", ".git"]`).
- `respect_git_ignore` (boolean, optional): Whether to respect `.gitignore` patterns when listing files. Defaults to `true`.
- **Behavior:**
- Returns a list of file and directory names.
- Indicates whether each entry is a directory.
@@ -23,17 +37,34 @@ All file system tools operate within a `rootDirectory` (usually the current work
## 2. `read_file` (ReadFile)
- **Tool Name:** `read_file`
- **Display Name:** ReadFile
`read_file` reads and returns the content of a specified file. This tool handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), and PDF files. For text files, it can read specific line ranges. Other binary file types are generally skipped.
- **Tool name:** `read_file`
- **Display name:** ReadFile
- **File:** `read-file.ts`
- **Description:** Reads and returns the content of a specified file. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), and PDF files. For text files, it can read specific line ranges. Other binary file types are generally skipped.
- **Parameters:**
- `path` (string, required): The absolute path to the file to read.
- `offset` (number, optional): For text files, the 0-based line number to start reading from. Requires `limit` to be set.
- `limit` (number, optional): For text files, the maximum number of lines to read. If omitted, reads a default maximum (e.g., 2000 lines) or the entire file if feasible.
- **Behavior:**
- For text files: Returns the content. If `offset` and `limit` are used, returns only that slice of lines. Indicates if content was truncated due to line limits or line length limits.
- For image and PDF files: Returns the file content as a base64 encoded data structure suitable for model consumption.
- For image and PDF files: Returns the file content as a base64-encoded data structure suitable for model consumption.
- For other binary files: Attempts to identify and skip them, returning a message indicating it's a generic binary file.
- **Output:** (`llmContent`):
- For text files: The file content, potentially prefixed with a truncation message (e.g., `[File content truncated: showing lines 1-100 of 500 total lines...]\nActual file content...`).
- For image/PDF files: An object containing `inlineData` with `mimeType` and base64 `data` (e.g., `{ inlineData: { mimeType: 'image/png', data: 'base64encodedstring' } }`).
- For other binary files: A message like `Cannot display content of binary file: /path/to/data.bin`.
- **Confirmation:** No.
- **Tool name:** `read_file`
- **Display name:** ReadFile
- **File:** `read-file.ts`
- **Parameters:**
- `path` (string, required): The absolute path to the file to read.
- `offset` (number, optional): For text files, the 0-based line number to start reading from. Requires `limit` to be set.
- `limit` (number, optional): For text files, the maximum number of lines to read. If omitted, reads a default maximum (e.g., 2000 lines) or the entire file if feasible.
- **Behavior:**
- For text files: Returns the content. If `offset` and `limit` are used, returns only that slice of lines. Indicates if content was truncated due to line limits or line length limits.
- For image and PDF files: Returns the file content as a base64-encoded data structure suitable for model consumption.
- For other binary files: Attempts to identify and skip them, returning a message indicating it's a generic binary file.
- **Output:** (`llmContent`):
- For text files: The file content, potentially prefixed with a truncation message (e.g., `[File content truncated: showing lines 1-100 of 500 total lines...]\nActual file content...`).
@@ -43,10 +74,22 @@ All file system tools operate within a `rootDirectory` (usually the current work
## 3. `write_file` (WriteFile)
- **Tool Name:** `write_file`
- **Display Name:** WriteFile
`write_file` writes content to a specified file. If the file exists, it will be overwritten. If the file doesn't exist, it (and any necessary parent directories) will be created.
- **Tool name:** `write_file`
- **Display name:** WriteFile
- **File:** `write-file.ts`
- **Parameters:**
- `file_path` (string, required): The absolute path to the file to write to.
- `content` (string, required): The content to write into the file.
- **Behavior:**
- Writes the provided `content` to the `file_path`.
- Creates parent directories if they don't exist.
- **Output (`llmContent`):** A success message, e.g., `Successfully overwrote file: /path/to/your/file.txt` or `Successfully created and wrote to new file: /path/to/new/file.txt`.
- **Confirmation:** Yes. Shows a diff of changes and asks for user approval before writing.
- **Tool name:** `write_file`
- **Display name:** WriteFile
- **File:** `write-file.ts`
- **Description:** Writes content to a specified file. If the file exists, it will be overwritten. If it doesn't exist, it (and any necessary parent directories) will be created.
- **Parameters:**
- `file_path` (string, required): The absolute path to the file to write to.
- `content` (string, required): The content to write into the file.
@@ -58,15 +101,30 @@ All file system tools operate within a `rootDirectory` (usually the current work
## 4. `glob` (FindFiles)
- **Tool Name:** `glob`
- **Display Name:** FindFiles
`glob` finds files matching specific glob patterns (e.g., `src/**/*.ts`, `*.md`), returning absolute paths sorted by modification time (newest first).
- **Tool name:** `glob`
- **Display name:** FindFiles
- **File:** `glob.ts`
- **Description:** Efficiently finds files matching specific glob patterns (e.g., `src/**/*.ts`, `*.md`), returning absolute paths sorted by modification time (newest first).
- **Parameters:**
- `pattern` (string, required): The glob pattern to match against (e.g., `"*.py"`, `"src/**/*.js"`).
- `path` (string, optional): The absolute path to the directory to search within. If omitted, searches the tool's root directory.
- `case_sensitive` (boolean, optional): Whether the search should be case-sensitive. Defaults to false.
- `respect_git_ignore` (boolean, optional): Whether to respect .gitignore patterns when finding files. Defaults to true.
- `case_sensitive` (boolean, optional): Whether the search should be case-sensitive. Defaults to `false`.
- `respect_git_ignore` (boolean, optional): Whether to respect .gitignore patterns when finding files. Defaults to `true`.
- **Behavior:**
- Searches for files matching the glob pattern within the specified directory.
- Returns a list of absolute paths, sorted with the most recently modified files first.
- Ignores common nuisance directories like `node_modules` and `.git` by default.
- **Output (`llmContent`):** A message like: `Found 5 file(s) matching "*.ts" within src, sorted by modification time (newest first):\nsrc/file1.ts\nsrc/subdir/file2.ts...`
- **Confirmation:** No.
- **Tool name:** `glob`
- **Display name:** FindFiles
- **File:** `glob.ts`
- **Parameters:**
- `pattern` (string, required): The glob pattern to match against (e.g., `"*.py"`, `"src/**/*.js"`).
- `path` (string, optional): The absolute path to the directory to search within. If omitted, searches the tool's root directory.
- `case_sensitive` (boolean, optional): Whether the search should be case-sensitive. Defaults to `false`.
- `respect_git_ignore` (boolean, optional): Whether to respect .gitignore patterns when finding files. Defaults to `true`.
- **Behavior:**
- Searches for files matching the glob pattern within the specified directory.
- Returns a list of absolute paths, sorted with the most recently modified files first.
@@ -76,10 +134,22 @@ All file system tools operate within a `rootDirectory` (usually the current work
## 5. `search_file_content` (SearchText)
- **Tool Name:** `search_file_content`
- **Display Name:** SearchText
`search_file_content` searches for a regular expression pattern within the content of files in a specified directory. Can filter files by a glob pattern. Returns the lines containing matches, along with their file paths and line numbers.
- **Tool name:** `search_file_content`
- **Display name:** SearchText
- **File:** `grep.ts`
- **Parameters:**
- `pattern` (string, required): The regular expression (regex) to search for (e.g., `"function\s+myFunction"`).
- `path` (string, optional): The absolute path to the directory to search within. Defaults to the current working directory.
- `include` (string, optional): A glob pattern to filter which files are searched (e.g., `"*.js"`, `"src/**/*.{ts,tsx}"`). If omitted, searches most files (respecting common ignores).
- **Behavior:**
- Uses `git grep` if available in a Git repository for speed, otherwise falls back to system `grep` or a JavaScript-based search.
- Returns a list of matching lines, each prefixed with its file path (relative to the search directory) and line number.
- **Output (`llmContent`):** A formatted string of matches, e.g.:
- **Tool name:** `search_file_content`
- **Display name:** SearchText
- **File:** `grep.ts`
- **Description:** Searches for a regular expression pattern within the content of files in a specified directory. Can filter files by a glob pattern. Returns the lines containing matches, along with their file paths and line numbers.
- **Parameters:**
- `pattern` (string, required): The regular expression (regex) to search for (e.g., `"function\s+myFunction"`).
- `path` (string, optional): The absolute path to the directory to search within. Defaults to the current working directory.
@@ -100,34 +170,62 @@ All file system tools operate within a `rootDirectory` (usually the current work
---
```
- **Confirmation:** No.
- **Confirmation:** No.
## 6. `replace` (Edit)
- **Tool Name:** `replace`
- **Display Name:** Edit
`replace` replaces text within a file. By default, replaces a single occurrence, but can replace multiple occurrences when `expected_replacements` is specified. This tool is designed for precise, targeted changes and requires significant context around the `old_string` to ensure it modifies the correct location.
- **Tool name:** `replace`
- **Display name:** Edit
- **File:** `edit.ts`
- **Description:** Replaces text within a file. By default, replaces a single occurrence, but can replace multiple occurrences when `expected_replacements` is specified. This tool is designed for precise, targeted changes and requires significant context around the `old_string` to ensure it modifies the correct location.
- **Parameters:**
- `file_path` (string, required): The absolute path to the file to modify.
- `old_string` (string, required): The exact literal text to replace. **CRITICAL:** This string must uniquely identify the single instance to change. It should include at least 3 lines of context _before_ and _after_ the target text, matching whitespace and indentation precisely. If `old_string` is empty, the tool attempts to create a new file at `file_path` with `new_string` as content.
- `old_string` (string, required): The exact literal text to replace.
**CRITICAL:** This string must uniquely identify the single instance to change. It should include at least 3 lines of context _before_ and _after_ the target text, matching whitespace and indentation precisely. If `old_string` is empty, the tool attempts to create a new file at `file_path` with `new_string` as content.
- `new_string` (string, required): The exact literal text to replace `old_string` with.
- `expected_replacements` (number, optional): The number of occurrences to replace. Defaults to 1.
- `expected_replacements` (number, optional): The number of occurrences to replace. Defaults to `1`.
- **Behavior:**
- If `old_string` is empty and `file_path` does not exist, creates a new file with `new_string` as content.
- If `old_string` is provided, it reads the `file_path` and attempts to find exactly one occurrence of `old_string`.
- If one occurrence is found, it replaces it with `new_string`.
- **Enhanced Reliability (Multi-Stage Edit Correction):** To significantly improve the success rate of edits, especially when the model-provided `old_string` might not be perfectly precise, the tool incorporates a multi-stage edit correction mechanism.
- If the initial `old_string` isn't found or matches multiple locations, the tool can leverage the Gemini model to iteratively refine `old_string` (and potentially `new_string`).
- This self-correction process attempts to identify the unique segment the model intended to modify, making the `replace` operation more robust even with slightly imperfect initial context.
- **Failure Conditions:** Despite the correction mechanism, the tool will fail if:
- `file_path` is not absolute or is outside the root directory.
- `old_string` is not empty, but the `file_path` does not exist.
- `old_string` is empty, but the `file_path` already exists.
- `old_string` is not found in the file after attempts to correct it.
- `old_string` is found multiple times, and the self-correction mechanism cannot resolve it to a single, unambiguous match.
- **Output (`llmContent`):**
- On success: `Successfully modified file: /path/to/file.txt (1 replacements).` or `Created new file: /path/to/new_file.txt with provided content.`
- On failure: An error message explaining the reason (e.g., `Failed to edit, 0 occurrences found...`, `Failed to edit, expected 1 occurrences but found 2...`).
- **Confirmation:** Yes. Shows a diff of the proposed changes and asks for user approval before writing to the file.
- `new_string` (string, required): The exact literal text to replace `old_string` with.
- `expected_replacements` (number, optional): The number of occurrences to replace. Defaults to `1`.
- **Behavior:**
- If `old_string` is empty and `file_path` does not exist, creates a new file with `new_string` as content.
- If `old_string` is provided, it reads the `file_path` and attempts to find exactly one occurrence of `old_string`.
- If one occurrence is found, it replaces it with `new_string`.
- **Enhanced Reliability (Multi-Stage Edit Correction):** To significantly improve the success rate of edits, especially when the model-provided `old_string` might not be perfectly precise, the tool incorporates a multi-stage edit correction mechanism.
- If the initial `old_string` isn't found or matches multiple locations, the tool can leverage the Gemini model to iteratively refine `old_string` (and potentially `new_string`).
- This self-correction process attempts to identify the unique segment the model intended to modify, making the `replace` operation more robust even with slightly imperfect initial context from the AI.
- **Failure Conditions:** Despite the correction mechanism, the tool will fail if:
- `file_path` is not absolute or is outside the root directory.
- `old_string` is not empty, but the `file_path` does not exist.
- `old_string` is empty, but the `file_path` already exists.
- `old_string` is not found in the file after attempts to correct it.
- `old_string` is found multiple times, and the self-correction mechanism cannot resolve it to a single, unambiguous match.
- This self-correction process attempts to identify the unique segment the model intended to modify, making the `replace` operation more robust even with slightly imperfect initial context.
- **Failure conditions:** Despite the correction mechanism, the tool will fail if:
- `file_path` is not absolute or is outside the root directory.
- `old_string` is not empty, but the `file_path` does not exist.
- `old_string` is empty, but the `file_path` already exists.
- `old_string` is not found in the file after attempts to correct it.
- `old_string` is found multiple times, and the self-correction mechanism cannot resolve it to a single, unambiguous match.
- **Output (`llmContent`):**
- On success: `Successfully modified file: /path/to/file.txt (1 replacements).` or `Created new file: /path/to/new_file.txt with provided content.`
- On failure: An error message explaining the reason (e.g., `Failed to edit, 0 occurrences found...`, `Failed to edit, expected 1 occurrences but found 2...`).
- **Confirmation:** Yes. Shows a diff of the proposed changes and asks for user approval before writing to the file.
These file system tools provide a robust foundation for the Gemini CLI to understand and interact with your local project context.
These file system tools provide a foundation for the Gemini CLI to understand and interact with your local project context.
+26 -21
View File
@@ -1,22 +1,24 @@
# Gemini CLI Tools: Overview
# Gemini CLI tools
The Gemini CLI is equipped with a powerful set of built-in tools that the Gemini model can utilize to interact with your local environment, access information, and perform actions. These tools significantly enhance the CLI's capabilities, allowing it to go beyond text generation and assist with a wide range of tasks.
The Gemini CLI includes built-in tools that the Gemini model uses to interact with your local environment, access information, and perform actions. These tools enhance the CLI's capabilities, enabling it to go beyond text generation and assist with a wide range of tasks.
## What are Tools?
## Overview of Gemini CLI tools
In the context of the Gemini CLI, tools are specific functions or modules that the Gemini model can request to be executed. For example, if you ask Gemini to "Summarize the contents of `my_document.txt`," the model will likely identify the need to read that file and will request the execution of the `read_file` tool.
The core component (`packages/core`) manages these tools, presents their definitions (schemas) to the Gemini model, executes them when requested, and returns the results to the model for further processing into a user-facing response.
## Why are Tools Important?
These tools provide the following capabilities:
- **Access to Local Information:** Tools allow Gemini to access your local file system, read file contents, list directories, etc.
- **Execution of Commands:** With tools like `run_shell_command`, Gemini can run shell commands (with appropriate safety measures and user confirmation).
- **Interaction with the Web:** Tools can fetch content from URLs.
- **Action Taking:** Tools can modify files, write new files, or perform other actions on your system (again, typically with safeguards).
- **Grounding Responses:** By using tools to fetch real-time or specific local data, Gemini's responses can be more accurate, relevant, and grounded in your actual context.
- **Access local information:** Tools allow Gemini to access your local file system, read file contents, list directories, etc.
- **Execute commands:** With tools like `run_shell_command`, Gemini can run shell commands (with appropriate safety measures and user confirmation).
- **Interact with the web:** Tools can fetch content from URLs.
- **Take actions:** Tools can modify files, write new files, or perform other actions on your system (again, typically with safeguards).
- **Ground responses:** By using tools to fetch real-time or specific local data, Gemini's responses can be more accurate, relevant, and grounded in your actual context.
## How Tools are Used
## How to use Gemini CLI tools
To use Gemini CLI tools, provide a prompt to the Gemini CLI. The process works as follows:
1. You provide a prompt to the Gemini CLI.
2. The CLI sends the prompt to the core.
@@ -28,24 +30,27 @@ The core component (`packages/core`) manages these tools, presents their definit
You will typically see messages in the CLI indicating when a tool is being called and whether it succeeded or failed.
## Security and Confirmation
## Security and confirmation
Many tools, especially those that can modify your file system or execute commands (`write_file`, `edit`, `run_shell_command`), are designed with safety in mind. The Gemini CLI will typically:
- **Require Confirmation:** Prompt you before executing potentially sensitive operations, showing you what action is about to be taken.
- **Utilize Sandboxing:** All tools are subject to restrictions enforced by sandboxing (see [README](../../README.md#sandboxing)). This means that when operating in a sandbox, any tools (including MCP servers) you wish to use must be available _inside_ the sandbox environment. For example, to run an MCP server through `npx`, the `npx` executable must be installed within the sandbox's Docker image or be available in the `sandbox-exec` environment.
- **Require confirmation:** Prompt you before executing potentially sensitive operations, showing you what action is about to be taken.
- **Utilize sandboxing:** All tools are subject to restrictions enforced by sandboxing (see [README](../../README.md#sandboxing)). This means that when operating in a sandbox, any tools (including MCP servers) you wish to use must be available _inside_ the sandbox environment. For example, to run an MCP server through `npx`, the `npx` executable must be installed within the sandbox's Docker image or be available in the `sandbox-exec` environment.
It's important to always review confirmation prompts carefully before allowing a tool to proceed.
## Categories of Built-in Tools
## Learn more about Gemini CLI's tools
The built-in tools can be broadly categorized as follows:
Gemini CLI's built-in tools can be broadly categorized as follows:
- **[File System Tools](./file-system.md):** For interacting with files and directories (reading, writing, listing, searching, etc.).
- **[Shell Tool](./shell.md):** For executing shell commands.
- **[Web Fetch Tool](./web-fetch.md):** For retrieving content from URLs.
- **[Web Search Tool](./web-search.md):** For searching the web.
- **[Multi-File Read Tool](./multi-file.md):** A specialized tool for reading content from multiple files or directories, often used by the `@` command.
- **[Memory Tool](./memory.md):** For saving and recalling information across sessions.
- **[Shell Tool](./shell.md) (`run_shell_command`):** For executing shell commands.
- **[Web Fetch Tool](./web-fetch.md) (`web_fetch`):** For retrieving content from URLs.
- **[Web Search Tool](./web-search.md) (`web_search`):** For searching the web.
- **[Multi-File Read Tool](./multi-file.md) (`read_many_files`):** A specialized tool for reading content from multiple files or directories, often used by the `@` command.
- **[Memory Tool](./memory.md) (`save_memory`):** For saving and recalling information across sessions.
Understanding the available tools and how they work will help you make the most effective use of the Gemini CLI.
Additionally, these tools incorporate:
- **[MCP servers](./mcp-server.md)**: MCP servers act as a bridge between the Gemini model and your local environment or other services like APIs.
- **[Sandboxing](../sandbox.md)**: Sandboxing isolates the model and its changes from your environment to reduce potential risk.
+428
View File
@@ -0,0 +1,428 @@
# MCP servers with the Gemini CLI
This document provides a guide to configuring and using Model Context Protocol (MCP) servers with the Gemini CLI.
## What is an MCP server?
An MCP server is an application that exposes tools and resources to the Gemini CLI through the Model Context Protocol, allowing it to interact with external systems and data sources. MCP servers act as a bridge between the Gemini model and your local environment or other services like APIs.
An MCP server enables the Gemini CLI to:
- **Discover tools:** List available tools, their descriptions, and parameters through standardized schema definitions.
- **Execute tools:** Call specific tools with defined arguments and receive structured responses.
- **Access resources:** Read data from specific resources (though the Gemini CLI primarily focuses on tool execution).
With an MCP server, you can extend the Gemini CLI's capabilities to perform actions beyond its built-in features, such as interacting with databases, APIs, custom scripts, or specialized workflows.
## Core Integration Architecture
The Gemini CLI integrates with MCP servers through a sophisticated discovery and execution system built into the core package (`packages/core/src/tools/`):
### Discovery Layer (`mcp-client.ts`)
The discovery process is orchestrated by `discoverMcpTools()`, which:
1. **Iterates through configured servers** from your `settings.json` `mcpServers` configuration
2. **Establishes connections** using appropriate transport mechanisms (Stdio, SSE, or Streamable HTTP)
3. **Fetches tool definitions** from each server using the MCP protocol
4. **Sanitizes and validates** tool schemas for compatibility with the Gemini API
5. **Registers tools** in the global tool registry with conflict resolution
### Execution Layer (`mcp-tool.ts`)
Each discovered MCP tool is wrapped in a `DiscoveredMCPTool` instance that:
- **Handles confirmation logic** based on server trust settings and user preferences
- **Manages tool execution** by calling the MCP server with proper parameters
- **Processes responses** for both the LLM context and user display
- **Maintains connection state** and handles timeouts
### Transport Mechanisms
The Gemini CLI supports three MCP transport types:
- **Stdio Transport:** Spawns a subprocess and communicates via stdin/stdout
- **SSE Transport:** Connects to Server-Sent Events endpoints
- **Streamable HTTP Transport:** Uses HTTP streaming for communication
## How to set up your MCP server
The Gemini CLI uses the `mcpServers` configuration in your `settings.json` file to locate and connect to MCP servers. This configuration supports multiple servers with different transport mechanisms.
### Configure the MCP server in settings.json
You can configure MCP servers at the global level in the `~/.gemini/settings.json` file or in your project's root directory, create or open the `.gemini/settings.json` file. Within the file, add the `mcpServers` configuration block.
### Configuration Structure
Add an `mcpServers` object to your `settings.json` file:
```json
{ ...file contains other config objects
"mcpServers": {
"serverName": {
"command": "path/to/server",
"args": ["--arg1", "value1"],
"env": {
"API_KEY": "$MY_API_TOKEN"
},
"cwd": "./server-directory",
"timeout": 30000,
"trust": false
}
}
}
```
### Configuration Properties
Each server configuration supports the following properties:
#### Required (one of the following)
- **`command`** (string): Path to the executable for Stdio transport
- **`url`** (string): SSE endpoint URL (e.g., `"http://localhost:8080/sse"`)
- **`httpUrl`** (string): HTTP streaming endpoint URL
#### Optional
- **`args`** (string[]): Command-line arguments for Stdio transport
- **`env`** (object): Environment variables for the server process. Values can reference environment variables using `$VAR_NAME` or `${VAR_NAME}` syntax
- **`cwd`** (string): Working directory for Stdio transport
- **`timeout`** (number): Request timeout in milliseconds (default: 600,000ms = 10 minutes)
- **`trust`** (boolean): When `true`, bypasses all tool call confirmations for this server (default: `false`)
### Example Configurations
#### Python MCP Server (Stdio)
```json
{
"mcpServers": {
"pythonTools": {
"command": "python",
"args": ["-m", "my_mcp_server", "--port", "8080"],
"cwd": "./mcp-servers/python",
"env": {
"DATABASE_URL": "$DB_CONNECTION_STRING",
"API_KEY": "${EXTERNAL_API_KEY}"
},
"timeout": 15000
}
}
}
```
#### Node.js MCP Server (Stdio)
```json
{
"mcpServers": {
"nodeServer": {
"command": "node",
"args": ["dist/server.js", "--verbose"],
"cwd": "./mcp-servers/node",
"trust": true
}
}
}
```
#### Docker-based MCP Server
```json
{
"mcpServers": {
"dockerizedServer": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"API_KEY",
"-v",
"${PWD}:/workspace",
"my-mcp-server:latest"
],
"env": {
"API_KEY": "$EXTERNAL_SERVICE_TOKEN"
}
}
}
}
```
#### HTTP-based MCP Server
```json
{
"mcpServers": {
"httpServer": {
"httpUrl": "http://localhost:3000/mcp",
"timeout": 5000
}
}
}
```
## Discovery Process Deep Dive
When the Gemini CLI starts, it performs MCP server discovery through the following detailed process:
### 1. Server Iteration and Connection
For each configured server in `mcpServers`:
1. **Status tracking begins:** Server status is set to `CONNECTING`
2. **Transport selection:** Based on configuration properties:
- `httpUrl``StreamableHTTPClientTransport`
- `url``SSEClientTransport`
- `command``StdioClientTransport`
3. **Connection establishment:** The MCP client attempts to connect with the configured timeout
4. **Error handling:** Connection failures are logged and the server status is set to `DISCONNECTED`
### 2. Tool Discovery
Upon successful connection:
1. **Tool listing:** The client calls the MCP server's tool listing endpoint
2. **Schema validation:** Each tool's function declaration is validated
3. **Name sanitization:** Tool names are cleaned to meet Gemini API requirements:
- Invalid characters (non-alphanumeric, underscore, dot, hyphen) are replaced with underscores
- Names longer than 63 characters are truncated with middle replacement (`___`)
### 3. Conflict Resolution
When multiple servers expose tools with the same name:
1. **First registration wins:** The first server to register a tool name gets the unprefixed name
2. **Automatic prefixing:** Subsequent servers get prefixed names: `serverName__toolName`
3. **Registry tracking:** The tool registry maintains mappings between server names and their tools
### 4. Schema Processing
Tool parameter schemas undergo sanitization for Gemini API compatibility:
- **`$schema` properties** are removed
- **`additionalProperties`** are stripped
- **`anyOf` with `default`** have their default values removed (Vertex AI compatibility)
- **Recursive processing** applies to nested schemas
### 5. Connection Management
After discovery:
- **Persistent connections:** Servers that successfully register tools maintain their connections
- **Cleanup:** Servers that provide no usable tools have their connections closed
- **Status updates:** Final server statuses are set to `CONNECTED` or `DISCONNECTED`
## Tool Execution Flow
When the Gemini model decides to use an MCP tool, the following execution flow occurs:
### 1. Tool Invocation
The model generates a `FunctionCall` with:
- **Tool name:** The registered name (potentially prefixed)
- **Arguments:** JSON object matching the tool's parameter schema
### 2. Confirmation Process
Each `DiscoveredMCPTool` implements sophisticated confirmation logic:
#### Trust-based Bypass
```typescript
if (this.trust) {
return false; // No confirmation needed
}
```
#### Dynamic Allow-listing
The system maintains internal allow-lists for:
- **Server-level:** `serverName` → All tools from this server are trusted
- **Tool-level:** `serverName.toolName` → This specific tool is trusted
#### User Choice Handling
When confirmation is required, users can choose:
- **Proceed once:** Execute this time only
- **Always allow this tool:** Add to tool-level allow-list
- **Always allow this server:** Add to server-level allow-list
- **Cancel:** Abort execution
### 3. Execution
Upon confirmation (or trust bypass):
1. **Parameter preparation:** Arguments are validated against the tool's schema
2. **MCP call:** The underlying `CallableTool` invokes the server with:
```typescript
const functionCalls = [
{
name: this.serverToolName, // Original server tool name
args: params,
},
];
```
3. **Response processing:** Results are formatted for both LLM context and user display
### 4. Response Handling
The execution result contains:
- **`llmContent`:** Raw response parts for the language model's context
- **`returnDisplay`:** Formatted output for user display (often JSON in markdown code blocks)
## How to interact with your MCP server
### Using the `/mcp` Command
The `/mcp` command provides comprehensive information about your MCP server setup:
```bash
/mcp
```
This displays:
- **Server list:** All configured MCP servers
- **Connection status:** `CONNECTED`, `CONNECTING`, or `DISCONNECTED`
- **Server details:** Configuration summary (excluding sensitive data)
- **Available tools:** List of tools from each server with descriptions
- **Discovery state:** Overall discovery process status
### Example `/mcp` Output
```
MCP Servers Status:
📡 pythonTools (CONNECTED)
Command: python -m my_mcp_server --port 8080
Working Directory: ./mcp-servers/python
Timeout: 15000ms
Tools: calculate_sum, file_analyzer, data_processor
🔌 nodeServer (DISCONNECTED)
Command: node dist/server.js --verbose
Error: Connection refused
🐳 dockerizedServer (CONNECTED)
Command: docker run -i --rm -e API_KEY my-mcp-server:latest
Tools: docker__deploy, docker__status
Discovery State: COMPLETED
```
### Tool Usage
Once discovered, MCP tools are available to the Gemini model like built-in tools. The model will automatically:
1. **Select appropriate tools** based on your requests
2. **Present confirmation dialogs** (unless the server is trusted)
3. **Execute tools** with proper parameters
4. **Display results** in a user-friendly format
## Status Monitoring and Troubleshooting
### Connection States
The MCP integration tracks several states:
#### Server Status (`MCPServerStatus`)
- **`DISCONNECTED`:** Server is not connected or has errors
- **`CONNECTING`:** Connection attempt in progress
- **`CONNECTED`:** Server is connected and ready
#### Discovery State (`MCPDiscoveryState`)
- **`NOT_STARTED`:** Discovery hasn't begun
- **`IN_PROGRESS`:** Currently discovering servers
- **`COMPLETED`:** Discovery finished (with or without errors)
### Common Issues and Solutions
#### Server Won't Connect
**Symptoms:** Server shows `DISCONNECTED` status
**Troubleshooting:**
1. **Check configuration:** Verify `command`, `args`, and `cwd` are correct
2. **Test manually:** Run the server command directly to ensure it works
3. **Check dependencies:** Ensure all required packages are installed
4. **Review logs:** Look for error messages in the CLI output
5. **Verify permissions:** Ensure the CLI can execute the server command
#### No Tools Discovered
**Symptoms:** Server connects but no tools are available
**Troubleshooting:**
1. **Verify tool registration:** Ensure your server actually registers tools
2. **Check MCP protocol:** Confirm your server implements the MCP tool listing correctly
3. **Review server logs:** Check stderr output for server-side errors
4. **Test tool listing:** Manually test your server's tool discovery endpoint
#### Tools Not Executing
**Symptoms:** Tools are discovered but fail during execution
**Troubleshooting:**
1. **Parameter validation:** Ensure your tool accepts the expected parameters
2. **Schema compatibility:** Verify your input schemas are valid JSON Schema
3. **Error handling:** Check if your tool is throwing unhandled exceptions
4. **Timeout issues:** Consider increasing the `timeout` setting
#### Sandbox Compatibility
**Symptoms:** MCP servers fail when sandboxing is enabled
**Solutions:**
1. **Docker-based servers:** Use Docker containers that include all dependencies
2. **Path accessibility:** Ensure server executables are available in the sandbox
3. **Network access:** Configure sandbox to allow necessary network connections
4. **Environment variables:** Verify required environment variables are passed through
### Debugging Tips
1. **Enable debug mode:** Run the CLI with `--debug_mode` for verbose output
2. **Check stderr:** MCP server stderr is captured and logged (INFO messages filtered)
3. **Test isolation:** Test your MCP server independently before integrating
4. **Incremental setup:** Start with simple tools before adding complex functionality
5. **Use `/mcp` frequently:** Monitor server status during development
## Important Notes
### Security Considerations
- **Trust settings:** The `trust` option bypasses all confirmation dialogs. Use cautiously and only for servers you completely control
- **Access tokens:** Be security-aware when configuring environment variables containing API keys or tokens
- **Sandbox compatibility:** When using sandboxing, ensure MCP servers are available within the sandbox environment
- **Private data:** Using broadly scoped personal access tokens can lead to information leakage between repositories
### Performance and Resource Management
- **Connection persistence:** The CLI maintains persistent connections to servers that successfully register tools
- **Automatic cleanup:** Connections to servers providing no tools are automatically closed
- **Timeout management:** Configure appropriate timeouts based on your server's response characteristics
- **Resource monitoring:** MCP servers run as separate processes and consume system resources
### Schema Compatibility
- **Property stripping:** The system automatically removes certain schema properties (`$schema`, `additionalProperties`) for Gemini API compatibility
- **Name sanitization:** Tool names are automatically sanitized to meet API requirements
- **Conflict resolution:** Tool name conflicts between servers are resolved through automatic prefixing
This comprehensive integration makes MCP servers a powerful way to extend the Gemini CLI's capabilities while maintaining security, reliability, and ease of use.
+41 -22
View File
@@ -1,25 +1,44 @@
# Memory Tool
# Memory Tool (`save_memory`)
This document provides details on the `save_memory` tool.
This document describes the `save_memory` tool for the Gemini CLI.
## `save_memory`
## Description
- **Purpose:** Saves a specific piece of information or fact to your long-term memory. This allows the CLI to remember key details across sessions, providing more personalized and effective assistance.
- **Arguments:**
- `fact` (string, required): The specific fact or piece of information to remember. This should be a clear, self-contained statement.
- **Behavior:**
- The tool appends the provided `fact` to a special `GEMINI.md` file located in the user's home directory (`~/.gemini/GEMINI.md`). This file can be configured to have a different name.
- The facts are stored under a `## Gemini Added Memories` section.
- This file is loaded as context in subsequent sessions, allowing the CLI to recall the saved information.
- **Examples:**
- Remembering a user preference:
```
save_memory(fact="My preferred programming language is Python.")
```
- Storing a project-specific detail:
```
save_memory(fact="The project I'm currently working on is called 'gemini-cli'.")
```
- **Important Notes:**
- This tool should be used for concise, important facts. It is not intended for storing large amounts of data or conversational history.
- The memory file is a plain text Markdown file, so you can view and edit it manually if needed.
Use `save_memory` to save and recall information across your Gemini CLI sessions. With `save_memory`, you can direct the CLI to remember key details across sessions, providing personalized and directed assistance.
### Arguments
`save_memory` takes one argument:
- `fact` (string, required): The specific fact or piece of information to remember. This should be a clear, self-contained statement written in natural language.
## How to use `save_memory` with the Gemini CLI
The tool appends the provided `fact` to a special `GEMINI.md` file located in the user's home directory (`~/.gemini/GEMINI.md`). This file can be configured to have a different name.
Once added, the facts are stored under a `## Gemini Added Memories` section. This file is loaded as context in subsequent sessions, allowing the CLI to recall the saved information.
Usage:
```
save_memory(fact="Your fact here.")
```
### `save_memory` examples
Remember a user preference:
```
save_memory(fact="My preferred programming language is Python.")
```
Store a project-specific detail:
```
save_memory(fact="The project I'm currently working on is called 'gemini-cli'.")
```
## Important notes
- **General usage:** This tool should be used for concise, important facts. It is not intended for storing large amounts of data or conversational history.
- **Memory file:** The memory file is a plain text Markdown file, so you can view and edit it manually if needed.
+63 -36
View File
@@ -1,39 +1,66 @@
# Multi-File Read Tool
# Multi File Read Tool (`read_many_files`)
This document provides details on the `read_many_files` tool.
This document describes the `read_many_files` tool for the Gemini CLI.
## `read_many_files`
## Description
- **Purpose:** Reads content from multiple files specified by paths or glob patterns. For text files, it concatenates their content into a single string. For image (e.g., PNG, JPEG) and PDF files, it reads and returns them as base64 encoded data, provided they are explicitly requested by name or extension. This is useful for getting an overview of a codebase, finding where specific functionality is implemented, reviewing documentation, or gathering context from multiple configuration files.
- **Arguments:**
- `paths` (list[string], required): An array of glob patterns or paths relative to the tool's target directory (e.g., `["src/**/*.ts"]`, `["README.md", "docs/", "assets/logo.png"]`).
- `exclude` (list[string], optional): Glob patterns for files/directories to exclude (e.g., `["**/*.log", "temp/"]`). These are added to default excludes if `useDefaultExcludes` is true.
- `include` (list[string], optional): Additional glob patterns to include. These are merged with `paths` (e.g., `["*.test.ts"]` to specifically add test files if they were broadly excluded, or `["images/*.jpg"]` to include specific image types).
- `recursive` (boolean, optional): Whether to search recursively. This is primarily controlled by `**` in glob patterns. Defaults to `true`.
- `useDefaultExcludes` (boolean, optional): Whether to apply a list of default exclusion patterns (e.g., `node_modules`, `.git`, non image/pdf binary files). Defaults to `true`.
- `respect_git_ignore` (boolean, optional): Whether to respect .gitignore patterns when finding files. Defaults to true.
- **Behavior:**
- The tool searches for files matching the provided `paths` and `include` patterns, while respecting `exclude` patterns and default excludes (if enabled).
- For text files: it reads the content of each matched file (attempting to skip binary files not explicitly requested as image/PDF) and concatenates it into a single string, with a separator `--- {filePath} ---` between the content of each file. Uses UTF-8 encoding by default.
- For image and PDF files: if explicitly requested by name or extension (e.g., `paths: ["logo.png"]` or `include: ["*.pdf"]`), the tool reads the file and returns its content as a base64 encoded string.
- The tool attempts to detect and skip other binary files (those not matching common image/PDF types or not explicitly requested) by checking for null bytes in their initial content.
- **Examples:**
- Reading all TypeScript files in the `src` directory:
```
read_many_files(paths=["src/**/*.ts"])
```
- Reading the main README, all Markdown files in the `docs` directory, and a specific logo image, excluding a specific file:
```
read_many_files(paths=["README.md", "docs/**/*.md", "assets/logo.png"], exclude=["docs/OLD_README.md"])
```
- Reading all JavaScript files but explicitly including test files and all JPEGs in an `images` folder:
```
read_many_files(paths=["**/*.js"], include=["**/*.test.js", "images/**/*.jpg"], useDefaultExcludes=False)
```
- **Important Notes:**
- **Binary File Handling:**
- **Image/PDF Files:** The tool can read common image types (PNG, JPEG, etc.) and PDF files, returning them as base64 encoded data. These files _must_ be explicitly targeted by the `paths` or `include` patterns (e.g., by specifying the exact filename like `image.png` or a pattern like `*.jpeg`).
- **Other Binary Files:** The tool attempts to detect and skip other types of binary files by examining their initial content for null bytes. Its behavior with such files is to exclude them from the output.
- **Performance:** Reading a very large number of files or very large individual files can be resource-intensive.
- **Path Specificity:** Ensure paths and glob patterns are correctly specified relative to the tool's target directory. For image/PDF files, ensure the patterns are specific enough to include them.
- **Default Excludes:** Be aware of the default exclusion patterns (like `node_modules`, `.git`) and use `useDefaultExcludes=False` if you need to override them, but do so cautiously.
Use `read_many_files` to read content from multiple files specified by paths or glob patterns. The behavior of this tool depends on the provided files:
- For text files, this tool concatenates their content into a single string.
- For image (e.g., PNG, JPEG) and PDF files, it reads and returns them as base64-encoded data, provided they are explicitly requested by name or extension.
`read_many_files` can be used to perform tasks such as getting an overview of a codebase, finding where specific functionality is implemented, reviewing documentation, or gathering context from multiple configuration files.
### Arguments
`read_many_files` takes the following arguments:
- `paths` (list[string], required): An array of glob patterns or paths relative to the tool's target directory (e.g., `["src/**/*.ts"]`, `["README.md", "docs/", "assets/logo.png"]`).
- `exclude` (list[string], optional): Glob patterns for files/directories to exclude (e.g., `["**/*.log", "temp/"]`). These are added to default excludes if `useDefaultExcludes` is true.
- `include` (list[string], optional): Additional glob patterns to include. These are merged with `paths` (e.g., `["*.test.ts"]` to specifically add test files if they were broadly excluded, or `["images/*.jpg"]` to include specific image types).
- `recursive` (boolean, optional): Whether to search recursively. This is primarily controlled by `**` in glob patterns. Defaults to `true`.
- `useDefaultExcludes` (boolean, optional): Whether to apply a list of default exclusion patterns (e.g., `node_modules`, `.git`, non image/pdf binary files). Defaults to `true`.
- `respect_git_ignore` (boolean, optional): Whether to respect .gitignore patterns when finding files. Defaults to true.
## How to use `read_many_files` with the Gemini CLI
`read_many_files` searches for files matching the provided `paths` and `include` patterns, while respecting `exclude` patterns and default excludes (if enabled).
- For text files: it reads the content of each matched file (attempting to skip binary files not explicitly requested as image/PDF) and concatenates it into a single string, with a separator `--- {filePath} ---` between the content of each file. Uses UTF-8 encoding by default.
- For image and PDF files: if explicitly requested by name or extension (e.g., `paths: ["logo.png"]` or `include: ["*.pdf"]`), the tool reads the file and returns its content as a base64 encoded string.
- The tool attempts to detect and skip other binary files (those not matching common image/PDF types or not explicitly requested) by checking for null bytes in their initial content.
Usage:
```
read_many_files(paths=["Your files or paths here."], include=["Additional files to include."], exclude=["Files to exclude."], recursive=False, useDefaultExcludes=false, respect_git_ignore=true)
```
## `read_many_files` examples
Read all TypeScript files in the `src` directory:
```
read_many_files(paths=["src/**/*.ts"])
```
Read the main README, all Markdown files in the `docs` directory, and a specific logo image, excluding a specific file:
```
read_many_files(paths=["README.md", "docs/**/*.md", "assets/logo.png"], exclude=["docs/OLD_README.md"])
```
Read all JavaScript files but explicitly including test files and all JPEGs in an `images` folder:
```
read_many_files(paths=["**/*.js"], include=["**/*.test.js", "images/**/*.jpg"], useDefaultExcludes=False)
```
## Important notes
- **Binary file handling:**
- **Image/PDF files:** The tool can read common image types (PNG, JPEG, etc.) and PDF files, returning them as base64 encoded data. These files _must_ be explicitly targeted by the `paths` or `include` patterns (e.g., by specifying the exact filename like `image.png` or a pattern like `*.jpeg`).
- **Other binary files:** The tool attempts to detect and skip other types of binary files by examining their initial content for null bytes. The tool excludes these files from its output.
- **Performance:** Reading a very large number of files or very large individual files can be resource-intensive.
- **Path specificity:** Ensure paths and glob patterns are correctly specified relative to the tool's target directory. For image/PDF files, ensure the patterns are specific enough to include them.
- **Default excludes:** Be aware of the default exclusion patterns (like `node_modules`, `.git`) and use `useDefaultExcludes=False` if you need to override them, but do so cautiously.
+53 -38
View File
@@ -1,46 +1,61 @@
# Shell Tool
# Shell Tool (`run_shell_command`)
This document provides details on the shell tool.
This document describes the `run_shell_command` tool for the Gemini CLI.
## `run_shell_command`
## Description
- **Purpose:** Executes a given shell command. On Windows, this will be executed with `cmd.exe /c`. On other platforms, it will be executed with `bash -c`. This tool is essential for interacting with the underlying operating system, running scripts, or performing command-line operations.
- **Arguments:**
- `command` (string, required): The exact shell command to execute.
- `description` (string, optional): A brief description of the command's purpose, which will be shown to the user.
- `directory` (string, optional): The directory (relative to the project root) in which to execute the command. If not provided, the command runs in the project root.
- **Behavior:**
- The command is executed as a subprocess.
- It can start background processes using `&`.
- The tool returns detailed information about the execution, including:
- `Command`: The command that was executed.
- `Directory`: The directory where the command was run.
- `Stdout`: Output from the standard output stream.
- `Stderr`: Output from the standard error stream.
- `Error`: Any error message reported by the subprocess.
- `Exit Code`: The exit code of the command.
- `Signal`: The signal number if the command was terminated by a signal.
- `Background PIDs`: A list of PIDs for any background processes started.
- **Examples:**
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. On Windows, the command will be executed with `cmd.exe /c`. On other platforms, the command will be executed with `bash -c`.
- Listing files in the current directory:
### Arguments
```
`run_shell_command` takes the following arguments:
run_shell_command(command="ls -la")
```
- `command` (string, required): The exact shell command to execute.
- `description` (string, optional): A brief description of the command's purpose, which will be shown to the user.
- `directory` (string, optional): The directory (relative to the project root) in which to execute the command. If not provided, the command runs in the project root.
- Running a script in a specific directory:
```
run_shell_command(command="./my_script.sh", directory="scripts", description="Run my custom script")
```
- Starting a background server:
```
run_shell_command(command="npm run dev &", description="Start development server in background")
```
## How to use `run_shell_command` with the Gemini CLI
- **Important Notes:**
- **Security:** Be cautious when executing commands, especially those constructed from user input, to prevent security vulnerabilities.
- **Interactive Commands:** Avoid commands that require interactive user input, as this can cause the tool to hang. Use non-interactive flags if available (e.g., `npm init -y`).
- **Error Handling:** Check the `Stderr`, `Error`, and `Exit Code` fields to determine if a command executed successfully.
- **Background Processes:** When a command is run in the background with `&`, the tool will return immediately and the process will continue to run in the background. The `Background PIDs` field will contain the process ID of the background process.
When using `run_shell_command`, the command is executed as a subprocess. `run_shell_command` can start background processes using `&`. The tool returns detailed information about the execution, including:
- `Command`: The command that was executed.
- `Directory`: The directory where the command was run.
- `Stdout`: Output from the standard output stream.
- `Stderr`: Output from the standard error stream.
- `Error`: Any error message reported by the subprocess.
- `Exit Code`: The exit code of the command.
- `Signal`: The signal number if the command was terminated by a signal.
- `Background PIDs`: A list of PIDs for any background processes started.
Usage:
```
run_shell_command(command="Your commands.", description="Your description of the command.", directory="Your execution directory.")
```
## `run_shell_command` examples
List files in the current directory:
```
run_shell_command(command="ls -la")
```
Run a script in a specific directory:
```
run_shell_command(command="./my_script.sh", directory="scripts", description="Run my custom script")
```
Start a background server:
```
run_shell_command(command="npm run dev &", description="Start development server in background")
```
## Important notes
- **Security:** Be cautious when executing commands, especially those constructed from user input, to prevent security vulnerabilities.
- **Interactive commands:** Avoid commands that require interactive user input, as this can cause the tool to hang. Use non-interactive flags if available (e.g., `npm init -y`).
- **Error handling:** Check the `Stderr`, `Error`, and `Exit Code` fields to determine if a command executed successfully.
- **Background processes:** When a command is run in the background with `&`, the tool will return immediately and the process will continue to run in the background. The `Background PIDs` field will contain the process ID of the background process.
+41 -24
View File
@@ -1,27 +1,44 @@
# Web Fetch Tool
# Web Fetch Tool (`web_fetch`)
This document describes the `web_fetch` tool.
This document describes the `web_fetch` tool for the Gemini CLI.
## `web_fetch`
## Description
- **Purpose:** Processes content from one or more URLs (up to 20) embedded in a prompt. This tool is ideal for tasks that require summarizing, comparing, or extracting specific information from web pages.
- **Arguments:**
- `prompt` (string, required): A comprehensive prompt that includes the URL(s) (up to 20) to fetch and specific instructions on how to process their content. For example: `"Summarize https://example.com/article and extract key points from https://another.com/data"`. The prompt must contain at least one URL starting with `http://` or `https://`.
- **Behavior:**
- The tool will first ask for confirmation before fetching any URLs.
- It attempts to process URLs through the Gemini API's `urlContext` tool first.
- If the Gemini API cannot access a URL (e.g., it's on `localhost`, a private network, or behind a firewall), the tool will fall back to fetching the content directly from the local machine.
- The tool can fetch content from `localhost` and private network addresses via this fallback mechanism.
- The tool formats the response, including source attribution with citations where possible, and returns it to the user.
- **Examples:**
- Summarizing a single article:
```
web_fetch(prompt="Can you summarize the main points of https://example.com/news/latest")
```
- Comparing two articles:
```
web_fetch(prompt="What are the differences in the conclusions of these two papers: https://arxiv.org/abs/2401.0001 and https://arxiv.org/abs/2401.0002?")
```
- **Important Notes:**
- This tool relies on the Gemini API's ability to access and process the given URLs.
- The quality of the output depends on the clarity of the instructions in the prompt.
Use `web_fetch` to summarize, compare, or extract information from web pages. The `web_fetch` tool processes content from one or more URLs (up to 20) embedded in a prompt. `web_fetch` takes a natural language prompt and returns a generated response.
### Arguments
`web_fetch` takes one argument:
- `prompt` (string, required): A comprehensive prompt that includes the URL(s) (up to 20) to fetch and specific instructions on how to process their content. For example: `"Summarize https://example.com/article and extract key points from https://another.com/data"`. The prompt must contain at least one URL starting with `http://` or `https://`.
## How to use `web_fetch` with the Gemini CLI
To use `web_fetch` with the Gemini CLI, provide a natural language prompt that contains URLs. The tool will ask for confirmation before fetching any URLs. Once confirmed, the tool will process URLs through Gemini API's `urlContext`.
If the Gemini API cannot access the URL, the tool will fall back to fetching content directly from the local machine. The tool will format the response, including source attribution and citations where possible. The tool will then provide the response to the user.
Usage:
```
web_fetch(prompt="Your prompt, including a URL such as https://google.com.")
```
## `web_fetch` examples
Summarize a single article:
```
web_fetch(prompt="Can you summarize the main points of https://example.com/news/latest")
```
Compare two articles:
```
web_fetch(prompt="What are the differences in the conclusions of these two papers: https://arxiv.org/abs/2401.0001 and https://arxiv.org/abs/2401.0002?")
```
## Important notes
- **URL processing:** `web_fetch` relies on the Gemini API's ability to access and process the given URLs.
- **Output quality:** The quality of the output will depend on the clarity of the instructions in the prompt.
+32 -16
View File
@@ -1,20 +1,36 @@
# Google Web Search Tool
# Web Search Tool (`google_web_search`)
This document describes the `google_web_search` tool.
## `google_web_search`
## Description
- **Purpose:** Performs a web search using Google Search (via the Gemini API) and returns a summary of the results with sources. This tool is useful for finding up-to-date information on the internet.
- **Arguments:**
- `query` (string, required): The search query.
- **Behavior:**
- The tool sends the query to the Gemini API, which performs a Google Search.
- It returns a generated response based on the search results, complete with citations and a list of sources.
- **Examples:**
- Getting information on a topic:
```
google_web_search(query="latest advancements in AI-powered code generation")
```
- **Important Notes:**
- The tool returns a processed summary, not a raw list of search results.
- The response includes citations to the sources used to generate the summary.
Use `google_web_search` to perform a web search using Google Search via the Gemini API. The `google_web_search` tool returns a summary of web results with sources.
### Arguments
`google_web_search` takes one argument:
- `query` (string, required): The search query.
## How to use `google_web_search` with the Gemini CLI
The `google_web_search` tool sends a query to the Gemini API, which then performs a web search. `google_web_search` will return a generated response based on the search results, including citations and sources.
Usage:
```
google_web_search(query="Your query goes here.")
```
## `google_web_search` examples
Get information on a topic:
```
google_web_search(query="latest advancements in AI-powered code generation")
```
## Important notes
- **Response returned:** The `google_web_search` tool returns a processed summary, not a raw list of search results.
- **Citations:** The response includes citations to the sources used to generate the summary.
+58
View File
@@ -0,0 +1,58 @@
# Gemini CLI: Terms of Service and Privacy Notice
Gemini CLI is an open-source tool that allows you to interact with Google's powerful language models directly from your command-line interface. The terms of service and privacy notices that apply to your usage of Gemini CLI depend on the type of account you use to authenticate with Google.
This article outlines the specific terms and privacy policies applicable for different auth methods.
## 1. Login with Google (Gemini Code Assist for [individuals](https://developers.google.com/gemini-code-assist/docs/overview#supported-features-gca))
For users who authenticate using their Google account to access Gemini Code Assist for individuals:
- Terms of Service: Your use of Gemini CLI is governed by the general [Google Terms of Service](https://policies.google.com/terms?hl=en-US).
- Privacy Notice: The collection and use of your data are described in the [Gemini Code Assist Privacy Notice for Individuals](https://developers.google.com/gemini-code-assist/resources/privacy-notice-gemini-code-assist-individuals).
## 2. Gemini API Key (Using Gemini Developer [API](https://ai.google.dev/gemini-api/docs) a: Unpaid Service, b: Paid Service)
If you are using a Gemini API key for authentication, the following terms apply:
- Terms of Service: Your use is subject to the [Gemini API Terms of Service](https://ai.google.dev/gemini-api/terms). For a. [Unpaid Service](https://ai.google.dev/gemini-api/terms#unpaid-services) or b. [Paid Service](https://ai.google.dev/gemini-api/terms#paid-services)
- Privacy Notice: Information regarding data handling and privacy is detailed in the general [Google Privacy Policy](https://policies.google.com/privacy).
## 3. Login with Google (for Workspace or Licensed Code Assist users)
For users of Standard or Enterprise [edition](https://cloud.google.com/gemini/docs/codeassist/overview#editions-overview) of Gemini Code Assist :
- Terms of Service: The [Google Cloud Platform Terms of Service](https://cloud.google.com/terms) govern your use of the service.
- Privacy Notice: The handling of your data is outlined in the [Gemini Code Assist Privacy Notices](https://developers.google.com/gemini-code-assist/resources/privacy-notices).
## 4. Vertex AI (Using Vertex AI Gen [API](https://cloud.google.com/vertex-ai/generative-ai/docs/reference/rest))
If you are using an API key with a Vertex AI Gen API backend:
- Terms of Service: Your usage is governed by the [Google Cloud Platform Service Terms](https://cloud.google.com/terms/service-terms/).
- Privacy Notice: The [Google Cloud Privacy Notice](https://cloud.google.com/terms/cloud-privacy-notice) describes how your data is collected and managed.
### Usage Statistics Opt-Out
You may opt-out from sending Usage Statistics to Google data by following the instructions available here: [Usage Statistics Configuration](./cli/configuration.md#usage-statistics).
## Frequently Asked Questions (FAQ) for Gemini CLI
### 1. Is my code, including prompts and answers, used to train Google's models?
This depends entirely on the type of auth method you use.
- **Auth method 1:** Yes. When you use your personal Google account, the Gemini Code Assist Privacy Notice for Individuals applies. Under this notice, your **prompts, answers, and related code are collected** and may be used to improve Google's products, which includes model training.
- **Auth method 2a:** Yes, When you use the Gemini API key Gemini API (Unpaid Service) terms apply. Under this notice , your **prompts, answers, and related code are collected** and may be used to improve Google's products, which includes model training.
- **Auth method 2b, 3 & 4:** No. For these accounts, your data is governed by the Google Cloud or Gemini API (Paid Service) terms, which treat your inputs as confidential. Your code, prompts, and other inputs are **not** used to train models.
### 2. What are "Usage Statistics" and what does the opt-out control?
The "Usage Statistics" setting is the single control for all optional data collection in the Gemini CLI. The data it collects depends on your account type:
- **Auth method 1:** When enabled, this setting allows Google to collect both anonymous telemetry (like commands run and performance metrics) and **your prompts and answers** for model improvement.
- **Auth method 2a:** When enabled, this setting allows Google to collect both anonymous telemetry (like commands run and performance metrics) and **your prompts and answers** for model improvement. When disabled we will use your data as described in the [How Google Uses Your Data](https://ai.google.dev/gemini-api/terms#data-use-unpaid).
- **Auth method 2b:** This setting only controls the collection of anonymous telemetry. Google logs prompts and responses for a limited period of time, solely for the purpose of detecting violations of the Prohibited Use Policy and any required legal or regulatory disclosures
- **Auth methods 3 & 4:** This setting only controls the collection of anonymous telemetry. Your prompts and answers are never collected, regardless of this setting.
You can disable Usage Statistics for any account type by following the instructions in the [Usage Statistics Configuration](./cli/configuration.md#usage-statistics) documentation.
+19 -2
View File
@@ -2,16 +2,33 @@
This guide provides solutions to common issues and debugging tips.
## Authentication
- **Error: `Failed to login. Message: Request contains an invalid argument`**
- Users with Google Workspace accounts, or users with Google Cloud accounts
associated with their Gmail accounts may not be able to activate the free
tier of the Google Code Assist plan.
- For Google Cloud accounts, you can work around this by setting
`GOOGLE_CLOUD_PROJECT` to your project ID.
- You can also grab an API key from [AI
Studio](http://aistudio.google.com/app/apikey), which also includes a
separate free tier.
## Frequently asked questions (FAQs)
- **Q: How do I update Gemini CLI to the latest version?**
- A: If installed globally via npm, update Gemini CLI using the command `npm install -g @gemini-cli/cli@latest`. If run from source, pull the latest changes from the repository and rebuild using `npm run build`.
- A: If installed globally via npm, update Gemini CLI using the command `npm install -g @google/gemini-cli@latest`. If run from source, pull the latest changes from the repository and rebuild using `npm run build`.
- **Q: Where are Gemini CLI configuration files stored?**
- A: The CLI configuration is stored within two `settings.json` files: one in your home directory and one in your project's root directory. In both locations, `settings.json` is found in the `.gemini/` folder. Refer to [CLI Configuration](./cli/configuration.md) for more details.
- **Q: Why don't I see cached token counts in my stats output?**
- A: Cached token information is only displayed when cached tokens are being used. This feature is available for API key users (Gemini API key or Vertex AI) but not for OAuth users (Google Personal/Enterprise accounts) at this time, as the Code Assist API does not support cached content creation. You can still view your total token usage with the `/stats` command.
## Common error messages and solutions
- **Error: `EADDRINUSE` (Address already in use) when starting an MCP server.**
@@ -20,7 +37,7 @@ This guide provides solutions to common issues and debugging tips.
- **Solution:**
Either stop the other process that is using the port or configure the MCP server to use a different port.
- **Error: Command not found (when attempting the run Gemini CLI).**
- **Error: Command not found (when attempting to run Gemini CLI).**
- **Cause:** Gemini CLI is not correctly installed or not in your system's PATH.
- **Solution:**
+1
View File
@@ -13,6 +13,7 @@ test('should be able to list a directory', async (t) => {
rig.setup(t.name);
rig.createFile('file1.txt', 'file 1 content');
rig.mkdir('subdir');
rig.sync();
const prompt = `Can you list the files in the current directory`;
const result = await rig.run(prompt);
+5
View File
@@ -42,6 +42,11 @@ export class TestRig {
mkdirSync(join(this.testDir, dir));
}
sync() {
// ensure file system is done before spawning
execSync('sync', { cwd: this.testDir });
}
run(prompt, ...args) {
const output = execSync(
`node ${this.bundlePath} --yolo --prompt "${prompt}" ${args.join(' ')}`,
+160 -23
View File
@@ -1,12 +1,12 @@
{
"name": "gemini-cli",
"version": "0.1.0",
"name": "@google/gemini-cli",
"version": "0.1.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gemini-cli",
"version": "0.1.0",
"name": "@google/gemini-cli",
"version": "0.1.6",
"workspaces": [
"packages/*"
],
@@ -18,6 +18,7 @@
"@types/mime-types": "^2.1.4",
"@types/minimatch": "^5.1.2",
"@vitest/coverage-v8": "^3.1.1",
"concurrently": "^9.2.0",
"cross-env": "^7.0.3",
"esbuild": "^0.25.0",
"eslint": "^9.24.0",
@@ -35,6 +36,9 @@
"react-devtools-core": "^4.28.5",
"typescript-eslint": "^8.30.1",
"yargs": "^17.7.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@alcalzone/ansi-tokenize": {
@@ -874,11 +878,11 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@gemini-cli/cli": {
"node_modules/@google/gemini-cli": {
"resolved": "packages/cli",
"link": true
},
"node_modules/@gemini-cli/core": {
"node_modules/@google/gemini-cli-core": {
"resolved": "packages/core",
"link": true
},
@@ -2614,9 +2618,9 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3384,9 +3388,9 @@
}
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3817,6 +3821,48 @@
"dev": true,
"license": "MIT"
},
"node_modules/concurrently": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.0.tgz",
"integrity": "sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"chalk": "^4.1.2",
"lodash": "^4.17.21",
"rxjs": "^7.8.1",
"shell-quote": "^1.8.1",
"supports-color": "^8.1.1",
"tree-kill": "^1.2.2",
"yargs": "^17.7.2"
},
"bin": {
"conc": "dist/bin/concurrently.js",
"concurrently": "dist/bin/concurrently.js"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
}
},
"node_modules/concurrently/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/config-chain": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
@@ -5638,9 +5684,9 @@
}
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
@@ -6204,6 +6250,24 @@
"ink": ">=4"
}
},
"node_modules/ink-link": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ink-link/-/ink-link-4.1.0.tgz",
"integrity": "sha512-3nNyJXum0FJIKAXBK8qat2jEOM41nJ1J60NRivwgK9Xh92R5UMN/k4vbz0A9xFzhJVrlf4BQEmmxMgXkCE1Jeg==",
"dependencies": {
"prop-types": "^15.8.1",
"terminal-link": "^3.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
},
"peerDependencies": {
"ink": ">=4"
}
},
"node_modules/ink-select-input": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/ink-select-input/-/ink-select-input-6.2.0.tgz",
@@ -8852,6 +8916,16 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/safe-array-concat": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
@@ -9614,6 +9688,18 @@
"node": ">=8"
}
},
"node_modules/supports-hyperlinks": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz",
"integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==",
"dependencies": {
"has-flag": "^4.0.0",
"supports-color": "^7.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
@@ -9633,6 +9719,46 @@
"dev": true,
"license": "MIT"
},
"node_modules/terminal-link": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-3.0.0.tgz",
"integrity": "sha512-flFL3m4wuixmf6IfhFJd1YPiLiMuxEc8uHRM1buzIeZPm22Au2pDqBJQgdo7n1WfPU1ONFGv7YDwpFBmHGF6lg==",
"dependencies": {
"ansi-escapes": "^5.0.0",
"supports-hyperlinks": "^2.2.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/terminal-link/node_modules/ansi-escapes": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz",
"integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==",
"dependencies": {
"type-fest": "^1.0.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/terminal-link/node_modules/type-fest": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
"integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/test-exclude": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz",
@@ -9649,9 +9775,9 @@
}
},
"node_modules/test-exclude/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9888,6 +10014,16 @@
"tslib": "2"
}
},
"node_modules/tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
"dev": true,
"license": "MIT",
"bin": {
"tree-kill": "cli.js"
}
},
"node_modules/ts-api-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@@ -11534,10 +11670,10 @@
}
},
"packages/cli": {
"name": "@gemini-cli/cli",
"version": "0.1.0",
"name": "@google/gemini-cli",
"version": "0.1.6",
"dependencies": {
"@gemini-cli/core": "file:../core",
"@google/gemini-cli-core": "*",
"@types/update-notifier": "^6.0.8",
"command-exists": "^1.2.9",
"diff": "^7.0.0",
@@ -11547,6 +11683,7 @@
"ink": "^5.2.0",
"ink-big-text": "^2.0.0",
"ink-gradient": "^3.0.0",
"ink-link": "^4.0.0",
"ink-select-input": "^6.0.0",
"ink-spinner": "^5.0.0",
"ink-text-input": "^6.0.0",
@@ -11607,8 +11744,8 @@
}
},
"packages/core": {
"name": "@gemini-cli/core",
"version": "0.1.0",
"name": "@google/gemini-cli-core",
"version": "0.1.6",
"dependencies": {
"@google/genai": "^1.4.0",
"@modelcontextprotocol/sdk": "^1.11.0",
+20 -8
View File
@@ -1,10 +1,17 @@
{
"name": "gemini-cli",
"version": "0.1.0",
"name": "@google/gemini-cli",
"version": "0.1.6",
"engines": {
"node": ">=18.0.0"
},
"type": "module",
"workspaces": [
"packages/*"
],
"repository": "google-gemini/gemini-cli",
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.1.5"
},
"scripts": {
"generate": "node scripts/generate-git-commit-info.js",
"build": "node scripts/build.js",
@@ -20,12 +27,13 @@
"test:integration:sandbox:docker": "GEMINI_SANDBOX=docker node integration-tests/run-tests.js",
"test:integration:sandbox:podman": "GEMINI_SANDBOX=podman node integration-tests/run-tests.js",
"start": "node scripts/start.js",
"debug": "cross-env DEBUG=1 node scripts/start.js",
"debug": "cross-env DEBUG=1 node --inspect-brk scripts/start.js",
"lint:fix": "eslint . --fix && eslint integration-tests --fix",
"lint": "eslint . --ext .ts,.tsx && eslint integration-tests",
"lint:ci": "eslint . --ext .ts,.tsx --max-warnings 0 && eslint integration-tests --max-warnings 0",
"typecheck": "npm run typecheck --workspaces --if-present",
"format": "prettier --write .",
"preflight": "npm run clean && npm ci && npm run format && npm run lint:fix && npm run build && npm run typecheck && npm run test:ci",
"preflight": "npm run clean && npm ci && npm run format && npm run lint:ci && npm run build && npm run typecheck && npm run test:ci",
"auth:npm": "npx google-artifactregistry-auth",
"auth:docker": "gcloud auth configure-docker us-west1-docker.pkg.dev",
"auth": "npm run auth:npm && npm run auth:docker",
@@ -34,13 +42,15 @@
"build:cli": "npm run build --workspace packages/cli",
"build:core": "npm run build --workspace packages/core",
"build:packages": "npm run build:core && npm run build:cli",
"build:docker": "node scripts/build_sandbox.js -s",
"build:sandbox:fast": "node scripts/build_sandbox.js --skip-npm-install-build",
"prepare:cli-packagejson": "node scripts/prepare-cli-packagejson.js",
"prepare:packages": "node scripts/prepare-cli-packagejson.js && node scripts/prepare-core-package.js",
"publish:sandbox": "node scripts/publish-sandbox.js",
"publish:npm": "npm publish --workspaces ${NPM_PUBLISH_TAG:+--tag=$NPM_PUBLISH_TAG} ${NPM_DRY_RUN:+--dry-run}",
"publish:release": "npm run build:packages && npm run prepare:cli-packagejson && npm run build:docker && npm run publish:sandbox && npm run publish:npm",
"publish:release": "npm run build:packages && npm run prepare:cli-packagejson && npm run build:sandbox:fast && npm run publish:sandbox && npm run publish:npm",
"telemetry": "node scripts/telemetry.js",
"start:gcp": "npm run telemetry -- --target=gcp & npm start"
"start:gcp": "concurrently --raw --kill-others \"npm run telemetry -- --target=gcp\" \"npm start\"",
"prepublishOnly": "node scripts/prepublish.js"
},
"bin": {
"gemini": "bundle/gemini.js"
@@ -55,6 +65,7 @@
"@types/mime-types": "^2.1.4",
"@types/minimatch": "^5.1.2",
"@vitest/coverage-v8": "^3.1.1",
"concurrently": "^9.2.0",
"cross-env": "^7.0.3",
"esbuild": "^0.25.0",
"eslint": "^9.24.0",
@@ -72,5 +83,6 @@
"react-devtools-core": "^4.28.5",
"typescript-eslint": "^8.30.1",
"yargs": "^17.7.2"
}
},
"dependencies": {}
}
-48
View File
@@ -1,48 +0,0 @@
# Gemini CLI
This package contains the core command-line interface for Gemini CLI.
## Setup
1. **Get a Gemini API Key:** Obtain your API key from Google AI Studio: [https://aistudio.google.com/app/apikey](https://aistudio.google.com/app/apikey)
2. **Set Environment Variable:** Set the `GEMINI_API_KEY` environment variable to your obtained key. You can do this temporarily in your current shell session:
```bash
export GEMINI_API_KEY="YOUR_API_KEY"
```
Or add it to your shell's configuration file (like `~/.bashrc`, `~/.zshrc`, or `~/.profile`) for persistence:
```bash
echo 'export GEMINI_API_KEY="YOUR_API_KEY"' >> ~/.bashrc # Or your preferred shell config file
source ~/.bashrc # Reload the config
```
Replace `"YOUR_API_KEY"` with your actual key.
## Building
To build only the CLI package, navigate to this directory (`packages/cli`) and run:
```bash
npm run build
```
This command executes the TypeScript compiler (`tsc`) as defined in this package's `package.json`. Ensure dependencies have been installed from the root directory (`npm install`) first.
## Running
To start the Gemini CLI directly from this directory:
```bash
npm start
```
This command executes `node dist/gemini.js` as defined in this package's `package.json`.
## Debugging
To debug the CLI application using VS Code:
1. Start the CLI in debug mode from this directory (`packages/cli`):
```bash
npm run debug
```
This command runs `node --inspect-brk dist/gemini.js`, pausing execution until a debugger attaches.
2. In VS Code (opened at the root of the monorepo), use the "Attach" launch configuration (found in `.vscode/launch.json`). This configuration is set up to attach to the Node.js process listening on port 9229, which is the default port used by `--inspect-brk`.
+7 -5
View File
@@ -1,7 +1,8 @@
{
"name": "@gemini-cli/cli",
"version": "0.1.0",
"name": "@google/gemini-cli",
"version": "0.1.6",
"description": "Gemini CLI",
"repository": "google-gemini/gemini-cli",
"type": "module",
"main": "dist/index.js",
"bin": {
@@ -19,8 +20,8 @@
"typecheck": "tsc --noEmit",
"prerelease:version": "node ../../scripts/bind_package_version.js",
"prerelease:deps": "node ../../scripts/bind_package_dependencies.js",
"prepublishOnly": "npm publish --workspace=@gemini-cli/core",
"prepack": "npm run build"
"prepack": "npm run build",
"prepublishOnly": "node ../../scripts/prepublish.js"
},
"files": [
"dist"
@@ -29,7 +30,7 @@
"sandboxImageUri": "gemini-cli-sandbox"
},
"dependencies": {
"@gemini-cli/core": "file:../core",
"@google/gemini-cli-core": "*",
"@types/update-notifier": "^6.0.8",
"command-exists": "^1.2.9",
"diff": "^7.0.0",
@@ -41,6 +42,7 @@
"ink-gradient": "^3.0.0",
"ink-select-input": "^6.0.0",
"ink-spinner": "^5.0.0",
"ink-link": "^4.0.0",
"ink-text-input": "^6.0.0",
"lowlight": "^3.3.0",
"mime-types": "^2.1.4",
+11 -16
View File
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { AuthType } from '@gemini-cli/core';
import { AuthType } from '@google/gemini-cli-core';
import { loadEnvironment } from './config.js';
export const validateAuthMethod = (authMethod: string): string | null => {
@@ -13,13 +13,6 @@ export const validateAuthMethod = (authMethod: string): string | null => {
return null;
}
if (authMethod === AuthType.LOGIN_WITH_GOOGLE_ENTERPRISE) {
if (!process.env.GOOGLE_CLOUD_PROJECT) {
return 'GOOGLE_CLOUD_PROJECT environment variable not found. Add that to your .env and try again, no reload needed!';
}
return null;
}
if (authMethod === AuthType.USE_GEMINI) {
if (!process.env.GEMINI_API_KEY) {
return 'GEMINI_API_KEY environment variable not found. Add that to your .env and try again, no reload needed!';
@@ -28,14 +21,16 @@ export const validateAuthMethod = (authMethod: string): string | null => {
}
if (authMethod === AuthType.USE_VERTEX_AI) {
if (!process.env.GOOGLE_API_KEY) {
return 'GOOGLE_API_KEY environment variable not found. Add that to your .env and try again, no reload needed!';
}
if (!process.env.GOOGLE_CLOUD_PROJECT) {
return 'GOOGLE_CLOUD_PROJECT environment variable not found. Add that to your .env and try again, no reload needed!';
}
if (!process.env.GOOGLE_CLOUD_LOCATION) {
return 'GOOGLE_CLOUD_LOCATION environment variable not found. Add that to your .env and try again, no reload needed!';
const hasVertexProjectLocationConfig =
!!process.env.GOOGLE_CLOUD_PROJECT && !!process.env.GOOGLE_CLOUD_LOCATION;
const hasGoogleApiKey = !!process.env.GOOGLE_API_KEY;
if (!hasVertexProjectLocationConfig && !hasGoogleApiKey) {
return (
'Must specify GOOGLE_GENAI_USE_VERTEXAI=true and either:\n' +
'• GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION environment variables.\n' +
'GOOGLE_API_KEY environment variable (if using express mode).\n' +
'Update your .env and try again, no reload needed!'
);
}
return null;
}
@@ -12,7 +12,7 @@ import {
Config,
ConfigParameters,
ContentGeneratorConfig,
} from '@gemini-cli/core';
} from '@google/gemini-cli-core';
const TEST_CONTENT_GENERATOR_CONFIG: ContentGeneratorConfig = {
apiKey: 'test-key',
@@ -21,8 +21,8 @@ const TEST_CONTENT_GENERATOR_CONFIG: ContentGeneratorConfig = {
};
// Mock file discovery service and tool registry
vi.mock('@gemini-cli/core', async () => {
const actual = await vi.importActual('@gemini-cli/core');
vi.mock('@google/gemini-cli-core', async () => {
const actual = await vi.importActual('@google/gemini-cli-core');
return {
...actual,
FileDiscoveryService: vi.fn().mockImplementation(() => ({
@@ -75,7 +75,9 @@ describe('Configuration Integration Tests', () => {
sandbox: false,
targetDir: tempDir,
debugMode: false,
fileFilteringRespectGitIgnore: false,
fileFiltering: {
respectGitIgnore: false,
},
};
const config = new Config(configParams);
@@ -109,7 +111,9 @@ describe('Configuration Integration Tests', () => {
sandbox: false,
targetDir: tempDir,
debugMode: false,
fileFilteringRespectGitIgnore: false,
fileFiltering: {
respectGitIgnore: false,
},
};
const config = new Config(configParams);
@@ -178,7 +182,9 @@ describe('Configuration Integration Tests', () => {
sandbox: false,
targetDir: tempDir,
debugMode: false,
fileFilteringRespectGitIgnore: false, // CI might need to see all files
fileFiltering: {
respectGitIgnore: false,
}, // CI might need to see all files
};
const config = new Config(configParams);
@@ -204,4 +210,34 @@ describe('Configuration Integration Tests', () => {
expect(config.getCheckpointingEnabled()).toBe(true);
});
});
describe('Extension Context Files', () => {
it('should have an empty array for extension context files by default', () => {
const configParams: ConfigParameters = {
cwd: '/tmp',
contentGeneratorConfig: TEST_CONTENT_GENERATOR_CONFIG,
embeddingModel: 'test-embedding-model',
sandbox: false,
targetDir: tempDir,
debugMode: false,
};
const config = new Config(configParams);
expect(config.getExtensionContextFilePaths()).toEqual([]);
});
it('should correctly store and return extension context file paths', () => {
const contextFiles = ['/path/to/file1.txt', '/path/to/file2.js'];
const configParams: ConfigParameters = {
cwd: '/tmp',
contentGeneratorConfig: TEST_CONTENT_GENERATOR_CONFIG,
embeddingModel: 'test-embedding-model',
sandbox: false,
targetDir: tempDir,
debugMode: false,
extensionContextFilePaths: contextFiles,
};
const config = new Config(configParams);
expect(config.getExtensionContextFilePaths()).toEqual(contextFiles);
});
});
});
+10 -11
View File
@@ -9,15 +9,13 @@ import * as os from 'os';
import { loadCliConfig } from './config.js';
import { Settings } from './settings.js';
import { Extension } from './extension.js';
import * as ServerConfig from '@gemini-cli/core';
const MOCK_HOME_DIR = '/mock/home/user';
import * as ServerConfig from '@google/gemini-cli-core';
vi.mock('os', async (importOriginal) => {
const actualOs = await importOriginal<typeof os>();
return {
...actualOs,
homedir: vi.fn(() => MOCK_HOME_DIR),
homedir: vi.fn(() => '/mock/home/user'),
};
});
@@ -31,9 +29,10 @@ vi.mock('read-package-up', () => ({
),
}));
vi.mock('@gemini-cli/core', async () => {
const actualServer =
await vi.importActual<typeof ServerConfig>('@gemini-cli/core');
vi.mock('@google/gemini-cli-core', async () => {
const actualServer = await vi.importActual<typeof ServerConfig>(
'@google/gemini-cli-core',
);
return {
...actualServer,
loadEnvironment: vi.fn(),
@@ -53,7 +52,7 @@ describe('loadCliConfig', () => {
beforeEach(() => {
vi.resetAllMocks();
vi.mocked(os.homedir).mockReturnValue(MOCK_HOME_DIR);
vi.mocked(os.homedir).mockReturnValue('/mock/home/user');
process.env.GEMINI_API_KEY = 'test-api-key'; // Ensure API key is set for tests
});
@@ -98,7 +97,7 @@ describe('loadCliConfig telemetry', () => {
beforeEach(() => {
vi.resetAllMocks();
vi.mocked(os.homedir).mockReturnValue(MOCK_HOME_DIR);
vi.mocked(os.homedir).mockReturnValue('/mock/home/user');
process.env.GEMINI_API_KEY = 'test-api-key';
});
@@ -250,7 +249,7 @@ describe('loadCliConfig telemetry', () => {
describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => {
beforeEach(() => {
vi.resetAllMocks();
vi.mocked(os.homedir).mockReturnValue(MOCK_HOME_DIR);
vi.mocked(os.homedir).mockReturnValue('/mock/home/user');
// Other common mocks would be reset here.
});
@@ -310,7 +309,7 @@ describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => {
// Example of a previously failing test structure:
/*
it('should correctly use mocked homedir for global path', async () => {
const MOCK_GEMINI_DIR_LOCAL = path.join(MOCK_HOME_DIR, '.gemini');
const MOCK_GEMINI_DIR_LOCAL = path.join('/mock/home/user', '.gemini');
const MOCK_GLOBAL_PATH_LOCAL = path.join(MOCK_GEMINI_DIR_LOCAL, 'GEMINI.md');
mockFs({
[MOCK_GLOBAL_PATH_LOCAL]: { type: 'file', content: 'GlobalContentOnly' }
+8 -2
View File
@@ -18,7 +18,7 @@ import {
DEFAULT_GEMINI_EMBEDDING_MODEL,
FileDiscoveryService,
TelemetryTarget,
} from '@gemini-cli/core';
} from '@google/gemini-cli-core';
import { Settings } from './settings.js';
import { Extension } from './extension.js';
@@ -227,8 +227,13 @@ export async function loadCliConfig(
settings.telemetry?.otlpEndpoint,
logPrompts: argv.telemetryLogPrompts ?? settings.telemetry?.logPrompts,
},
usageStatisticsEnabled: settings.usageStatisticsEnabled ?? true,
// Git-aware file filtering settings
fileFilteringRespectGitIgnore: settings.fileFiltering?.respectGitIgnore,
fileFiltering: {
respectGitIgnore: settings.fileFiltering?.respectGitIgnore,
enableRecursiveFileSearch:
settings.fileFiltering?.enableRecursiveFileSearch,
},
checkpointing: argv.checkpointing || settings.checkpointing?.enabled,
proxy:
process.env.HTTPS_PROXY ||
@@ -239,6 +244,7 @@ export async function loadCliConfig(
fileDiscoveryService: fileService,
bugCommand: settings.bugCommand,
model: argv.model!,
extensionContextFilePaths,
});
}
+1 -1
View File
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { MCPServerConfig } from '@gemini-cli/core';
import { MCPServerConfig } from '@google/gemini-cli-core';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
+8 -3
View File
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { SandboxConfig } from '@gemini-cli/core';
import { SandboxConfig } from '@google/gemini-cli-core';
import commandExists from 'command-exists';
import * as os from 'node:os';
import { getPackageJson } from '../utils/package.js';
@@ -36,7 +36,12 @@ function getSandboxCommand(
}
// note environment variable takes precedence over argument (from command line or settings)
sandbox = process.env.GEMINI_SANDBOX?.toLowerCase().trim() ?? sandbox;
const environmentConfiguredSandbox =
process.env.GEMINI_SANDBOX?.toLowerCase().trim() ?? '';
sandbox =
environmentConfiguredSandbox?.length > 0
? environmentConfiguredSandbox
: sandbox;
if (sandbox === '1' || sandbox === 'true') sandbox = true;
else if (sandbox === '0' || sandbox === 'false' || !sandbox) sandbox = false;
@@ -53,7 +58,7 @@ function getSandboxCommand(
);
process.exit(1);
}
// confirm that specfied command exists
// confirm that specified command exists
if (commandExists.sync(sandbox)) {
return sandbox;
}
+3 -5
View File
@@ -6,15 +6,13 @@
/// <reference types="vitest/globals" />
const MOCK_HOME_DIR = '/mock/home/user'; // MUST BE FIRST
// Mock 'os' first. Its factory uses MOCK_HOME_DIR.
// Mock 'os' first.
import * as osActual from 'os'; // Import for type info for the mock factory
vi.mock('os', async (importOriginal) => {
const actualOs = await importOriginal<typeof osActual>();
return {
...actualOs,
homedir: vi.fn(() => MOCK_HOME_DIR),
homedir: vi.fn(() => '/mock/home/user'),
};
});
@@ -77,7 +75,7 @@ describe('Settings Loading and Merging', () => {
mockFsMkdirSync = vi.mocked(fs.mkdirSync);
mockStripJsonComments = vi.mocked(stripJsonComments);
vi.mocked(osActual.homedir).mockReturnValue(MOCK_HOME_DIR);
vi.mocked(osActual.homedir).mockReturnValue('/mock/home/user');
(mockStripJsonComments as unknown as Mock).mockImplementation(
(jsonString: string) => jsonString,
);
+4 -1
View File
@@ -13,7 +13,7 @@ import {
BugCommandSettings,
TelemetrySettings,
AuthType,
} from '@gemini-cli/core';
} from '@google/gemini-cli-core';
import stripJsonComments from 'strip-json-comments';
import { DefaultLight } from '../ui/themes/default-light.js';
import { DefaultDark } from '../ui/themes/default.js';
@@ -49,13 +49,16 @@ export interface Settings {
contextFileName?: string | string[];
accessibility?: AccessibilitySettings;
telemetry?: TelemetrySettings;
usageStatisticsEnabled?: boolean;
preferredEditor?: string;
bugCommand?: BugCommandSettings;
checkpointing?: CheckpointingSettings;
autoConfigureMaxOldSpaceSize?: boolean;
// Git-aware file filtering settings
fileFiltering?: {
respectGitIgnore?: boolean;
enableRecursiveFileSearch?: boolean;
};
// UI setting. Does not display the ANSI-controlled terminal title.
+71 -8
View File
@@ -10,6 +10,9 @@ import { AppWrapper } from './ui/App.js';
import { loadCliConfig } from './config/config.js';
import { readStdin } from './utils/readStdin.js';
import { basename } from 'node:path';
import v8 from 'node:v8';
import os from 'node:os';
import { spawn } from 'node:child_process';
import { start_sandbox } from './utils/sandbox.js';
import {
LoadedSettings,
@@ -30,10 +33,54 @@ import {
sessionId,
logUserPrompt,
AuthType,
} from '@gemini-cli/core';
} from '@google/gemini-cli-core';
import { validateAuthMethod } from './config/auth.js';
import { setMaxSizedBoxDebugging } from './ui/components/shared/MaxSizedBox.js';
function getNodeMemoryArgs(config: Config): string[] {
const totalMemoryMB = os.totalmem() / (1024 * 1024);
const heapStats = v8.getHeapStatistics();
const currentMaxOldSpaceSizeMb = Math.floor(
heapStats.heap_size_limit / 1024 / 1024,
);
// Set target to 50% of total memory
const targetMaxOldSpaceSizeInMB = Math.floor(totalMemoryMB * 0.5);
if (config.getDebugMode()) {
console.debug(
`Current heap size ${currentMaxOldSpaceSizeMb.toFixed(2)} MB`,
);
}
if (process.env.GEMINI_CLI_NO_RELAUNCH) {
return [];
}
if (targetMaxOldSpaceSizeInMB > currentMaxOldSpaceSizeMb) {
if (config.getDebugMode()) {
console.debug(
`Need to relaunch with more memory: ${targetMaxOldSpaceSizeInMB.toFixed(2)} MB`,
);
}
return [`--max-old-space-size=${targetMaxOldSpaceSizeInMB}`];
}
return [];
}
async function relaunchWithAdditionalArgs(additionalArgs: string[]) {
const nodeArgs = [...additionalArgs, ...process.argv.slice(1)];
const newEnv = { ...process.env, GEMINI_CLI_NO_RELAUNCH: 'true' };
const child = spawn(process.execPath, nodeArgs, {
stdio: 'inherit',
env: newEnv,
});
await new Promise((resolve) => child.on('close', resolve));
process.exit(0);
}
export async function main() {
const workspaceRoot = process.cwd();
const settings = loadSettings(workspaceRoot);
@@ -55,7 +102,7 @@ export async function main() {
const config = await loadCliConfig(settings.merged, extensions, sessionId);
// set default fallback to gemini api key
// this has to go after load cli becuase thats where the env is set
// this has to go after load cli because thats where the env is set
if (!settings.merged.selectedAuthType && process.env.GEMINI_API_KEY) {
settings.setValue(
SettingScope.User,
@@ -84,24 +131,38 @@ export async function main() {
}
}
const memoryArgs = settings.merged.autoConfigureMaxOldSpaceSize
? getNodeMemoryArgs(config)
: [];
// hop into sandbox if we are outside and sandboxing is enabled
if (!process.env.SANDBOX) {
const sandboxConfig = config.getSandbox();
if (sandboxConfig) {
if (settings.merged.selectedAuthType) {
// Validate authentication here because the sandbox will interfere with the Oauth2 web redirect.
const err = validateAuthMethod(settings.merged.selectedAuthType);
if (err) {
console.error(err);
try {
const err = validateAuthMethod(settings.merged.selectedAuthType);
if (err) {
throw new Error(err);
}
await config.refreshAuth(settings.merged.selectedAuthType);
} catch (err) {
console.error('Error authenticating:', err);
process.exit(1);
}
await config.refreshAuth(settings.merged.selectedAuthType);
}
await start_sandbox(sandboxConfig);
await start_sandbox(sandboxConfig, memoryArgs);
process.exit(0);
} else {
// Not in a sandbox and not entering one, so relaunch with additional
// arguments to control memory usage if needed.
if (memoryArgs.length > 0) {
await relaunchWithAdditionalArgs(memoryArgs);
process.exit(0);
}
}
}
let input = config.getQuestion();
const startupWarnings = await getStartupWarnings();
@@ -131,6 +192,8 @@ export async function main() {
}
logUserPrompt(config, {
'event.name': 'user_prompt',
'event.timestamp': new Date().toISOString(),
prompt: input,
prompt_length: input.length,
});
+9 -10
View File
@@ -7,15 +7,14 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { runNonInteractive } from './nonInteractiveCli.js';
import { Config, GeminiClient, ToolRegistry } from '@gemini-cli/core';
import { Config, GeminiClient, ToolRegistry } from '@google/gemini-cli-core';
import { GenerateContentResponse, Part, FunctionCall } from '@google/genai';
// Mock dependencies
vi.mock('@gemini-cli/core', async () => {
const actualCore =
await vi.importActual<typeof import('@gemini-cli/core')>(
'@gemini-cli/core',
);
vi.mock('@google/gemini-cli-core', async () => {
const actualCore = await vi.importActual<
typeof import('@google/gemini-cli-core')
>('@google/gemini-cli-core');
return {
...actualCore,
GeminiClient: vi.fn(),
@@ -53,6 +52,7 @@ describe('runNonInteractive', () => {
mockConfig = {
getToolRegistry: vi.fn().mockReturnValue(mockToolRegistry),
getGeminiClient: vi.fn().mockReturnValue(mockGeminiClient),
getContentGeneratorConfig: vi.fn().mockReturnValue({}),
} as unknown as Config;
mockProcessStdoutWrite = vi.fn().mockImplementation(() => true);
@@ -109,7 +109,7 @@ describe('runNonInteractive', () => {
};
const { executeToolCall: mockCoreExecuteToolCall } = await import(
'@gemini-cli/core'
'@google/gemini-cli-core'
);
vi.mocked(mockCoreExecuteToolCall).mockResolvedValue({
callId: 'fc1',
@@ -162,7 +162,7 @@ describe('runNonInteractive', () => {
};
const { executeToolCall: mockCoreExecuteToolCall } = await import(
'@gemini-cli/core'
'@google/gemini-cli-core'
);
vi.mocked(mockCoreExecuteToolCall).mockResolvedValue({
callId: 'fcError',
@@ -215,8 +215,7 @@ describe('runNonInteractive', () => {
await runNonInteractive(mockConfig, 'Initial fail');
expect(consoleErrorSpy).toHaveBeenCalledWith(
'Error processing input:',
apiError,
'[API Error: API connection failed]',
);
});
});
+18 -2
View File
@@ -11,7 +11,7 @@ import {
ToolRegistry,
shutdownTelemetry,
isTelemetrySdkInitialized,
} from '@gemini-cli/core';
} from '@google/gemini-cli-core';
import {
Content,
Part,
@@ -19,6 +19,8 @@ import {
GenerateContentResponse,
} from '@google/genai';
import { parseAndFormatApiError } from './ui/utils/errorParsing.js';
function getResponseText(response: GenerateContentResponse): string | null {
if (response.candidates && response.candidates.length > 0) {
const candidate = response.candidates[0];
@@ -45,6 +47,14 @@ export async function runNonInteractive(
config: Config,
input: string,
): Promise<void> {
// Handle EPIPE errors when the output is piped to a command that closes early.
process.stdout.on('error', (err: NodeJS.ErrnoException) => {
if (err.code === 'EPIPE') {
// Exit gracefully if the pipe is closed.
process.exit(0);
}
});
const geminiClient = config.getGeminiClient();
const toolRegistry: ToolRegistry = await config.getToolRegistry();
@@ -89,6 +99,7 @@ export async function runNonInteractive(
callId,
name: fc.name as string,
args: (fc.args ?? {}) as Record<string, unknown>,
isClientInitiated: false,
};
const toolResponse = await executeToolCall(
@@ -125,7 +136,12 @@ export async function runNonInteractive(
}
}
} catch (error) {
console.error('Error processing input:', error);
console.error(
parseAndFormatApiError(
error,
config.getContentGeneratorConfig().authType,
),
);
process.exit(1);
} finally {
if (isTelemetrySdkInitialized()) {
+11 -9
View File
@@ -14,7 +14,7 @@ import {
ToolRegistry,
AccessibilitySettings,
SandboxConfig,
} from '@gemini-cli/core';
} from '@google/gemini-cli-core';
import { LoadedSettings, SettingsFile, Settings } from '../config/settings.js';
import process from 'node:process';
@@ -68,9 +68,10 @@ interface MockServerConfig {
getAllGeminiMdFilenames: Mock<() => string[]>;
}
// Mock @gemini-cli/core and its Config class
vi.mock('@gemini-cli/core', async (importOriginal) => {
const actualCore = await importOriginal<typeof import('@gemini-cli/core')>();
// Mock @google/gemini-cli-core and its Config class
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actualCore =
await importOriginal<typeof import('@google/gemini-cli-core')>();
const ConfigClassMock = vi
.fn()
.mockImplementation((optionsPassedToConstructor) => {
@@ -125,6 +126,7 @@ vi.mock('@gemini-cli/core', async (importOriginal) => {
getGeminiClient: vi.fn(() => ({})),
getCheckpointingEnabled: vi.fn(() => opts.checkpointing ?? true),
getAllGeminiMdFilenames: vi.fn(() => ['GEMINI.md']),
setFlashFallbackHandler: vi.fn(),
};
});
return {
@@ -260,7 +262,7 @@ describe('App UI', () => {
it('should display custom contextFileName in footer when set and count is 1', async () => {
mockSettings = createMockSettings({
contextFileName: 'AGENTS.MD',
contextFileName: 'AGENTS.md',
theme: 'Default',
});
mockConfig.getGeminiMdFileCount.mockReturnValue(1);
@@ -275,12 +277,12 @@ describe('App UI', () => {
);
currentUnmount = unmount;
await Promise.resolve();
expect(lastFrame()).toContain('Using 1 AGENTS.MD file');
expect(lastFrame()).toContain('Using 1 AGENTS.md file');
});
it('should display the first custom contextFileName when an array is provided', async () => {
it('should display a generic message when multiple context files with different names are provided', async () => {
mockSettings = createMockSettings({
contextFileName: ['AGENTS.MD', 'CONTEXT.MD'],
contextFileName: ['AGENTS.md', 'CONTEXT.md'],
theme: 'Default',
});
mockConfig.getGeminiMdFileCount.mockReturnValue(2);
@@ -295,7 +297,7 @@ describe('App UI', () => {
);
currentUnmount = unmount;
await Promise.resolve();
expect(lastFrame()).toContain('Using 2 AGENTS.MD files');
expect(lastFrame()).toContain('Using 2 context files');
});
it('should display custom contextFileName with plural when set and count is > 1', async () => {
+142 -33
View File
@@ -12,6 +12,7 @@ import {
Static,
Text,
useStdin,
useStdout,
useInput,
type Key as InkKeyType,
} from 'ink';
@@ -53,7 +54,7 @@ import {
ApprovalMode,
isEditorAvailable,
EditorType,
} from '@gemini-cli/core';
} from '@google/gemini-cli-core';
import { validateAuthMethod } from '../config/auth.js';
import { useLogger } from './hooks/useLogger.js';
import { StreamingContext } from './contexts/StreamingContext.js';
@@ -62,10 +63,15 @@ import {
useSessionStats,
} from './contexts/SessionContext.js';
import { useGitBranchName } from './hooks/useGitBranchName.js';
import { useBracketedPaste } from './hooks/useBracketedPaste.js';
import { useTextBuffer } from './components/shared/text-buffer.js';
import * as fs from 'fs';
import { UpdateNotification } from './components/UpdateNotification.js';
import { checkForUpdates } from './utils/updateCheck.js';
import ansiEscapes from 'ansi-escapes';
import { OverflowProvider } from './contexts/OverflowContext.js';
import { ShowMoreLines } from './components/ShowMoreLines.js';
import { PrivacyNotice } from './privacy/PrivacyNotice.js';
const CTRL_EXIT_PROMPT_DURATION_MS = 1000;
@@ -82,7 +88,9 @@ export const AppWrapper = (props: AppProps) => (
);
const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
useBracketedPaste();
const [updateMessage, setUpdateMessage] = useState<string | null>(null);
const { stdout } = useStdout();
useEffect(() => {
checkForUpdates().then(setUpdateMessage);
@@ -98,8 +106,9 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
const [staticNeedsRefresh, setStaticNeedsRefresh] = useState(false);
const [staticKey, setStaticKey] = useState(0);
const refreshStatic = useCallback(() => {
stdout.write(ansiEscapes.clearTerminal);
setStaticKey((prev) => prev + 1);
}, [setStaticKey]);
}, [setStaticKey, stdout]);
const [geminiMdFileCount, setGeminiMdFileCount] = useState<number>(0);
const [debugMessage, setDebugMessage] = useState<string>('');
@@ -109,6 +118,7 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
const [editorError, setEditorError] = useState<string | null>(null);
const [footerHeight, setFooterHeight] = useState<number>(0);
const [corgiMode, setCorgiMode] = useState(false);
const [currentModel, setCurrentModel] = useState(config.getModel());
const [shellModeActive, setShellModeActive] = useState(false);
const [showErrorDetails, setShowErrorDetails] = useState<boolean>(false);
const [showToolDescriptions, setShowToolDescriptions] =
@@ -121,6 +131,11 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
const [ctrlDPressedOnce, setCtrlDPressedOnce] = useState(false);
const ctrlDTimerRef = useRef<NodeJS.Timeout | null>(null);
const [constrainHeight, setConstrainHeight] = useState<boolean>(true);
const [showPrivacyNotice, setShowPrivacyNotice] = useState<boolean>(false);
const openPrivacyNotice = useCallback(() => {
setShowPrivacyNotice(true);
}, []);
const errorCount = useMemo(
() => consoleMessages.filter((msg) => msg.type === 'error').length,
@@ -168,7 +183,7 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
addItem(
{
type: MessageType.INFO,
text: 'Refreshing hierarchical memory (GEMINI.md files)...',
text: 'Refreshing hierarchical memory (GEMINI.md or other context files)...',
},
Date.now(),
);
@@ -177,6 +192,7 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
process.cwd(),
config.getDebugMode(),
config.getFileService(),
config.getExtensionContextFilePaths(),
);
config.setUserMemory(memoryContent);
config.setGeminiMdFileCount(fileCount);
@@ -207,12 +223,52 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
}
}, [config, addItem]);
// Watch for model changes (e.g., from Flash fallback)
useEffect(() => {
const checkModelChange = () => {
const configModel = config.getModel();
if (configModel !== currentModel) {
setCurrentModel(configModel);
}
};
// Check immediately and then periodically
checkModelChange();
const interval = setInterval(checkModelChange, 1000); // Check every second
return () => clearInterval(interval);
}, [config, currentModel]);
// Set up Flash fallback handler
useEffect(() => {
const flashFallbackHandler = async (
currentModel: string,
fallbackModel: string,
): Promise<boolean> => {
// Add message to UI history
addItem(
{
type: MessageType.INFO,
text: `⚡ Slow response times detected. Automatically switching from ${currentModel} to ${fallbackModel} for faster responses for the remainder of this session.
To avoid this you can either upgrade to Standard tier. See: https://goo.gle/set-up-gemini-code-assist
Or you can utilize a Gemini API Key. See: https://goo.gle/gemini-cli-docs-auth#gemini-api-key
You can switch authentication methods by typing /auth`,
},
Date.now(),
);
return true; // Always accept the fallback
};
config.setFlashFallbackHandler(flashFallbackHandler);
}, [config, addItem]);
const {
handleSlashCommand,
slashCommands,
pendingHistoryItems: pendingSlashCommandHistoryItems,
} = useSlashCommandProcessor(
config,
settings,
history,
addItem,
clearItems,
@@ -227,10 +283,12 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
toggleCorgiMode,
showToolDescriptions,
setQuittingMessages,
openPrivacyNotice,
);
const pendingHistoryItems = [...pendingSlashCommandHistoryItems];
const { rows: terminalHeight, columns: terminalWidth } = useTerminalSize();
const isInitialMount = useRef(true);
const { stdin, setRawMode } = useStdin();
const isValidPath = useCallback((filePath: string): boolean => {
try {
@@ -285,13 +343,33 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
);
useInput((input: string, key: InkKeyType) => {
let enteringConstrainHeightMode = false;
if (!constrainHeight) {
// Automatically re-enter constrain height mode if the user types
// anything. When constrainHeight==false, the user will experience
// significant flickering so it is best to disable it immediately when
// the user starts interacting with the app.
enteringConstrainHeightMode = true;
setConstrainHeight(true);
// If our pending history item happens to exceed the terminal height we will most likely need to refresh
// our static collection to ensure no duplication or tearing. This is currently working around a core bug
// in Ink which we have a PR out to fix: https://github.com/vadimdemedes/ink/pull/717
if (pendingHistoryItemRef.current && pendingHistoryItems.length > 0) {
const pendingItemDimensions = measureElement(
pendingHistoryItemRef.current,
);
if (pendingItemDimensions.height > availableTerminalHeight) {
refreshStatic();
}
}
}
if (key.ctrl && input === 'o') {
setShowErrorDetails((prev) => !prev);
refreshStatic();
} else if (key.ctrl && input === 't') {
const newValue = !showToolDescriptions;
setShowToolDescriptions(newValue);
refreshStatic();
const mcpServers = config.getMcpServers();
if (Object.keys(mcpServers || {}).length > 0) {
@@ -305,8 +383,8 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
return;
}
handleExit(ctrlDPressedOnce, setCtrlDPressedOnce, ctrlDTimerRef);
} else if (key.ctrl && input === 's') {
setConstrainHeight((prev) => !prev);
} else if (key.ctrl && input === 's' && !enteringConstrainHeightMode) {
setConstrainHeight(false);
}
});
@@ -353,6 +431,7 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
shellModeActive,
getPreferredEditor,
onAuthError,
performMemoryRefresh,
);
pendingHistoryItems.push(...pendingGeminiHistoryItems);
const { elapsedTime, currentLoadingPhrase } =
@@ -433,6 +512,24 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
[terminalHeight, footerHeight],
);
useEffect(() => {
// skip refreshing Static during first mount
if (isInitialMount.current) {
isInitialMount.current = false;
return;
}
// debounce so it doesn't fire up too often during resize
const handler = setTimeout(() => {
setStaticNeedsRefresh(false);
refreshStatic();
}, 300);
return () => {
clearTimeout(handler);
};
}, [terminalWidth, terminalHeight, refreshStatic]);
useEffect(() => {
if (!pendingHistoryItems.length) {
return;
@@ -493,7 +590,7 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
);
}
const mainAreaWidth = Math.floor(terminalWidth * 0.9);
const debugConsoleMaxHeight = Math.max(terminalHeight * 0.2, 5);
const debugConsoleMaxHeight = Math.floor(Math.max(terminalHeight * 0.2, 5));
// Arbitrary threshold to ensure that items in the static area are large
// enough but not too large to make the terminal hard to use.
const staticAreaMaxItemHeight = Math.max(terminalHeight * 4, 100);
@@ -533,23 +630,27 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
>
{(item) => item}
</Static>
<Box ref={pendingHistoryItemRef}>
{pendingHistoryItems.map((item, i) => (
<HistoryItemDisplay
key={i}
availableTerminalHeight={
constrainHeight ? availableTerminalHeight : undefined
}
terminalWidth={mainAreaWidth}
// TODO(taehykim): It seems like references to ids aren't necessary in
// HistoryItemDisplay. Refactor later. Use a fake id for now.
item={{ ...item, id: 0 }}
isPending={true}
config={config}
isFocused={!isEditorDialogOpen}
/>
))}
</Box>
<OverflowProvider>
<Box ref={pendingHistoryItemRef} flexDirection="column">
{pendingHistoryItems.map((item, i) => (
<HistoryItemDisplay
key={i}
availableTerminalHeight={
constrainHeight ? availableTerminalHeight : undefined
}
terminalWidth={mainAreaWidth}
// TODO(taehykim): It seems like references to ids aren't necessary in
// HistoryItemDisplay. Refactor later. Use a fake id for now.
item={{ ...item, id: 0 }}
isPending={true}
config={config}
isFocused={!isEditorDialogOpen}
/>
))}
<ShowMoreLines constrainHeight={constrainHeight} />
</Box>
</OverflowProvider>
{showHelp && <Help commands={slashCommands} />}
<Box flexDirection="column" ref={mainControlsRef}>
@@ -618,6 +719,11 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
onExit={exitEditorDialog}
/>
</Box>
) : showPrivacyNotice ? (
<PrivacyNotice
onExit={() => setShowPrivacyNotice(false)}
config={config}
/>
) : (
<>
<LoadingIndicator
@@ -673,13 +779,16 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
</Box>
{showErrorDetails && (
<DetailedMessagesDisplay
messages={filteredConsoleMessages}
maxHeight={
constrainHeight ? debugConsoleMaxHeight : undefined
}
width={inputWidth}
/>
<OverflowProvider>
<DetailedMessagesDisplay
messages={filteredConsoleMessages}
maxHeight={
constrainHeight ? debugConsoleMaxHeight : undefined
}
width={inputWidth}
/>
<ShowMoreLines constrainHeight={constrainHeight} />
</OverflowProvider>
)}
{isInputActive && (
@@ -732,7 +841,7 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
</Box>
)}
<Footer
model={config.getModel()}
model={currentModel}
targetDir={config.getTargetDir()}
debugMode={config.getDebugMode()}
branchName={branchName}
@@ -14,6 +14,8 @@ interface AboutBoxProps {
osVersion: string;
sandboxEnv: string;
modelVersion: string;
selectedAuthType: string;
gcpProject: string;
}
export const AboutBox: React.FC<AboutBoxProps> = ({
@@ -21,6 +23,8 @@ export const AboutBox: React.FC<AboutBoxProps> = ({
osVersion,
sandboxEnv,
modelVersion,
selectedAuthType,
gcpProject,
}) => (
<Box
borderStyle="round"
@@ -87,5 +91,29 @@ export const AboutBox: React.FC<AboutBoxProps> = ({
<Text>{osVersion}</Text>
</Box>
</Box>
<Box flexDirection="row">
<Box width="35%">
<Text bold color={Colors.LightBlue}>
Auth Method
</Text>
</Box>
<Box>
<Text>
{selectedAuthType.startsWith('oauth') ? 'OAuth' : selectedAuthType}
</Text>
</Box>
</Box>
{gcpProject && (
<Box flexDirection="row">
<Box width="35%">
<Text bold color={Colors.LightBlue}>
GCP Project
</Text>
</Box>
<Box>
<Text>{gcpProject}</Text>
</Box>
</Box>
)}
</Box>
);
@@ -5,11 +5,14 @@
*/
import { render } from 'ink-testing-library';
import { describe, it, expect, vi } from 'vitest';
import { AuthDialog } from './AuthDialog.js';
import { LoadedSettings } from '../../config/settings.js';
import { AuthType } from '@gemini-cli/core';
import { LoadedSettings, SettingScope } from '../../config/settings.js';
import { AuthType } from '@google/gemini-cli-core';
describe('AuthDialog', () => {
const wait = (ms = 50) => new Promise((resolve) => setTimeout(resolve, ms));
it('should show an error if the initial auth type is invalid', () => {
const settings: LoadedSettings = new LoadedSettings(
{
@@ -38,4 +41,75 @@ describe('AuthDialog', () => {
'GEMINI_API_KEY environment variable not found',
);
});
it('should prevent exiting when no auth method is selected and show error message', async () => {
const onSelect = vi.fn();
const settings: LoadedSettings = new LoadedSettings(
{
settings: {
selectedAuthType: undefined,
},
path: '',
},
{
settings: {},
path: '',
},
[],
);
const { lastFrame, stdin, unmount } = render(
<AuthDialog
onSelect={onSelect}
onHighlight={() => {}}
settings={settings}
/>,
);
await wait();
// Simulate pressing escape key
stdin.write('\u001b'); // ESC key
await wait();
// Should show error message instead of calling onSelect
expect(lastFrame()).toContain(
'You must select an auth method to proceed. Press Ctrl+C twice to exit.',
);
expect(onSelect).not.toHaveBeenCalled();
unmount();
});
it('should allow exiting when auth method is already selected', async () => {
const onSelect = vi.fn();
const settings: LoadedSettings = new LoadedSettings(
{
settings: {
selectedAuthType: AuthType.USE_GEMINI,
},
path: '',
},
{
settings: {},
path: '',
},
[],
);
const { stdin, unmount } = render(
<AuthDialog
onSelect={onSelect}
onHighlight={() => {}}
settings={settings}
/>,
);
await wait();
// Simulate pressing escape key
stdin.write('\u001b'); // ESC key
await wait();
// Should call onSelect with undefined to exit
expect(onSelect).toHaveBeenCalledWith(undefined, SettingScope.User);
unmount();
});
});
+21 -25
View File
@@ -9,7 +9,7 @@ import { Box, Text, useInput } from 'ink';
import { Colors } from '../colors.js';
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
import { LoadedSettings, SettingScope } from '../../config/settings.js';
import { AuthType } from '@gemini-cli/core';
import { AuthType } from '@google/gemini-cli-core';
import { validateAuthMethod } from '../../config/auth.js';
interface AuthDialogProps {
@@ -28,32 +28,15 @@ export function AuthDialog({
const [errorMessage, setErrorMessage] = useState<string | null>(
initialErrorMessage || null,
);
const allAuthItems = [
const items = [
{
label: 'Login with Google',
value: AuthType.LOGIN_WITH_GOOGLE_PERSONAL,
},
{ label: 'Gemini API Key', value: AuthType.USE_GEMINI },
{
label: 'Login with Google Work',
value: AuthType.LOGIN_WITH_GOOGLE_ENTERPRISE,
},
{ label: 'Vertex API Key', value: AuthType.USE_VERTEX_AI },
{ label: 'Gemini API Key (AI Studio)', value: AuthType.USE_GEMINI },
{ label: 'Vertex AI', value: AuthType.USE_VERTEX_AI },
];
const isSelectedAuthInMore = allAuthItems
.slice(2)
.some((item) => item.value === settings.merged.selectedAuthType);
const [showAll, setShowAll] = useState(isSelectedAuthInMore);
const initialAuthItems = [
...allAuthItems.slice(0, 2),
{ label: 'More...', value: 'more' },
];
const items = showAll ? allAuthItems : initialAuthItems;
let initialAuthIndex = items.findIndex(
(item) => item.value === settings.merged.selectedAuthType,
);
@@ -63,10 +46,6 @@ export function AuthDialog({
}
const handleAuthSelect = (authMethod: string) => {
if (authMethod === 'more') {
setShowAll(true);
return;
}
const error = validateAuthMethod(authMethod);
if (error) {
setErrorMessage(error);
@@ -78,6 +57,13 @@ export function AuthDialog({
useInput((_input, key) => {
if (key.escape) {
if (settings.merged.selectedAuthType === undefined) {
// Prevent exiting if no auth method is set
setErrorMessage(
'You must select an auth method to proceed. Press Ctrl+C twice to exit.',
);
return;
}
onSelect(undefined, SettingScope.User);
}
});
@@ -106,6 +92,16 @@ export function AuthDialog({
<Box marginTop={1}>
<Text color={Colors.Gray}>(Use Enter to select)</Text>
</Box>
<Box marginTop={1}>
<Text>Terms of Services and Privacy Notice for Gemini CLI</Text>
</Box>
<Box marginTop={1}>
<Text color={Colors.AccentBlue}>
{
'https://github.com/google-gemini/gemini-cli/blob/main/docs/tos-privacy.md'
}
</Text>
</Box>
</Box>
);
}
@@ -7,7 +7,7 @@
import React from 'react';
import { Box, Text } from 'ink';
import { Colors } from '../colors.js';
import { ApprovalMode } from '@gemini-cli/core';
import { ApprovalMode } from '@google/gemini-cli-core';
interface AutoAcceptIndicatorProps {
approvalMode: ApprovalMode;
@@ -7,7 +7,7 @@
import React from 'react';
import { Text } from 'ink';
import { Colors } from '../colors.js';
import { type MCPServerConfig } from '@gemini-cli/core';
import { type MCPServerConfig } from '@google/gemini-cli-core';
interface ContextSummaryDisplayProps {
geminiMdFileCount: number;
@@ -28,12 +28,16 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
return <Text> </Text>; // Render an empty space to reserve height
}
const geminiMdText =
geminiMdFileCount > 0
? `${geminiMdFileCount} ${contextFileNames[0]} file${
geminiMdFileCount > 1 ? 's' : ''
}`
: '';
const geminiMdText = (() => {
if (geminiMdFileCount === 0) {
return '';
}
const allNamesTheSame = new Set(contextFileNames).size < 2;
const name = allNamesTheSame ? contextFileNames[0] : 'context';
return `${geminiMdFileCount} ${name} file${
geminiMdFileCount > 1 ? 's' : ''
}`;
})();
const mcpText =
mcpServerCount > 0
@@ -37,7 +37,7 @@ export const DetailedMessagesDisplay: React.FC<
>
<Box marginBottom={1}>
<Text bold color={Colors.Foreground}>
Debug Console <Text color={Colors.Gray}>(ctrl+O to close)</Text>
Debug Console <Text color={Colors.Gray}>(ctrl+o to close)</Text>
</Text>
</Box>
<MaxSizedBox maxHeight={maxHeight} maxWidth={width - borderAndPadding}>
@@ -14,7 +14,7 @@ import {
} from '../editors/editorSettingsManager.js';
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
import { LoadedSettings, SettingScope } from '../../config/settings.js';
import { EditorType, isEditorAvailable } from '@gemini-cli/core';
import { EditorType, isEditorAvailable } from '@google/gemini-cli-core';
interface EditorDialogProps {
onSelect: (editorType: EditorType | undefined, scope: SettingScope) => void;
+2 -2
View File
@@ -7,7 +7,7 @@
import React from 'react';
import { Box, Text } from 'ink';
import { Colors } from '../colors.js';
import { shortenPath, tildeifyPath, tokenLimit } from '@gemini-cli/core';
import { shortenPath, tildeifyPath, tokenLimit } from '@google/gemini-cli-core';
import { ConsoleSummaryDisplay } from './ConsoleSummaryDisplay.js';
import process from 'node:process';
import { MemoryUsageDisplay } from './MemoryUsageDisplay.js';
@@ -74,7 +74,7 @@ export const Footer: React.FC<FooterProps> = ({
</Text>
) : (
<Text color={Colors.AccentRed}>
no sandbox <Text color={Colors.Gray}>(see docs)</Text>
no sandbox <Text color={Colors.Gray}>(see /docs)</Text>
</Text>
)}
</Box>
+15 -10
View File
@@ -30,17 +30,22 @@ export const Header: React.FC<HeaderProps> = ({
terminalWidth >= widthOfLongLogo ? longAsciiLogo : shortAsciiLogo;
}
const artWidth = getAsciiArtWidth(displayTitle);
return (
<>
<Box marginBottom={1} alignItems="flex-start">
{Colors.GradientColors ? (
<Gradient colors={Colors.GradientColors}>
<Text>{displayTitle}</Text>
</Gradient>
) : (
<Box
marginBottom={1}
alignItems="flex-start"
width={artWidth}
flexShrink={0}
>
{Colors.GradientColors ? (
<Gradient colors={Colors.GradientColors}>
<Text>{displayTitle}</Text>
)}
</Box>
</>
</Gradient>
) : (
<Text>{displayTitle}</Text>
)}
</Box>
);
};
@@ -67,6 +67,8 @@ describe('<HistoryItemDisplay />', () => {
osVersion: 'test-os',
sandboxEnv: 'test-env',
modelVersion: 'test-model',
selectedAuthType: 'test-auth',
gcpProject: 'test-project',
};
const { lastFrame } = render(
<HistoryItemDisplay {...baseItem} item={item} />,
@@ -18,7 +18,7 @@ import { Box } from 'ink';
import { AboutBox } from './AboutBox.js';
import { StatsDisplay } from './StatsDisplay.js';
import { SessionSummaryDisplay } from './SessionSummaryDisplay.js';
import { Config } from '@gemini-cli/core';
import { Config } from '@google/gemini-cli-core';
interface HistoryItemDisplayProps {
item: HistoryItem;
@@ -65,6 +65,8 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
osVersion={item.osVersion}
sandboxEnv={item.sandboxEnv}
modelVersion={item.modelVersion}
selectedAuthType={item.selectedAuthType}
gcpProject={item.gcpProject}
/>
)}
{item.type === 'stats' && (
@@ -7,7 +7,7 @@
import { render } from 'ink-testing-library';
import { InputPrompt, InputPromptProps } from './InputPrompt.js';
import type { TextBuffer } from './shared/text-buffer.js';
import { Config } from '@gemini-cli/core';
import { Config } from '@google/gemini-cli-core';
import { vi } from 'vitest';
import { useShellHistory } from '../hooks/useShellHistory.js';
import { useCompletion } from '../hooks/useCompletion.js';
+52 -40
View File
@@ -5,7 +5,7 @@
*/
import React, { useCallback, useEffect, useState } from 'react';
import { Text, Box, useInput } from 'ink';
import { Box, Text } from 'ink';
import { Colors } from '../colors.js';
import { SuggestionsDisplay } from './SuggestionsDisplay.js';
import { useInputHistory } from '../hooks/useInputHistory.js';
@@ -16,9 +16,10 @@ import stringWidth from 'string-width';
import process from 'node:process';
import { useShellHistory } from '../hooks/useShellHistory.js';
import { useCompletion } from '../hooks/useCompletion.js';
import { useKeypress, Key } from '../hooks/useKeypress.js';
import { isAtCommand, isSlashCommand } from '../utils/commandUtils.js';
import { SlashCommand } from '../hooks/slashCommandProcessor.js';
import { Config } from '@gemini-cli/core';
import { Config } from '@google/gemini-cli-core';
export interface InputPromptProps {
buffer: TextBuffer;
@@ -155,29 +156,29 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
],
);
useInput(
(input, key) => {
const handleInput = useCallback(
(key: Key) => {
if (!focus) {
return;
}
const query = buffer.text;
if (input === '!' && query === '' && !completion.showSuggestions) {
if (key.sequence === '!' && query === '' && !completion.showSuggestions) {
setShellModeActive(!shellModeActive);
buffer.setText(''); // Clear the '!' from input
return true;
}
if (completion.showSuggestions) {
if (key.upArrow) {
if (key.name === 'up') {
completion.navigateUp();
return;
}
if (key.downArrow) {
if (key.name === 'down') {
completion.navigateDown();
return;
}
if (key.tab) {
if (key.name === 'tab') {
if (completion.suggestions.length > 0) {
const targetIndex =
completion.activeSuggestionIndex === -1
@@ -189,7 +190,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
}
return;
}
if (key.return) {
if (key.name === 'return') {
if (completion.activeSuggestionIndex >= 0) {
handleAutocomplete(completion.activeSuggestionIndex);
} else if (query.trim()) {
@@ -199,19 +200,19 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
}
} else {
// Keybindings when suggestions are not shown
if (key.ctrl && input === 'l') {
if (key.ctrl && key.name === 'l') {
onClearScreen();
return true;
return;
}
if (key.ctrl && input === 'p') {
if (key.ctrl && key.name === 'p') {
inputHistory.navigateUp();
return true;
return;
}
if (key.ctrl && input === 'n') {
if (key.ctrl && key.name === 'n') {
inputHistory.navigateDown();
return true;
return;
}
if (key.escape) {
if (key.name === 'escape') {
if (shellModeActive) {
setShellModeActive(false);
return;
@@ -222,54 +223,55 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
}
// Ctrl+A (Home)
if (key.ctrl && input === 'a') {
if (key.ctrl && key.name === 'a') {
buffer.move('home');
buffer.moveToOffset(0);
return;
}
// Ctrl+E (End)
if (key.ctrl && input === 'e') {
if (key.ctrl && key.name === 'e') {
buffer.move('end');
buffer.moveToOffset(cpLen(buffer.text));
return;
}
// Ctrl+L (Clear Screen)
if (key.ctrl && input === 'l') {
if (key.ctrl && key.name === 'l') {
onClearScreen();
return;
}
// Ctrl+P (History Up)
if (key.ctrl && input === 'p' && !completion.showSuggestions) {
if (key.ctrl && key.name === 'p' && !completion.showSuggestions) {
inputHistory.navigateUp();
return;
}
// Ctrl+N (History Down)
if (key.ctrl && input === 'n' && !completion.showSuggestions) {
if (key.ctrl && key.name === 'n' && !completion.showSuggestions) {
inputHistory.navigateDown();
return;
}
// Core text editing from MultilineTextEditor's useInput
if (key.ctrl && input === 'k') {
if (key.ctrl && key.name === 'k') {
buffer.killLineRight();
return;
}
if (key.ctrl && input === 'u') {
if (key.ctrl && key.name === 'u') {
buffer.killLineLeft();
return;
}
const isCtrlX =
(key.ctrl && (input === 'x' || input === '\x18')) || input === '\x18';
(key.ctrl && (key.name === 'x' || key.sequence === '\x18')) ||
key.sequence === '\x18';
const isCtrlEFromEditor =
(key.ctrl && (input === 'e' || input === '\x05')) ||
input === '\x05' ||
(key.ctrl && (key.name === 'e' || key.sequence === '\x05')) ||
key.sequence === '\x05' ||
(!key.ctrl &&
input === 'e' &&
input.length === 1 &&
input.charCodeAt(0) === 5);
key.name === 'e' &&
key.sequence.length === 1 &&
key.sequence.charCodeAt(0) === 5);
if (isCtrlX || isCtrlEFromEditor) {
if (isCtrlEFromEditor && !(key.ctrl && input === 'e')) {
if (isCtrlEFromEditor && !(key.ctrl && key.name === 'e')) {
// Avoid double handling Ctrl+E
buffer.openInExternalEditor();
return;
@@ -284,16 +286,15 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
process.env['TEXTBUFFER_DEBUG'] === '1' ||
process.env['TEXTBUFFER_DEBUG'] === 'true'
) {
console.log('[InputPromptCombined] event', { input, key });
console.log('[InputPromptCombined] event', { key });
}
// Ctrl+Enter for newline, Enter for submit
if (key.return) {
if (key.name === 'return') {
const [row, col] = buffer.cursor;
const line = buffer.lines[row];
const charBefore = col > 0 ? cpSlice(line, col - 1, col) : '';
if (key.ctrl || charBefore === '\\') {
if (key.ctrl || key.meta || charBefore === '\\' || key.paste) {
// Ctrl+Enter or escaped newline
if (charBefore === '\\') {
buffer.backspace();
@@ -309,7 +310,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
}
// Standard arrow navigation within the buffer
if (key.upArrow && !completion.showSuggestions) {
if (key.name === 'up' && !completion.showSuggestions) {
if (shellModeActive) {
const prevCommand = shellHistory.getPreviousCommand();
if (prevCommand !== null) {
@@ -328,7 +329,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
}
return;
}
if (key.downArrow && !completion.showSuggestions) {
if (key.name === 'down' && !completion.showSuggestions) {
if (shellModeActive) {
const nextCommand = shellHistory.getNextCommand();
if (nextCommand !== null) {
@@ -349,13 +350,24 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
}
// Fallback to buffer's default input handling
buffer.handleInput(input, key as Record<string, boolean>);
},
{
isActive: focus,
buffer.handleInput(key);
},
[
focus,
buffer,
completion,
shellModeActive,
setShellModeActive,
onClearScreen,
inputHistory,
handleAutocomplete,
handleSubmitAndClear,
shellHistory,
],
);
useKeypress(handleInput, { isActive: focus });
const linesToRender = buffer.viewportVisualLines;
const [cursorVisualRowAbsolute, cursorVisualColAbsolute] =
buffer.visualCursor;
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { ThoughtSummary } from '@gemini-cli/core';
import { ThoughtSummary } from '@google/gemini-cli-core';
import React from 'react';
import { Box, Text } from 'ink';
import { Colors } from '../colors.js';
@@ -0,0 +1,40 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { Box, Text } from 'ink';
import { useOverflowState } from '../contexts/OverflowContext.js';
import { useStreamingContext } from '../contexts/StreamingContext.js';
import { StreamingState } from '../types.js';
import { Colors } from '../colors.js';
interface ShowMoreLinesProps {
constrainHeight: boolean;
}
export const ShowMoreLines = ({ constrainHeight }: ShowMoreLinesProps) => {
const overflowState = useOverflowState();
const streamingState = useStreamingContext();
if (
overflowState === undefined ||
overflowState.overflowingIds.size === 0 ||
!constrainHeight ||
!(
streamingState === StreamingState.Idle ||
streamingState === StreamingState.WaitingForConfirmation
)
) {
return null;
}
return (
<Box>
<Text color={Colors.Gray} wrap="truncate">
Press ctrl-s to show more lines
</Text>
</Box>
);
};
+7 -5
View File
@@ -76,11 +76,13 @@ export const StatsColumn: React.FC<{
label="Thoughts Tokens"
value={stats.thoughtsTokens.toLocaleString()}
/>
<StatRow
label="Cached Tokens"
value={cachedDisplay}
valueColor={cachedColor}
/>
{stats.cachedTokens > 0 && (
<StatRow
label="Cached Tokens"
value={cachedDisplay}
valueColor={cachedColor}
/>
)}
{/* Divider Line */}
<Box
borderTop={true}
+123 -74
View File
@@ -115,94 +115,143 @@ export function ThemeDialog({
1,
);
const DAILOG_PADDING = 2;
const selectThemeHeight = themeItems.length + 1;
const SCOPE_SELECTION_HEIGHT = 4; // Height for the scope selection section + margin.
const SPACE_BETWEEN_THEME_SELECTION_AND_APPLY_TO = 1;
const TAB_TO_SELECT_HEIGHT = 2;
availableTerminalHeight = availableTerminalHeight ?? Number.MAX_SAFE_INTEGER;
availableTerminalHeight -= 2; // Top and bottom borders.
availableTerminalHeight -= TAB_TO_SELECT_HEIGHT;
let totalLeftHandSideHeight =
DAILOG_PADDING +
selectThemeHeight +
SCOPE_SELECTION_HEIGHT +
SPACE_BETWEEN_THEME_SELECTION_AND_APPLY_TO;
let showScopeSelection = true;
let includePadding = true;
// Remove content from the LHS that can be omitted if it exceeds the available height.
if (totalLeftHandSideHeight > availableTerminalHeight) {
includePadding = false;
totalLeftHandSideHeight -= DAILOG_PADDING;
}
if (totalLeftHandSideHeight > availableTerminalHeight) {
// First, try hiding the scope selection
totalLeftHandSideHeight -= SCOPE_SELECTION_HEIGHT;
showScopeSelection = false;
}
// Don't focus the scope selection if it is hidden due to height constraints.
const currenFocusedSection = !showScopeSelection ? 'theme' : focusedSection;
// Vertical space taken by elements other than the two code blocks in the preview pane.
// Includes "Preview" title, borders, padding, and margin between blocks.
const PREVIEW_PANE_FIXED_VERTICAL_SPACE = 7;
const availableTerminalHeightCodeBlock = availableTerminalHeight
? Math.max(
Math.floor(
(availableTerminalHeight - PREVIEW_PANE_FIXED_VERTICAL_SPACE) / 2,
),
2,
)
: undefined;
// Includes "Preview" title, borders, and margin between blocks.
const PREVIEW_PANE_FIXED_VERTICAL_SPACE = 8;
// The right column doesn't need to ever be shorter than the left column.
availableTerminalHeight = Math.max(
availableTerminalHeight,
totalLeftHandSideHeight,
);
const availableTerminalHeightCodeBlock =
availableTerminalHeight -
PREVIEW_PANE_FIXED_VERTICAL_SPACE -
(includePadding ? 2 : 0) * 2;
// Give slightly more space to the code block as it is 3 lines longer.
const diffHeight = Math.floor(availableTerminalHeightCodeBlock / 2) - 1;
const codeBlockHeight = Math.ceil(availableTerminalHeightCodeBlock / 2) + 1;
return (
<Box
borderStyle="round"
borderColor={Colors.Gray}
flexDirection="row"
padding={1}
flexDirection="column"
paddingTop={includePadding ? 1 : 0}
paddingBottom={includePadding ? 1 : 0}
paddingLeft={1}
paddingRight={1}
width="100%"
>
{/* Left Column: Selection */}
<Box flexDirection="column" width="45%" paddingRight={2}>
<Text bold={focusedSection === 'theme'}>
{focusedSection === 'theme' ? '> ' : ' '}Select Theme{' '}
<Text color={Colors.Gray}>{otherScopeModifiedMessage}</Text>
</Text>
<RadioButtonSelect
key={selectInputKey}
items={themeItems}
initialIndex={initialThemeIndex}
onSelect={handleThemeSelect}
onHighlight={onHighlight}
isFocused={focusedSection === 'theme'}
/>
{/* Scope Selection */}
<Box marginTop={1} flexDirection="column">
<Text bold={focusedSection === 'scope'}>
{focusedSection === 'scope' ? '> ' : ' '}Apply To
<Box flexDirection="row">
{/* Left Column: Selection */}
<Box flexDirection="column" width="45%" paddingRight={2}>
<Text bold={currenFocusedSection === 'theme'} wrap="truncate">
{currenFocusedSection === 'theme' ? '> ' : ' '}Select Theme{' '}
<Text color={Colors.Gray}>{otherScopeModifiedMessage}</Text>
</Text>
<RadioButtonSelect
items={scopeItems}
initialIndex={0} // Default to User Settings
onSelect={handleScopeSelect}
onHighlight={handleScopeHighlight}
isFocused={focusedSection === 'scope'}
key={selectInputKey}
items={themeItems}
initialIndex={initialThemeIndex}
onSelect={handleThemeSelect}
onHighlight={onHighlight}
isFocused={currenFocusedSection === 'theme'}
/>
{/* Scope Selection */}
{showScopeSelection && (
<Box marginTop={1} flexDirection="column">
<Text bold={currenFocusedSection === 'scope'} wrap="truncate">
{currenFocusedSection === 'scope' ? '> ' : ' '}Apply To
</Text>
<RadioButtonSelect
items={scopeItems}
initialIndex={0} // Default to User Settings
onSelect={handleScopeSelect}
onHighlight={handleScopeHighlight}
isFocused={currenFocusedSection === 'scope'}
/>
</Box>
)}
</Box>
<Box marginTop={1}>
<Text color={Colors.Gray}>
(Use Enter to select, Tab to change focus)
</Text>
{/* Right Column: Preview */}
<Box flexDirection="column" width="55%" paddingLeft={2}>
<Text bold>Preview</Text>
<Box
borderStyle="single"
borderColor={Colors.Gray}
paddingTop={includePadding ? 1 : 0}
paddingBottom={includePadding ? 1 : 0}
paddingLeft={1}
paddingRight={1}
flexDirection="column"
>
{colorizeCode(
`# function
-def fibonacci(n):
- a, b = 0, 1
- for _ in range(n):
- a, b = b, a + b
- return a`,
'python',
codeBlockHeight,
colorizeCodeWidth,
)}
<Box marginTop={1} />
<DiffRenderer
diffContent={`--- a/old_file.txt
-+++ b/new_file.txt
-@@ -1,4 +1,5 @@
- This is a context line.
--This line was deleted.
-+This line was added.
-`}
availableTerminalHeight={diffHeight}
terminalWidth={colorizeCodeWidth}
/>
</Box>
</Box>
</Box>
{/* Right Column: Preview */}
<Box flexDirection="column" width="55%" paddingLeft={2}>
<Text bold>Preview</Text>
<Box
borderStyle="single"
borderColor={Colors.Gray}
padding={1}
flexDirection="column"
>
{colorizeCode(
`# function
def fibonacci(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a`,
'python',
availableTerminalHeightCodeBlock,
colorizeCodeWidth,
)}
<Box marginTop={1} />
<DiffRenderer
diffContent={`--- a/old_file.txt
+++ b/new_file.txt
@@ -1,4 +1,5 @@
This is a context line.
-This line was deleted.
+This line was added.
`}
availableTerminalHeight={availableTerminalHeightCodeBlock}
terminalWidth={colorizeCodeWidth}
/>
</Box>
<Box marginTop={1}>
<Text color={Colors.Gray} wrap="truncate">
(Use Enter to select
{showScopeSelection ? ', Tab to change focus' : ''})
</Text>
</Box>
</Box>
);
+11 -11
View File
@@ -7,7 +7,7 @@
import React from 'react';
import { Box, Text } from 'ink';
import { Colors } from '../colors.js';
import { type Config } from '@gemini-cli/core';
import { type Config } from '@google/gemini-cli-core';
interface TipsProps {
config: Config;
@@ -19,27 +19,27 @@ export const Tips: React.FC<TipsProps> = ({ config }) => {
<Box flexDirection="column" marginBottom={1}>
<Text color={Colors.Foreground}>Tips for getting started:</Text>
<Text color={Colors.Foreground}>
1.{' '}
<Text bold color={Colors.AccentPurple}>
/help
</Text>{' '}
for more information.
1. Ask questions, edit files, or run commands.
</Text>
<Text color={Colors.Foreground}>
2. Ask coding questions, edit code or run commands.
</Text>
<Text color={Colors.Foreground}>
3. Be specific for the best results.
2. Be specific for the best results.
</Text>
{geminiMdFileCount === 0 && (
<Text color={Colors.Foreground}>
4. Create{' '}
3. Create{' '}
<Text bold color={Colors.AccentPurple}>
GEMINI.md
</Text>{' '}
files to customize your interactions with Gemini.
</Text>
)}
<Text color={Colors.Foreground}>
{geminiMdFileCount === 0 ? '4.' : '3.'}{' '}
<Text bold color={Colors.AccentPurple}>
/help
</Text>{' '}
for more information.
</Text>
</Box>
);
};
@@ -33,7 +33,6 @@ exports[`<SessionSummaryDisplay /> > renders zero state correctly 1`] = `
│ Input Tokens 0 │
│ Output Tokens 0 │
│ Thoughts Tokens 0 │
│ Cached Tokens 0 │
│ ────────────────────────── │
│ Total Tokens 0 │
│ │
@@ -31,7 +31,6 @@ exports[`<StatsDisplay /> > renders zero state correctly 1`] = `
│ Input Tokens 0 Input Tokens 0 │
│ Output Tokens 0 Output Tokens 0 │
│ Thoughts Tokens 0 Thoughts Tokens 0 │
│ Cached Tokens 0 Cached Tokens 0 │
│ ───────────────────────────────────────────── ───────────────────────────────────────────── │
│ Total Tokens 0 Total Tokens 0 │
│ │
@@ -4,12 +4,13 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { OverflowProvider } from '../../contexts/OverflowContext.js';
import { render } from 'ink-testing-library';
import { DiffRenderer } from './DiffRenderer.js';
import * as CodeColorizer from '../../utils/CodeColorizer.js';
import { vi } from 'vitest';
describe('<DiffRenderer />', () => {
describe('<OverflowProvider><DiffRenderer /></OverflowProvider>', () => {
const mockColorizeCode = vi.spyOn(CodeColorizer, 'colorizeCode');
beforeEach(() => {
@@ -30,11 +31,13 @@ index 0000000..e69de29
+print("hello world")
`;
render(
<DiffRenderer
diffContent={newFileDiffContent}
filename="test.py"
terminalWidth={80}
/>,
<OverflowProvider>
<DiffRenderer
diffContent={newFileDiffContent}
filename="test.py"
terminalWidth={80}
/>
</OverflowProvider>,
);
expect(mockColorizeCode).toHaveBeenCalledWith(
'print("hello world")',
@@ -55,11 +58,13 @@ index 0000000..e69de29
+some content
`;
render(
<DiffRenderer
diffContent={newFileDiffContent}
filename="test.unknown"
terminalWidth={80}
/>,
<OverflowProvider>
<DiffRenderer
diffContent={newFileDiffContent}
filename="test.unknown"
terminalWidth={80}
/>
</OverflowProvider>,
);
expect(mockColorizeCode).toHaveBeenCalledWith(
'some content',
@@ -80,7 +85,9 @@ index 0000000..e69de29
+some text content
`;
render(
<DiffRenderer diffContent={newFileDiffContent} terminalWidth={80} />,
<OverflowProvider>
<DiffRenderer diffContent={newFileDiffContent} terminalWidth={80} />
</OverflowProvider>,
);
expect(mockColorizeCode).toHaveBeenCalledWith(
'some text content',
@@ -101,11 +108,13 @@ index 0000001..0000002 100644
+new line
`;
const { lastFrame } = render(
<DiffRenderer
diffContent={existingFileDiffContent}
filename="test.txt"
terminalWidth={80}
/>,
<OverflowProvider>
<DiffRenderer
diffContent={existingFileDiffContent}
filename="test.txt"
terminalWidth={80}
/>
</OverflowProvider>,
);
// colorizeCode is used internally by the line-by-line rendering, not for the whole block
expect(mockColorizeCode).not.toHaveBeenCalledWith(
@@ -129,11 +138,13 @@ index 1234567..1234567 100644
+++ b/file.txt
`;
const { lastFrame } = render(
<DiffRenderer
diffContent={noChangeDiff}
filename="file.txt"
terminalWidth={80}
/>,
<OverflowProvider>
<DiffRenderer
diffContent={noChangeDiff}
filename="file.txt"
terminalWidth={80}
/>
</OverflowProvider>,
);
expect(lastFrame()).toContain('No changes detected');
expect(mockColorizeCode).not.toHaveBeenCalled();
@@ -141,7 +152,9 @@ index 1234567..1234567 100644
it('should handle empty diff content', () => {
const { lastFrame } = render(
<DiffRenderer diffContent="" terminalWidth={80} />,
<OverflowProvider>
<DiffRenderer diffContent="" terminalWidth={80} />
</OverflowProvider>,
);
expect(lastFrame()).toContain('No diff content');
expect(mockColorizeCode).not.toHaveBeenCalled();
@@ -162,11 +175,13 @@ index 123..456 100644
context line 11
`;
const { lastFrame } = render(
<DiffRenderer
diffContent={diffWithGap}
filename="file.txt"
terminalWidth={80}
/>,
<OverflowProvider>
<DiffRenderer
diffContent={diffWithGap}
filename="file.txt"
terminalWidth={80}
/>
</OverflowProvider>,
);
const output = lastFrame();
expect(output).toContain('═'); // Check for the border character used in the gap
@@ -197,11 +212,13 @@ index abc..def 100644
context line 15
`;
const { lastFrame } = render(
<DiffRenderer
diffContent={diffWithSmallGap}
filename="file.txt"
terminalWidth={80}
/>,
<OverflowProvider>
<DiffRenderer
diffContent={diffWithSmallGap}
filename="file.txt"
terminalWidth={80}
/>
</OverflowProvider>,
);
const output = lastFrame();
expect(output).not.toContain('═'); // Ensure no separator is rendered
@@ -267,12 +284,14 @@ index 123..789 100644
'with terminalWidth $terminalWidth and height $height',
({ terminalWidth, height, expected }) => {
const { lastFrame } = render(
<DiffRenderer
diffContent={diffWithMultipleHunks}
filename="multi.js"
terminalWidth={terminalWidth}
availableTerminalHeight={height}
/>,
<OverflowProvider>
<DiffRenderer
diffContent={diffWithMultipleHunks}
filename="multi.js"
terminalWidth={terminalWidth}
availableTerminalHeight={height}
/>
</OverflowProvider>,
);
const output = lastFrame();
expect(sanitizeOutput(output, terminalWidth)).toEqual(expected);
@@ -297,11 +316,13 @@ fileDiff Index: file.txt
\\ No newline at end of file
`;
const { lastFrame } = render(
<DiffRenderer
diffContent={newFileDiff}
filename="TEST"
terminalWidth={80}
/>,
<OverflowProvider>
<DiffRenderer
diffContent={newFileDiff}
filename="TEST"
terminalWidth={80}
/>
</OverflowProvider>,
);
const output = lastFrame();
@@ -325,11 +346,13 @@ fileDiff Index: Dockerfile
\\ No newline at end of file
`;
const { lastFrame } = render(
<DiffRenderer
diffContent={newFileDiff}
filename="Dockerfile"
terminalWidth={80}
/>,
<OverflowProvider>
<DiffRenderer
diffContent={newFileDiff}
filename="Dockerfile"
terminalWidth={80}
/>
</OverflowProvider>,
);
const output = lastFrame();
expect(output).toEqual(`1 FROM node:14
@@ -7,7 +7,7 @@
import { render } from 'ink-testing-library';
import { describe, it, expect, vi } from 'vitest';
import { ToolConfirmationMessage } from './ToolConfirmationMessage.js';
import { ToolCallConfirmationDetails } from '@gemini-cli/core';
import { ToolCallConfirmationDetails } from '@google/gemini-cli-core';
describe('ToolConfirmationMessage', () => {
it('should not display urls if prompt and url are the same', () => {
@@ -14,7 +14,7 @@ import {
ToolExecuteConfirmationDetails,
ToolMcpConfirmationDetails,
Config,
} from '@gemini-cli/core';
} from '@google/gemini-cli-core';
import {
RadioButtonSelect,
RadioSelectItem,
@@ -10,7 +10,7 @@ import { IndividualToolCallDisplay, ToolCallStatus } from '../../types.js';
import { ToolMessage } from './ToolMessage.js';
import { ToolConfirmationMessage } from './ToolConfirmationMessage.js';
import { Colors } from '../../colors.js';
import { Config } from '@gemini-cli/core';
import { Config } from '@google/gemini-cli-core';
interface ToolGroupMessageProps {
groupId: number;
@@ -77,7 +77,6 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
marginLeft={1}
borderDimColor={hasPending}
borderColor={borderColor}
marginBottom={1}
>
{toolCalls.map((tool) => {
const isConfirming = toolAwaitingApproval?.callId === tool.callId;
@@ -18,6 +18,9 @@ const RESERVED_LINE_COUNT = 5; // for tool name, status, padding etc.
const STATUS_INDICATOR_WIDTH = 3;
const MIN_LINES_SHOWN = 2; // show at least this many lines
// Large threshold to ensure we don't cause performance issues for very large
// outputs that will get truncated further MaxSizedBox anyway.
const MAXIMUM_RESULT_DISPLAY_CHARACTERS = 1000000;
export type TextEmphasis = 'high' | 'medium' | 'low';
export interface ToolMessageProps extends IndividualToolCallDisplay {
@@ -44,8 +47,21 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
)
: undefined;
const childWidth = terminalWidth - 3; // account for padding.
// Long tool call response in MarkdownDisplay doesn't respect availableTerminalHeight properly,
// we're forcing it to not render as markdown when the response is too long, it will fallback
// to render as plain text, which is contained within the terminal using MaxSizedBox
if (availableHeight) {
renderOutputAsMarkdown = false;
}
const childWidth = terminalWidth - 3; // account for padding.
if (typeof resultDisplay === 'string') {
if (resultDisplay.length > MAXIMUM_RESULT_DISPLAY_CHARACTERS) {
// Truncate the result display to fit within the available width.
resultDisplay =
'...' + resultDisplay.slice(-MAXIMUM_RESULT_DISPLAY_CHARACTERS);
}
}
return (
<Box paddingX={1} paddingY={0} flexDirection="column">
<Box minHeight={1}>
@@ -5,6 +5,7 @@
*/
import { render } from 'ink-testing-library';
import { OverflowProvider } from '../../contexts/OverflowContext.js';
import { MaxSizedBox, setMaxSizedBoxDebugging } from './MaxSizedBox.js';
import { Box, Text } from 'ink';
import { describe, it, expect } from 'vitest';
@@ -18,28 +19,32 @@ describe('<MaxSizedBox />', () => {
it('renders children without truncation when they fit', () => {
const { lastFrame } = render(
<MaxSizedBox maxWidth={80} maxHeight={10}>
<Box>
<Text>Hello, World!</Text>
</Box>
</MaxSizedBox>,
<OverflowProvider>
<MaxSizedBox maxWidth={80} maxHeight={10}>
<Box>
<Text>Hello, World!</Text>
</Box>
</MaxSizedBox>
</OverflowProvider>,
);
expect(lastFrame()).equals('Hello, World!');
});
it('hides lines when content exceeds maxHeight', () => {
const { lastFrame } = render(
<MaxSizedBox maxWidth={80} maxHeight={2}>
<Box>
<Text>Line 1</Text>
</Box>
<Box>
<Text>Line 2</Text>
</Box>
<Box>
<Text>Line 3</Text>
</Box>
</MaxSizedBox>,
<OverflowProvider>
<MaxSizedBox maxWidth={80} maxHeight={2}>
<Box>
<Text>Line 1</Text>
</Box>
<Box>
<Text>Line 2</Text>
</Box>
<Box>
<Text>Line 3</Text>
</Box>
</MaxSizedBox>
</OverflowProvider>,
);
expect(lastFrame()).equals(`... first 2 lines hidden ...
Line 3`);
@@ -47,17 +52,19 @@ Line 3`);
it('hides lines at the end when content exceeds maxHeight and overflowDirection is bottom', () => {
const { lastFrame } = render(
<MaxSizedBox maxWidth={80} maxHeight={2} overflowDirection="bottom">
<Box>
<Text>Line 1</Text>
</Box>
<Box>
<Text>Line 2</Text>
</Box>
<Box>
<Text>Line 3</Text>
</Box>
</MaxSizedBox>,
<OverflowProvider>
<MaxSizedBox maxWidth={80} maxHeight={2} overflowDirection="bottom">
<Box>
<Text>Line 1</Text>
</Box>
<Box>
<Text>Line 2</Text>
</Box>
<Box>
<Text>Line 3</Text>
</Box>
</MaxSizedBox>
</OverflowProvider>,
);
expect(lastFrame()).equals(`Line 1
... last 2 lines hidden ...`);
@@ -65,11 +72,13 @@ Line 3`);
it('wraps text that exceeds maxWidth', () => {
const { lastFrame } = render(
<MaxSizedBox maxWidth={10} maxHeight={5}>
<Box>
<Text wrap="wrap">This is a long line of text</Text>
</Box>
</MaxSizedBox>,
<OverflowProvider>
<MaxSizedBox maxWidth={10} maxHeight={5}>
<Box>
<Text wrap="wrap">This is a long line of text</Text>
</Box>
</MaxSizedBox>
</OverflowProvider>,
);
expect(lastFrame()).equals(`This is a
@@ -82,19 +91,21 @@ of text`);
And has a line break.
Leading spaces preserved.`;
const { lastFrame } = render(
<MaxSizedBox maxWidth={20} maxHeight={20}>
<Box>
<Text>Example</Text>
</Box>
<Box>
<Text>No Wrap: </Text>
<Text wrap="wrap">{multilineText}</Text>
</Box>
<Box>
<Text>Longer No Wrap: </Text>
<Text wrap="wrap">This part will wrap around.</Text>
</Box>
</MaxSizedBox>,
<OverflowProvider>
<MaxSizedBox maxWidth={20} maxHeight={20}>
<Box>
<Text>Example</Text>
</Box>
<Box>
<Text>No Wrap: </Text>
<Text wrap="wrap">{multilineText}</Text>
</Box>
<Box>
<Text>Longer No Wrap: </Text>
<Text wrap="wrap">This part will wrap around.</Text>
</Box>
</MaxSizedBox>
</OverflowProvider>,
);
expect(lastFrame()).equals(
@@ -118,11 +129,13 @@ Longer No Wrap: This
it('handles words longer than maxWidth by splitting them', () => {
const { lastFrame } = render(
<MaxSizedBox maxWidth={5} maxHeight={5}>
<Box>
<Text wrap="wrap">Supercalifragilisticexpialidocious</Text>
</Box>
</MaxSizedBox>,
<OverflowProvider>
<MaxSizedBox maxWidth={5} maxHeight={5}>
<Box>
<Text wrap="wrap">Supercalifragilisticexpialidocious</Text>
</Box>
</MaxSizedBox>
</OverflowProvider>,
);
expect(lastFrame()).equals(`... …
@@ -134,14 +147,16 @@ ious`);
it('does not truncate when maxHeight is undefined', () => {
const { lastFrame } = render(
<MaxSizedBox maxWidth={80} maxHeight={undefined}>
<Box>
<Text>Line 1</Text>
</Box>
<Box>
<Text>Line 2</Text>
</Box>
</MaxSizedBox>,
<OverflowProvider>
<MaxSizedBox maxWidth={80} maxHeight={undefined}>
<Box>
<Text>Line 1</Text>
</Box>
<Box>
<Text>Line 2</Text>
</Box>
</MaxSizedBox>
</OverflowProvider>,
);
expect(lastFrame()).equals(`Line 1
Line 2`);
@@ -149,17 +164,19 @@ Line 2`);
it('shows plural "lines" when more than one line is hidden', () => {
const { lastFrame } = render(
<MaxSizedBox maxWidth={80} maxHeight={2}>
<Box>
<Text>Line 1</Text>
</Box>
<Box>
<Text>Line 2</Text>
</Box>
<Box>
<Text>Line 3</Text>
</Box>
</MaxSizedBox>,
<OverflowProvider>
<MaxSizedBox maxWidth={80} maxHeight={2}>
<Box>
<Text>Line 1</Text>
</Box>
<Box>
<Text>Line 2</Text>
</Box>
<Box>
<Text>Line 3</Text>
</Box>
</MaxSizedBox>
</OverflowProvider>,
);
expect(lastFrame()).equals(`... first 2 lines hidden ...
Line 3`);
@@ -167,17 +184,19 @@ Line 3`);
it('shows plural "lines" when more than one line is hidden and overflowDirection is bottom', () => {
const { lastFrame } = render(
<MaxSizedBox maxWidth={80} maxHeight={2} overflowDirection="bottom">
<Box>
<Text>Line 1</Text>
</Box>
<Box>
<Text>Line 2</Text>
</Box>
<Box>
<Text>Line 3</Text>
</Box>
</MaxSizedBox>,
<OverflowProvider>
<MaxSizedBox maxWidth={80} maxHeight={2} overflowDirection="bottom">
<Box>
<Text>Line 1</Text>
</Box>
<Box>
<Text>Line 2</Text>
</Box>
<Box>
<Text>Line 3</Text>
</Box>
</MaxSizedBox>
</OverflowProvider>,
);
expect(lastFrame()).equals(`Line 1
... last 2 lines hidden ...`);
@@ -185,7 +204,9 @@ Line 3`);
it('renders an empty box for empty children', () => {
const { lastFrame } = render(
<MaxSizedBox maxWidth={80} maxHeight={10}></MaxSizedBox>,
<OverflowProvider>
<MaxSizedBox maxWidth={80} maxHeight={10}></MaxSizedBox>
</OverflowProvider>,
);
// Expect an empty string or a box with nothing in it.
// Ink renders an empty box as an empty string.
@@ -194,11 +215,13 @@ Line 3`);
it('wraps text with multi-byte unicode characters correctly', () => {
const { lastFrame } = render(
<MaxSizedBox maxWidth={5} maxHeight={5}>
<Box>
<Text wrap="wrap"></Text>
</Box>
</MaxSizedBox>,
<OverflowProvider>
<MaxSizedBox maxWidth={5} maxHeight={5}>
<Box>
<Text wrap="wrap"></Text>
</Box>
</MaxSizedBox>
</OverflowProvider>,
);
// "你好" has a visual width of 4. "世界" has a visual width of 4.
@@ -209,11 +232,13 @@ Line 3`);
it('wraps text with multi-byte emoji characters correctly', () => {
const { lastFrame } = render(
<MaxSizedBox maxWidth={5} maxHeight={5}>
<Box>
<Text wrap="wrap">🐶🐶🐶🐶🐶</Text>
</Box>
</MaxSizedBox>,
<OverflowProvider>
<MaxSizedBox maxWidth={5} maxHeight={5}>
<Box>
<Text wrap="wrap">🐶🐶🐶🐶🐶</Text>
</Box>
</MaxSizedBox>
</OverflowProvider>,
);
// Each "🐶" has a visual width of 2.
@@ -225,17 +250,19 @@ Line 3`);
it('accounts for additionalHiddenLinesCount', () => {
const { lastFrame } = render(
<MaxSizedBox maxWidth={80} maxHeight={2} additionalHiddenLinesCount={5}>
<Box>
<Text>Line 1</Text>
</Box>
<Box>
<Text>Line 2</Text>
</Box>
<Box>
<Text>Line 3</Text>
</Box>
</MaxSizedBox>,
<OverflowProvider>
<MaxSizedBox maxWidth={80} maxHeight={2} additionalHiddenLinesCount={5}>
<Box>
<Text>Line 1</Text>
</Box>
<Box>
<Text>Line 2</Text>
</Box>
<Box>
<Text>Line 3</Text>
</Box>
</MaxSizedBox>
</OverflowProvider>,
);
// 1 line is hidden by overflow, 5 are additionally hidden.
expect(lastFrame()).equals(`... first 7 lines hidden ...
@@ -244,19 +271,21 @@ Line 3`);
it('handles React.Fragment as a child', () => {
const { lastFrame } = render(
<MaxSizedBox maxWidth={80} maxHeight={10}>
<>
<OverflowProvider>
<MaxSizedBox maxWidth={80} maxHeight={10}>
<>
<Box>
<Text>Line 1 from Fragment</Text>
</Box>
<Box>
<Text>Line 2 from Fragment</Text>
</Box>
</>
<Box>
<Text>Line 1 from Fragment</Text>
<Text>Line 3 direct child</Text>
</Box>
<Box>
<Text>Line 2 from Fragment</Text>
</Box>
</>
<Box>
<Text>Line 3 direct child</Text>
</Box>
</MaxSizedBox>,
</MaxSizedBox>
</OverflowProvider>,
);
expect(lastFrame()).equals(`Line 1 from Fragment
Line 2 from Fragment
@@ -270,11 +299,13 @@ Line 3 direct child`);
).join('\n');
const { lastFrame } = render(
<MaxSizedBox maxWidth={80} maxHeight={10}>
<Box>
<Text>{THIRTY_LINES}</Text>
</Box>
</MaxSizedBox>,
<OverflowProvider>
<MaxSizedBox maxWidth={80} maxHeight={10}>
<Box>
<Text>{THIRTY_LINES}</Text>
</Box>
</MaxSizedBox>
</OverflowProvider>,
);
const expected = [
@@ -292,11 +323,13 @@ Line 3 direct child`);
).join('\n');
const { lastFrame } = render(
<MaxSizedBox maxWidth={80} maxHeight={10} overflowDirection="bottom">
<Box>
<Text>{THIRTY_LINES}</Text>
</Box>
</MaxSizedBox>,
<OverflowProvider>
<MaxSizedBox maxWidth={80} maxHeight={10} overflowDirection="bottom">
<Box>
<Text>{THIRTY_LINES}</Text>
</Box>
</MaxSizedBox>
</OverflowProvider>,
);
const expected = [
@@ -4,14 +4,22 @@
* SPDX-License-Identifier: Apache-2.0
*/
import React, { Fragment } from 'react';
import React, { Fragment, useEffect, useId } from 'react';
import { Box, Text } from 'ink';
import stringWidth from 'string-width';
import { Colors } from '../../colors.js';
import { toCodePoints } from '../../utils/textUtils.js';
import { useOverflowActions } from '../../contexts/OverflowContext.js';
let enableDebugLog = false;
/**
* Minimum height for the MaxSizedBox component.
* This ensures there is room for at least one line of content as well as the
* message that content was truncated.
*/
export const MINIMUM_MAX_HEIGHT = 2;
export function setMaxSizedBoxDebugging(value: boolean) {
enableDebugLog = value;
}
@@ -95,27 +103,18 @@ export const MaxSizedBox: React.FC<MaxSizedBoxProps> = ({
overflowDirection = 'top',
additionalHiddenLinesCount = 0,
}) => {
// When maxHeight is not set, we render the content normally rather
// than using our custom layout logic. This should slightly improve
// performance for the case where there is no height limit and is
// a useful debugging tool to ensure that our layouts are consist
// with the expected layout when there is no height limit.
// In the future we might choose to still apply our layout logic
// even in this case particularlly if there are cases where we
// intentionally diverse how certain layouts are rendered.
if (maxHeight === undefined) {
return (
<Box width={maxWidth} height={maxHeight} flexDirection="column">
{children}
</Box>
);
}
const id = useId();
const { addOverflowingId, removeOverflowingId } = useOverflowActions() || {};
const laidOutStyledText: StyledText[][] = [];
const targetMaxHeight = Math.max(
Math.round(maxHeight ?? Number.MAX_SAFE_INTEGER),
MINIMUM_MAX_HEIGHT,
);
if (maxWidth === undefined) {
throw new Error('maxWidth must be defined when maxHeight is set.');
}
const laidOutStyledText: StyledText[][] = [];
function visitRows(element: React.ReactNode) {
if (!React.isValidElement(element)) {
return;
@@ -135,22 +134,36 @@ export const MaxSizedBox: React.FC<MaxSizedBoxProps> = ({
React.Children.forEach(children, visitRows);
const contentWillOverflow =
(laidOutStyledText.length > maxHeight && maxHeight > 0) ||
(targetMaxHeight !== undefined &&
laidOutStyledText.length > targetMaxHeight) ||
additionalHiddenLinesCount > 0;
const visibleContentHeight = contentWillOverflow ? maxHeight - 1 : maxHeight;
const visibleContentHeight =
contentWillOverflow && targetMaxHeight !== undefined
? targetMaxHeight - 1
: targetMaxHeight;
const hiddenLinesCount = Math.max(
0,
laidOutStyledText.length - visibleContentHeight,
);
const hiddenLinesCount =
visibleContentHeight !== undefined
? Math.max(0, laidOutStyledText.length - visibleContentHeight)
: 0;
const totalHiddenLines = hiddenLinesCount + additionalHiddenLinesCount;
useEffect(() => {
if (totalHiddenLines > 0) {
addOverflowingId?.(id);
} else {
removeOverflowingId?.(id);
}
return () => {
removeOverflowingId?.(id);
};
}, [id, totalHiddenLines, addOverflowingId, removeOverflowingId]);
const visibleStyledText =
hiddenLinesCount > 0
? overflowDirection === 'top'
? laidOutStyledText.slice(
laidOutStyledText.length - visibleContentHeight,
)
? laidOutStyledText.slice(hiddenLinesCount, laidOutStyledText.length)
: laidOutStyledText.slice(0, visibleContentHeight)
: laidOutStyledText;
@@ -395,7 +408,9 @@ function layoutInkElementAsStyledText(
) {
lines.push(currentLine);
}
output.push(...lines);
for (const line of lines) {
output.push(line);
}
return;
}
@@ -511,5 +526,7 @@ function layoutInkElementAsStyledText(
if (wrappingPart.length > 0) {
addWrappingPartToLines();
}
output.push(...lines);
for (const line of lines) {
output.push(line);
}
}
@@ -5,7 +5,7 @@
*/
import React from 'react';
import { Text } from 'ink';
import { Text, Box } from 'ink';
import SelectInput, {
type ItemProps as InkSelectItemProps,
type IndicatorProps as InkSelectIndicatorProps,
@@ -78,12 +78,11 @@ export function RadioButtonSelect<T>({
isSelected = false,
}: InkSelectIndicatorProps): React.JSX.Element {
return (
<Text
color={isSelected ? Colors.AccentGreen : Colors.Foreground}
wrap="truncate"
>
{isSelected ? '● ' : '○ '}
</Text>
<Box minWidth={2} flexShrink={0}>
<Text color={isSelected ? Colors.AccentGreen : Colors.Foreground}>
{isSelected ? '●' : '○'}
</Text>
</Box>
);
}
@@ -574,8 +574,24 @@ describe('useTextBuffer', () => {
const { result } = renderHook(() =>
useTextBuffer({ viewport, isValidPath: () => false }),
);
act(() => result.current.handleInput('h', {}));
act(() => result.current.handleInput('i', {}));
act(() =>
result.current.handleInput({
name: 'h',
ctrl: false,
meta: false,
shift: false,
sequence: 'h',
}),
);
act(() =>
result.current.handleInput({
name: 'i',
ctrl: false,
meta: false,
shift: false,
sequence: 'i',
}),
);
expect(getBufferState(result).text).toBe('hi');
});
@@ -583,7 +599,15 @@ describe('useTextBuffer', () => {
const { result } = renderHook(() =>
useTextBuffer({ viewport, isValidPath: () => false }),
);
act(() => result.current.handleInput(undefined, { return: true }));
act(() =>
result.current.handleInput({
name: 'return',
ctrl: false,
meta: false,
shift: false,
sequence: '\r',
}),
);
expect(getBufferState(result).lines).toEqual(['', '']);
});
@@ -596,7 +620,15 @@ describe('useTextBuffer', () => {
}),
);
act(() => result.current.move('end'));
act(() => result.current.handleInput(undefined, { backspace: true }));
act(() =>
result.current.handleInput({
name: 'backspace',
ctrl: false,
meta: false,
shift: false,
sequence: '\x7f',
}),
);
expect(getBufferState(result).text).toBe('');
});
@@ -671,9 +703,25 @@ describe('useTextBuffer', () => {
}),
);
act(() => result.current.move('end')); // cursor [0,2]
act(() => result.current.handleInput(undefined, { leftArrow: true })); // cursor [0,1]
act(() =>
result.current.handleInput({
name: 'left',
ctrl: false,
meta: false,
shift: false,
sequence: '\x1b[D',
}),
); // cursor [0,1]
expect(getBufferState(result).cursor).toEqual([0, 1]);
act(() => result.current.handleInput(undefined, { rightArrow: true })); // cursor [0,2]
act(() =>
result.current.handleInput({
name: 'right',
ctrl: false,
meta: false,
shift: false,
sequence: '\x1b[C',
}),
); // cursor [0,2]
expect(getBufferState(result).cursor).toEqual([0, 2]);
});
@@ -683,7 +731,15 @@ describe('useTextBuffer', () => {
);
const textWithAnsi = '\x1B[31mHello\x1B[0m \x1B[32mWorld\x1B[0m';
// Simulate pasting by calling handleInput with a string longer than 1 char
act(() => result.current.handleInput(textWithAnsi, {}));
act(() =>
result.current.handleInput({
name: undefined,
ctrl: false,
meta: false,
shift: false,
sequence: textWithAnsi,
}),
);
expect(getBufferState(result).text).toBe('Hello World');
});
@@ -691,7 +747,15 @@ describe('useTextBuffer', () => {
const { result } = renderHook(() =>
useTextBuffer({ viewport, isValidPath: () => false }),
);
act(() => result.current.handleInput('\r', {})); // Simulates Shift+Enter in VSCode terminal
act(() =>
result.current.handleInput({
name: 'return',
ctrl: false,
meta: false,
shift: true,
sequence: '\r',
}),
); // Simulates Shift+Enter in VSCode terminal
expect(getBufferState(result).lines).toEqual(['', '']);
});
@@ -880,7 +944,15 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
useTextBuffer({ viewport, isValidPath: () => false }),
);
const textWithAnsi = '\x1B[31mHello\x1B[0m';
act(() => result.current.handleInput(textWithAnsi, {}));
act(() =>
result.current.handleInput({
name: undefined,
ctrl: false,
meta: false,
shift: false,
sequence: textWithAnsi,
}),
);
expect(getBufferState(result).text).toBe('Hello');
});
@@ -889,7 +961,15 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
useTextBuffer({ viewport, isValidPath: () => false }),
);
const textWithControlChars = 'H\x07e\x08l\x0Bl\x0Co'; // BELL, BACKSPACE, VT, FF
act(() => result.current.handleInput(textWithControlChars, {}));
act(() =>
result.current.handleInput({
name: undefined,
ctrl: false,
meta: false,
shift: false,
sequence: textWithControlChars,
}),
);
expect(getBufferState(result).text).toBe('Hello');
});
@@ -898,7 +978,15 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
useTextBuffer({ viewport, isValidPath: () => false }),
);
const textWithMixed = '\u001B[4mH\u001B[0mello';
act(() => result.current.handleInput(textWithMixed, {}));
act(() =>
result.current.handleInput({
name: undefined,
ctrl: false,
meta: false,
shift: false,
sequence: textWithMixed,
}),
);
expect(getBufferState(result).text).toBe('Hello');
});
@@ -907,7 +995,15 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
useTextBuffer({ viewport, isValidPath: () => false }),
);
const validText = 'Hello World\nThis is a test.';
act(() => result.current.handleInput(validText, {}));
act(() =>
result.current.handleInput({
name: undefined,
ctrl: false,
meta: false,
shift: false,
sequence: validText,
}),
);
expect(getBufferState(result).text).toBe(validText);
});
@@ -916,7 +1012,15 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
useTextBuffer({ viewport, isValidPath: () => false }),
);
const pastedText = '\u001B[4mPasted\u001B[4m Text';
act(() => result.current.handleInput(pastedText, {}));
act(() =>
result.current.handleInput({
name: undefined,
ctrl: false,
meta: false,
shift: false,
sequence: pastedText,
}),
);
expect(getBufferState(result).text).toBe('Pasted Text');
});
});
@@ -11,7 +11,7 @@ import os from 'os';
import pathMod from 'path';
import { useState, useCallback, useEffect, useMemo } from 'react';
import stringWidth from 'string-width';
import { unescapePath } from '@gemini-cli/core';
import { unescapePath } from '@google/gemini-cli-core';
import { toCodePoints, cpLen, cpSlice } from '../../utils/textUtils.js';
export type Direction =
@@ -1220,9 +1220,16 @@ export function useTextBuffer({
);
const handleInput = useCallback(
(input: string | undefined, key: Record<string, boolean>): boolean => {
(key: {
name: string;
ctrl: boolean;
meta: boolean;
shift: boolean;
paste: boolean;
sequence: string;
}): boolean => {
const { sequence: input } = key;
dbg('handleInput', {
input,
key,
cursor: [cursorRow, cursorCol],
visualCursor,
@@ -1231,50 +1238,46 @@ export function useTextBuffer({
const beforeLogicalCursor = [cursorRow, cursorCol];
const beforeVisualCursor = [...visualCursor];
if (key['escape']) return false;
if (key.name === 'escape') return false;
if (
key['return'] ||
key.name === 'return' ||
input === '\r' ||
input === '\n' ||
input === '\\\r' // VSCode terminal represents shift + enter this way
)
newline();
else if (key['leftArrow'] && !key['meta'] && !key['ctrl'] && !key['alt'])
move('left');
else if (key['ctrl'] && input === 'b') move('left');
else if (key['rightArrow'] && !key['meta'] && !key['ctrl'] && !key['alt'])
move('right');
else if (key['ctrl'] && input === 'f') move('right');
else if (key['upArrow']) move('up');
else if (key['downArrow']) move('down');
else if ((key['ctrl'] || key['alt']) && key['leftArrow'])
move('wordLeft');
else if (key['meta'] && input === 'b') move('wordLeft');
else if ((key['ctrl'] || key['alt']) && key['rightArrow'])
else if (key.name === 'left' && !key.meta && !key.ctrl) move('left');
else if (key.ctrl && key.name === 'b') move('left');
else if (key.name === 'right' && !key.meta && !key.ctrl) move('right');
else if (key.ctrl && key.name === 'f') move('right');
else if (key.name === 'up') move('up');
else if (key.name === 'down') move('down');
else if ((key.ctrl || key.meta) && key.name === 'left') move('wordLeft');
else if (key.meta && key.name === 'b') move('wordLeft');
else if ((key.ctrl || key.meta) && key.name === 'right')
move('wordRight');
else if (key['meta'] && input === 'f') move('wordRight');
else if (key['home']) move('home');
else if (key['ctrl'] && input === 'a') move('home');
else if (key['end']) move('end');
else if (key['ctrl'] && input === 'e') move('end');
else if (key['ctrl'] && input === 'w') deleteWordLeft();
else if (key.meta && key.name === 'f') move('wordRight');
else if (key.name === 'home') move('home');
else if (key.ctrl && key.name === 'a') move('home');
else if (key.name === 'end') move('end');
else if (key.ctrl && key.name === 'e') move('end');
else if (key.ctrl && key.name === 'w') deleteWordLeft();
else if (
(key['meta'] || key['ctrl'] || key['alt']) &&
(key['backspace'] || input === '\x7f')
(key.meta || key.ctrl) &&
(key.name === 'backspace' || input === '\x7f')
)
deleteWordLeft();
else if ((key['meta'] || key['ctrl'] || key['alt']) && key['delete'])
else if ((key.meta || key.ctrl) && key.name === 'delete')
deleteWordRight();
else if (
key['backspace'] ||
key.name === 'backspace' ||
input === '\x7f' ||
(key['ctrl'] && input === 'h') ||
(key['delete'] && !key['shift'])
(key.ctrl && key.name === 'h')
)
backspace();
else if (key['delete'] || (key['ctrl'] && input === 'd')) del();
else if (input && !key['ctrl'] && !key['meta']) {
else if (key.name === 'delete' || (key.ctrl && key.name === 'd')) del();
else if (input && !key.ctrl && !key.meta) {
insert(input);
}
@@ -1483,10 +1486,14 @@ export interface TextBuffer {
/**
* High level "handleInput" receives what Ink gives us.
*/
handleInput: (
input: string | undefined,
key: Record<string, boolean>,
) => boolean;
handleInput: (key: {
name: string;
ctrl: boolean;
meta: boolean;
shift: boolean;
paste: boolean;
sequence: string;
}) => boolean;
/**
* Opens the current buffer contents in the user's preferred terminal text
* editor ($VISUAL or $EDITOR, falling back to "vi"). The method blocks
@@ -0,0 +1,87 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import React, {
createContext,
useContext,
useState,
useCallback,
useMemo,
} from 'react';
interface OverflowState {
overflowingIds: ReadonlySet<string>;
}
interface OverflowActions {
addOverflowingId: (id: string) => void;
removeOverflowingId: (id: string) => void;
}
const OverflowStateContext = createContext<OverflowState | undefined>(
undefined,
);
const OverflowActionsContext = createContext<OverflowActions | undefined>(
undefined,
);
export const useOverflowState = (): OverflowState | undefined =>
useContext(OverflowStateContext);
export const useOverflowActions = (): OverflowActions | undefined =>
useContext(OverflowActionsContext);
export const OverflowProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [overflowingIds, setOverflowingIds] = useState(new Set<string>());
const addOverflowingId = useCallback((id: string) => {
setOverflowingIds((prevIds) => {
if (prevIds.has(id)) {
return prevIds;
}
const newIds = new Set(prevIds);
newIds.add(id);
return newIds;
});
}, []);
const removeOverflowingId = useCallback((id: string) => {
setOverflowingIds((prevIds) => {
if (!prevIds.has(id)) {
return prevIds;
}
const newIds = new Set(prevIds);
newIds.delete(id);
return newIds;
});
}, []);
const stateValue = useMemo(
() => ({
overflowingIds,
}),
[overflowingIds],
);
const actionsValue = useMemo(
() => ({
addOverflowingId,
removeOverflowingId,
}),
[addOverflowingId, removeOverflowingId],
);
return (
<OverflowStateContext.Provider value={stateValue}>
<OverflowActionsContext.Provider value={actionsValue}>
{children}
</OverflowActionsContext.Provider>
</OverflowStateContext.Provider>
);
};
@@ -8,7 +8,7 @@ import {
allowEditorTypeInSandbox,
checkHasEditorType,
type EditorType,
} from '@gemini-cli/core';
} from '@google/gemini-cli-core';
export interface EditorDisplay {
name: string;
@@ -17,6 +17,7 @@ export interface EditorDisplay {
}
export const EDITOR_DISPLAY_NAMES: Record<EditorType, string> = {
zed: 'Zed',
vscode: 'VS Code',
windsurf: 'Windsurf',
cursor: 'Cursor',
@@ -27,7 +28,13 @@ class EditorSettingsManager {
private readonly availableEditors: EditorDisplay[];
constructor() {
const editorTypes: EditorType[] = ['vscode', 'windsurf', 'cursor', 'vim'];
const editorTypes: EditorType[] = [
'zed',
'vscode',
'windsurf',
'cursor',
'vim',
];
this.availableEditors = [
{
name: 'None',
@@ -7,7 +7,7 @@
import { describe, it, expect, vi, beforeEach, afterEach, Mock } from 'vitest';
import type { Mocked } from 'vitest';
import { handleAtCommand } from './atCommandProcessor.js';
import { Config, FileDiscoveryService } from '@gemini-cli/core';
import { Config, FileDiscoveryService } from '@google/gemini-cli-core';
import { ToolCallStatus } from '../types.js';
import { UseHistoryManagerReturn } from './useHistoryManager.js';
import * as fsPromises from 'fs/promises';
@@ -21,6 +21,7 @@ const mockConfig = {
isSandboxed: vi.fn(() => false),
getFileService: vi.fn(),
getFileFilteringRespectGitIgnore: vi.fn(() => true),
getEnableRecursiveFileSearch: vi.fn(() => true),
} as unknown as Config;
const mockReadManyFilesExecute = vi.fn();
@@ -51,8 +52,8 @@ vi.mock('fs/promises', async () => {
};
});
vi.mock('@gemini-cli/core', async () => {
const actual = await vi.importActual('@gemini-cli/core');
vi.mock('@google/gemini-cli-core', async () => {
const actual = await vi.importActual('@google/gemini-cli-core');
return {
...actual,
FileDiscoveryService: vi.fn(),
@@ -720,4 +721,35 @@ describe('handleAtCommand', () => {
expect(result.shouldProceed).toBe(true);
});
});
describe('when recursive file search is disabled', () => {
beforeEach(() => {
vi.mocked(mockConfig.getEnableRecursiveFileSearch).mockReturnValue(false);
});
it('should not use glob search for a nonexistent file', async () => {
const invalidFile = 'nonexistent.txt';
const query = `@${invalidFile}`;
vi.mocked(fsPromises.stat).mockRejectedValue(
Object.assign(new Error('ENOENT'), { code: 'ENOENT' }),
);
const result = await handleAtCommand({
query,
config: mockConfig,
addItem: mockAddItem,
onDebugMessage: mockOnDebugMessage,
messageId: 300,
signal: abortController.signal,
});
expect(mockGlobExecute).not.toHaveBeenCalled();
expect(mockOnDebugMessage).toHaveBeenCalledWith(
`Glob tool not found. Path ${invalidFile} will be skipped.`,
);
expect(result.processedQuery).toEqual([{ text: query }]);
expect(result.shouldProceed).toBe(true);
});
});
});
@@ -12,7 +12,7 @@ import {
getErrorMessage,
isNodeError,
unescapePath,
} from '@gemini-cli/core';
} from '@google/gemini-cli-core';
import {
HistoryItem,
IndividualToolCallDisplay,
@@ -210,10 +210,10 @@ export async function handleAtCommand({
resolvedSuccessfully = true;
} catch (error) {
if (isNodeError(error) && error.code === 'ENOENT') {
onDebugMessage(
`Path ${pathName} not found directly, attempting glob search.`,
);
if (globTool) {
if (config.getEnableRecursiveFileSearch() && globTool) {
onDebugMessage(
`Path ${pathName} not found directly, attempting glob search.`,
);
try {
const globResult = await globTool.execute(
{ pattern: `**/*${pathName}*`, path: config.getTargetDir() },
@@ -7,7 +7,7 @@
import { act, renderHook } from '@testing-library/react';
import { vi } from 'vitest';
import { useShellCommandProcessor } from './shellCommandProcessor';
import { Config, GeminiClient } from '@gemini-cli/core';
import { Config, GeminiClient } from '@google/gemini-cli-core';
import * as fs from 'fs';
import EventEmitter from 'events';
@@ -22,7 +22,7 @@ vi.mock('os', () => ({
platform: () => 'linux',
tmpdir: () => '/tmp',
}));
vi.mock('@gemini-cli/core');
vi.mock('@google/gemini-cli-core');
vi.mock('../utils/textUtils.js', () => ({
isBinary: vi.fn(),
}));
@@ -8,7 +8,7 @@ import { spawn } from 'child_process';
import { StringDecoder } from 'string_decoder';
import type { HistoryItemWithoutId } from '../types.js';
import { useCallback } from 'react';
import { Config, GeminiClient } from '@gemini-cli/core';
import { Config, GeminiClient } from '@google/gemini-cli-core';
import { type PartListUnion } from '@google/genai';
import { formatMemoryUsage } from '../utils/formatters.js';
import { isBinary } from '../utils/textUtils.js';
@@ -62,23 +62,15 @@ import {
} from './slashCommandProcessor.js';
import { MessageType } from '../types.js';
import {
type Config,
MCPServerStatus,
getMCPServerStatus,
Config,
MCPDiscoveryState,
MCPServerStatus,
getMCPDiscoveryState,
getMCPServerStatus,
GeminiClient,
} from '@gemini-cli/core';
} from '@google/gemini-cli-core';
import { useSessionStats } from '../contexts/SessionContext.js';
vi.mock('@gemini-code/core', async (importOriginal) => {
const actual = await importOriginal<typeof import('@gemini-code/core')>();
return {
...actual,
GitService: vi.fn(),
};
});
import { LoadedSettings } from '../../config/settings.js';
import * as ShowMemoryCommandModule from './useShowMemoryCommand.js';
import { GIT_COMMIT_INFO } from '../../generated/git-commit.js';
@@ -161,10 +153,16 @@ describe('useSlashCommandProcessor', () => {
process.env = { ...globalThis.process.env };
});
const getProcessorHook = (showToolDescriptions: boolean = false) =>
renderHook(() =>
const getProcessorHook = (showToolDescriptions: boolean = false) => {
const settings = {
merged: {
contextFileName: 'GEMINI.md',
},
} as LoadedSettings;
return renderHook(() =>
useSlashCommandProcessor(
mockConfig,
settings,
[],
mockAddItem,
mockClearItems,
@@ -181,6 +179,7 @@ describe('useSlashCommandProcessor', () => {
mockSetQuittingMessages,
),
);
};
const getProcessor = (showToolDescriptions: boolean = false) =>
getProcessorHook(showToolDescriptions).result.current;
@@ -253,7 +252,11 @@ describe('useSlashCommandProcessor', () => {
});
expect(
ShowMemoryCommandModule.createShowMemoryAction,
).toHaveBeenCalledWith(mockConfig, expect.any(Function));
).toHaveBeenCalledWith(
mockConfig,
expect.any(Object),
expect.any(Function),
);
expect(mockReturnedShowAction).toHaveBeenCalled();
expect(commandResult).toBe(true);
});
@@ -333,6 +336,89 @@ describe('useSlashCommandProcessor', () => {
});
});
describe('/about command', () => {
it('should show the about box with all details including auth and project', async () => {
// Arrange
mockGetCliVersionFn.mockResolvedValue('test-version');
process.env.SANDBOX = 'gemini-sandbox';
process.env.GOOGLE_CLOUD_PROJECT = 'test-gcp-project';
vi.mocked(mockConfig.getModel).mockReturnValue('test-model-from-config');
const settings = {
merged: {
selectedAuthType: 'test-auth-type',
contextFileName: 'GEMINI.md',
},
} as LoadedSettings;
const { result } = renderHook(() =>
useSlashCommandProcessor(
mockConfig,
settings,
[],
mockAddItem,
mockClearItems,
mockLoadHistory,
mockRefreshStatic,
mockSetShowHelp,
mockOnDebugMessage,
mockOpenThemeDialog,
mockOpenAuthDialog,
mockOpenEditorDialog,
mockPerformMemoryRefresh,
mockCorgiMode,
false,
mockSetQuittingMessages,
),
);
// Act
await act(async () => {
await result.current.handleSlashCommand('/about');
});
// Assert
expect(mockAddItem).toHaveBeenCalledTimes(2); // user message + about message
expect(mockAddItem).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
type: 'about',
cliVersion: 'test-version',
osVersion: 'test-platform',
sandboxEnv: 'gemini-sandbox',
modelVersion: 'test-model-from-config',
selectedAuthType: 'test-auth-type',
gcpProject: 'test-gcp-project',
}),
expect.any(Number),
);
});
it('should show sandbox-exec profile when applicable', async () => {
// Arrange
mockGetCliVersionFn.mockResolvedValue('test-version');
process.env.SANDBOX = 'sandbox-exec';
process.env.SEATBELT_PROFILE = 'test-profile';
vi.mocked(mockConfig.getModel).mockReturnValue('test-model-from-config');
const { result } = getProcessorHook();
// Act
await act(async () => {
await result.current.handleSlashCommand('/about');
});
// Assert
expect(mockAddItem).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
sandboxEnv: 'sandbox-exec (test-profile)',
}),
expect.any(Number),
);
});
});
describe('Other commands', () => {
it('/help should open help and return true', async () => {
const { handleSlashCommand } = getProcessor();
@@ -405,14 +491,7 @@ describe('useSlashCommandProcessor', () => {
// Use the mocked memoryUsage value
const memoryUsage = '11.8 MB';
const diagnosticInfo = `
## Describe the bug
A clear and concise description of what the bug is.
## Additional context
Add any other context about the problem here.
## Diagnostic Information
const info = `
* **CLI Version:** ${cliVersion}
* **Git Commit:** ${GIT_COMMIT_INFO}
* **Operating System:** ${osVersion}
@@ -421,11 +500,11 @@ Add any other context about the problem here.
* **Memory Usage:** ${memoryUsage}
`;
let url =
'https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.md';
'https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.yml';
if (description) {
url += `&title=${encodeURIComponent(description)}`;
}
url += `&body=${encodeURIComponent(diagnosticInfo)}`;
url += `&info=${encodeURIComponent(info)}`;
return url;
};
@@ -457,7 +536,7 @@ Add any other context about the problem here.
process.env.SEATBELT_PROFILE = 'permissive-open';
const bugCommand = {
urlTemplate:
'https://custom-bug-tracker.com/new?title={title}&body={body}',
'https://custom-bug-tracker.com/new?title={title}&info={info}',
};
mockConfig = {
...mockConfig,
@@ -467,14 +546,7 @@ Add any other context about the problem here.
const { handleSlashCommand } = getProcessor();
const bugDescription = 'This is a custom bug';
const diagnosticInfo = `
## Describe the bug
A clear and concise description of what the bug is.
## Additional context
Add any other context about the problem here.
## Diagnostic Information
const info = `
* **CLI Version:** 0.1.0
* **Git Commit:** ${GIT_COMMIT_INFO}
* **Operating System:** test-platform test-node-version
@@ -484,7 +556,7 @@ Add any other context about the problem here.
`;
const expectedUrl = bugCommand.urlTemplate
.replace('{title}', encodeURIComponent(bugDescription))
.replace('{body}', encodeURIComponent(diagnosticInfo));
.replace('{info}', encodeURIComponent(info));
let commandResult: SlashCommandActionReturn | boolean = false;
await act(async () => {
@@ -700,7 +772,7 @@ Add any other context about the problem here.
describe('/mcp command', () => {
beforeEach(() => {
// Mock the core module with getMCPServerStatus and getMCPDiscoveryState
vi.mock('@gemini-cli/core', async (importOriginal) => {
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual = await importOriginal();
return {
...actual,
@@ -742,7 +814,8 @@ Add any other context about the problem here.
expect(commandResult).toBe(true);
});
it('should display a message when no MCP servers are configured', async () => {
it('should display a message with a URL when no MCP servers are configured in a sandbox', async () => {
process.env.SANDBOX = 'sandbox';
mockConfig = {
...mockConfig,
getToolRegistry: vi.fn().mockResolvedValue({
@@ -761,11 +834,39 @@ Add any other context about the problem here.
2,
expect.objectContaining({
type: MessageType.INFO,
text: 'No MCP servers configured.',
text: `No MCP servers configured. Please open the following URL in your browser to view documentation:\nhttps://goo.gle/gemini-cli-docs-mcp`,
}),
expect.any(Number),
);
expect(commandResult).toBe(true);
delete process.env.SANDBOX;
});
it('should display a message and open a URL when no MCP servers are configured outside a sandbox', async () => {
mockConfig = {
...mockConfig,
getToolRegistry: vi.fn().mockResolvedValue({
getToolsByServer: vi.fn().mockReturnValue([]),
}),
getMcpServers: vi.fn().mockReturnValue({}),
} as unknown as Config;
const { handleSlashCommand } = getProcessor();
let commandResult: SlashCommandActionReturn | boolean = false;
await act(async () => {
commandResult = await handleSlashCommand('/mcp');
});
expect(mockAddItem).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
type: MessageType.INFO,
text: 'No MCP servers configured. Opening documentation in your browser: https://goo.gle/gemini-cli-docs-mcp',
}),
expect.any(Number),
);
expect(open).toHaveBeenCalledWith('https://goo.gle/gemini-cli-docs-mcp');
expect(commandResult).toBe(true);
});
it('should display configured MCP servers with status indicators and their tools', async () => {
+194 -111
View File
@@ -18,7 +18,7 @@ import {
MCPServerStatus,
getMCPDiscoveryState,
getMCPServerStatus,
} from '@gemini-cli/core';
} from '@google/gemini-cli-core';
import { useSessionStats } from '../contexts/SessionContext.js';
import {
Message,
@@ -32,6 +32,7 @@ import { createShowMemoryAction } from './useShowMemoryCommand.js';
import { GIT_COMMIT_INFO } from '../../generated/git-commit.js';
import { formatDuration, formatMemoryUsage } from '../utils/formatters.js';
import { getCliVersion } from '../../utils/version.js';
import { LoadedSettings } from '../../config/settings.js';
export interface SlashCommandActionReturn {
shouldScheduleTool?: boolean;
@@ -60,6 +61,7 @@ export interface SlashCommand {
*/
export const useSlashCommandProcessor = (
config: Config | null,
settings: LoadedSettings,
history: HistoryItem[],
addItem: UseHistoryManagerReturn['addItem'],
clearItems: UseHistoryManagerReturn['clearItems'],
@@ -74,6 +76,7 @@ export const useSlashCommandProcessor = (
toggleCorgiMode: () => void,
showToolDescriptions: boolean = false,
setQuittingMessages: (message: HistoryItem[]) => void,
openPrivacyNotice: () => void,
) => {
const session = useSessionStats();
const gitService = useMemo(() => {
@@ -101,6 +104,8 @@ export const useSlashCommandProcessor = (
osVersion: message.osVersion,
sandboxEnv: message.sandboxEnv,
modelVersion: message.modelVersion,
selectedAuthType: message.selectedAuthType,
gcpProject: message.gcpProject,
};
} else if (message.type === MessageType.STATS) {
historyItemContent = {
@@ -135,9 +140,9 @@ export const useSlashCommandProcessor = (
);
const showMemoryAction = useCallback(async () => {
const actionFn = createShowMemoryAction(config, addMessage);
const actionFn = createShowMemoryAction(config, settings, addMessage);
await actionFn();
}, [config, addMessage]);
}, [config, settings, addMessage]);
const addMemoryAction = useCallback(
(
@@ -169,6 +174,23 @@ export const useSlashCommandProcessor = (
[addMessage],
);
const savedChatTags = useCallback(async () => {
const geminiDir = config?.getProjectTempDir();
if (!geminiDir) {
return [];
}
try {
const files = await fs.readdir(geminiDir);
return files
.filter(
(file) => file.startsWith('checkpoint-') && file.endsWith('.json'),
)
.map((file) => file.replace('checkpoint-', '').replace('.json', ''));
} catch (_err) {
return [];
}
}, [config]);
const slashCommands: SlashCommand[] = useMemo(() => {
const commands: SlashCommand[] = [
{
@@ -180,6 +202,27 @@ export const useSlashCommandProcessor = (
setShowHelp(true);
},
},
{
name: 'docs',
description: 'open full Gemini CLI documentation in your browser',
action: async (_mainCommand, _subCommand, _args) => {
const docsUrl = 'https://goo.gle/gemini-cli-docs';
if (process.env.SANDBOX && process.env.SANDBOX !== 'sandbox-exec') {
addMessage({
type: MessageType.INFO,
content: `Please open the following URL in your browser to view the documentation:\n${docsUrl}`,
timestamp: new Date(),
});
} else {
addMessage({
type: MessageType.INFO,
content: `Opening documentation in your browser: ${docsUrl}`,
timestamp: new Date(),
});
await open(docsUrl);
}
},
},
{
name: 'clear',
description: 'clear the screen and conversation history',
@@ -212,6 +255,13 @@ export const useSlashCommandProcessor = (
openEditorDialog();
},
},
{
name: 'privacy',
description: 'display the privacy notice',
action: (_mainCommand, _subCommand, _args) => {
openPrivacyNotice();
},
},
{
name: 'stats',
altName: 'usage',
@@ -268,11 +318,21 @@ export const useSlashCommandProcessor = (
const serverNames = Object.keys(mcpServers);
if (serverNames.length === 0) {
addMessage({
type: MessageType.INFO,
content: 'No MCP servers configured.',
timestamp: new Date(),
});
const docsUrl = 'https://goo.gle/gemini-cli-docs-mcp';
if (process.env.SANDBOX && process.env.SANDBOX !== 'sandbox-exec') {
addMessage({
type: MessageType.INFO,
content: `No MCP servers configured. Please open the following URL in your browser to view documentation:\n${docsUrl}`,
timestamp: new Date(),
});
} else {
addMessage({
type: MessageType.INFO,
content: `No MCP servers configured. Opening documentation in your browser: ${docsUrl}`,
timestamp: new Date(),
});
await open(docsUrl);
}
return;
}
@@ -427,19 +487,27 @@ export const useSlashCommandProcessor = (
switch (subCommand) {
case 'show':
showMemoryAction();
return; // Explicitly return void
return;
case 'refresh':
performMemoryRefresh();
return; // Explicitly return void
return;
case 'add':
return addMemoryAction(mainCommand, subCommand, args); // Return the object
case undefined:
addMessage({
type: MessageType.ERROR,
content:
'Missing command\nUsage: /memory <show|refresh|add> [text for add]',
timestamp: new Date(),
});
return;
default:
addMessage({
type: MessageType.ERROR,
content: `Unknown /memory command: ${subCommand}. Available: show, refresh, add`,
timestamp: new Date(),
});
return; // Explicitly return void
return;
}
},
},
@@ -538,6 +606,8 @@ export const useSlashCommandProcessor = (
}
const modelVersion = config?.getModel() || 'Unknown';
const cliVersion = await getCliVersion();
const selectedAuthType = settings.merged.selectedAuthType || '';
const gcpProject = process.env.GOOGLE_CLOUD_PROJECT || '';
addMessage({
type: MessageType.ABOUT,
timestamp: new Date(),
@@ -545,6 +615,8 @@ export const useSlashCommandProcessor = (
osVersion,
sandboxEnv,
modelVersion,
selectedAuthType,
gcpProject,
});
},
},
@@ -571,14 +643,7 @@ export const useSlashCommandProcessor = (
const cliVersion = await getCliVersion();
const memoryUsage = formatMemoryUsage(process.memoryUsage().rss);
const diagnosticInfo = `
## Describe the bug
A clear and concise description of what the bug is.
## Additional context
Add any other context about the problem here.
## Diagnostic Information
const info = `
* **CLI Version:** ${cliVersion}
* **Git Commit:** ${GIT_COMMIT_INFO}
* **Operating System:** ${osVersion}
@@ -588,14 +653,14 @@ Add any other context about the problem here.
`;
let bugReportUrl =
'https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.md&title={title}&body={body}';
'https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.yml&title={title}&info={info}';
const bugCommand = config?.getBugCommand();
if (bugCommand?.urlTemplate) {
bugReportUrl = bugCommand.urlTemplate;
}
bugReportUrl = bugReportUrl
.replace('{title}', encodeURIComponent(bugDescription))
.replace('{body}', encodeURIComponent(diagnosticInfo));
.replace('{info}', encodeURIComponent(info));
addMessage({
type: MessageType.INFO,
@@ -618,109 +683,124 @@ Add any other context about the problem here.
},
},
{
name: 'save',
description: 'save conversation checkpoint. Usage: /save [tag]',
action: async (_mainCommand, subCommand, _args) => {
const tag = (subCommand || '').trim();
const logger = new Logger(config?.getSessionId() || '');
await logger.initialize();
const chat = await config?.getGeminiClient()?.getChat();
const history = chat?.getHistory() || [];
if (history.length > 0) {
await logger.saveCheckpoint(chat?.getHistory() || [], tag);
addMessage({
type: MessageType.INFO,
content: `Conversation checkpoint saved${tag ? ' with tag: ' + tag : ''}.`,
timestamp: new Date(),
});
} else {
addMessage({
type: MessageType.INFO,
content: 'No conversation found to save.',
timestamp: new Date(),
});
}
},
},
{
name: 'resume',
name: 'chat',
description:
'resume from conversation checkpoint. Usage: /resume [tag]',
completion: async () => {
const geminiDir = config?.getProjectTempDir();
if (!geminiDir) {
return [];
}
try {
const files = await fs.readdir(geminiDir);
return files
.filter(
(file) =>
file.startsWith('checkpoint-') && file.endsWith('.json'),
)
.map((file) =>
file.replace('checkpoint-', '').replace('.json', ''),
);
} catch (_err) {
return [];
}
},
action: async (_mainCommand, subCommand, _args) => {
const tag = (subCommand || '').trim();
'Manage conversation history. Usage: /chat <list|save|resume> [tag]',
action: async (_mainCommand, subCommand, args) => {
const tag = (args || '').trim();
const logger = new Logger(config?.getSessionId() || '');
await logger.initialize();
const conversation = await logger.loadCheckpoint(tag);
if (conversation.length === 0) {
addMessage({
type: MessageType.INFO,
content: `No saved checkpoint found${tag ? ' with tag: ' + tag : ''}.`,
timestamp: new Date(),
});
return;
}
const chat = await config?.getGeminiClient()?.getChat();
if (!chat) {
addMessage({
type: MessageType.ERROR,
content: 'No chat client available to resume conversation.',
content: 'No chat client available for conversation status.',
timestamp: new Date(),
});
return;
}
clearItems();
chat.clearHistory();
const rolemap: { [key: string]: MessageType } = {
user: MessageType.USER,
model: MessageType.GEMINI,
};
let i = 0;
for (const item of conversation) {
i += 1;
const text =
item.parts
?.filter((m) => !!m.text)
.map((m) => m.text)
.join('') || '';
if (i <= 2) {
// Skip system prompt back and forth.
continue;
}
if (!text) {
// Parsing Part[] back to various non-text output not yet implemented.
continue;
}
addItem(
{
type: (item.role && rolemap[item.role]) || MessageType.GEMINI,
text,
} as HistoryItemWithoutId,
i,
);
chat.addHistory(item);
if (!subCommand) {
addMessage({
type: MessageType.ERROR,
content: 'Missing command\nUsage: /chat <list|save|resume> [tag]',
timestamp: new Date(),
});
return;
}
switch (subCommand) {
case 'save': {
const history = chat.getHistory();
if (history.length > 0) {
await logger.saveCheckpoint(chat?.getHistory() || [], tag);
addMessage({
type: MessageType.INFO,
content: `Conversation checkpoint saved${tag ? ' with tag: ' + tag : ''}.`,
timestamp: new Date(),
});
} else {
addMessage({
type: MessageType.INFO,
content: 'No conversation found to save.',
timestamp: new Date(),
});
}
return;
}
case 'resume':
case 'restore':
case 'load': {
const conversation = await logger.loadCheckpoint(tag);
if (conversation.length === 0) {
addMessage({
type: MessageType.INFO,
content: `No saved checkpoint found${tag ? ' with tag: ' + tag : ''}.`,
timestamp: new Date(),
});
return;
}
clearItems();
chat.clearHistory();
const rolemap: { [key: string]: MessageType } = {
user: MessageType.USER,
model: MessageType.GEMINI,
};
let hasSystemPrompt = false;
let i = 0;
for (const item of conversation) {
i += 1;
// Add each item to history regardless of whether we display
// it.
chat.addHistory(item);
const text =
item.parts
?.filter((m) => !!m.text)
.map((m) => m.text)
.join('') || '';
if (!text) {
// Parsing Part[] back to various non-text output not yet implemented.
continue;
}
if (i === 1 && text.match(/context for our chat/)) {
hasSystemPrompt = true;
}
if (i > 2 || !hasSystemPrompt) {
addItem(
{
type:
(item.role && rolemap[item.role]) || MessageType.GEMINI,
text,
} as HistoryItemWithoutId,
i,
);
}
}
console.clear();
refreshStatic();
return;
}
case 'list':
addMessage({
type: MessageType.INFO,
content:
'list of saved conversations: ' +
(await savedChatTags()).join(', '),
timestamp: new Date(),
});
return;
default:
addMessage({
type: MessageType.ERROR,
content: `Unknown /chat command: ${subCommand}. Available: list, save, resume`,
timestamp: new Date(),
});
return;
}
console.clear();
refreshStatic();
},
completion: async () =>
(await savedChatTags()).map((tag) => 'resume ' + tag),
},
{
name: 'quit',
@@ -939,7 +1019,9 @@ Add any other context about the problem here.
addMemoryAction,
addMessage,
toggleCorgiMode,
savedChatTags,
config,
settings,
showToolDescriptions,
session,
gitService,
@@ -948,6 +1030,7 @@ Add any other context about the problem here.
setQuittingMessages,
pendingCompressionItemRef,
setPendingCompressionItem,
openPrivacyNotice,
]);
const handleSlashCommand = useCallback(
+2 -8
View File
@@ -11,7 +11,7 @@ import {
Config,
clearCachedCredentialFile,
getErrorMessage,
} from '@gemini-cli/core';
} from '@google/gemini-cli-core';
async function performAuthFlow(authMethod: AuthType, config: Config) {
await config.refreshAuth(authMethod);
@@ -46,13 +46,7 @@ export const useAuthCommand = (
config,
);
} catch (e) {
const errorMessage =
settings.merged.selectedAuthType ===
AuthType.LOGIN_WITH_GOOGLE_PERSONAL
? `Failed to login. Ensure your Google account is not an enterprise account.
Message: ${getErrorMessage(e)}`
: `Failed to login. Message: ${getErrorMessage(e)}`;
setAuthError(errorMessage);
setAuthError(`Failed to login. Message: ${getErrorMessage(e)}`);
openAuthDialog();
} finally {
setIsAuthenticating(false);

Some files were not shown because too many files have changed in this diff Show More